Index: /trunk/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java	(revision 14205)
+++ /trunk/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java	(revision 14205)
@@ -0,0 +1,318 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.gpx;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.Pair;
+
+/**
+ * Correlation logic for {@code CorrelateGpxWithImages}.
+ * @since 14205
+ */
+public final class GpxImageCorrelation {
+
+    private GpxImageCorrelation() {
+        // Hide public constructor
+    }
+
+    /**
+     * Match a list of photos to a gpx track with a given offset.
+     * All images need a exifTime attribute and the List must be sorted according to these times.
+     * @param images images to match
+     * @param selectedGpx selected GPX data
+     * @param offset offset
+     * @param forceTags force tagging of all photos, otherwise prefs are used
+     * @return number of matched points
+     */
+    public static int matchGpxTrack(List<? extends GpxImageEntry> images, GpxData selectedGpx, long offset, boolean forceTags) {
+        int ret = 0;
+
+        long prevWpTime = 0;
+        WayPoint prevWp = null;
+
+        List<List<List<WayPoint>>> trks = new ArrayList<>();
+
+        for (GpxTrack trk : selectedGpx.tracks) {
+            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;
+            }
+        });
+
+        boolean trkInt, trkTag, segInt, segTag;
+        int trkTime, trkDist, trkTagTime, segTime, segDist, segTagTime;
+
+        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;
+
+            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 {
+                            // Timestamps on waypoints without preceding timestamps in the same segment can not be interpolated, so try next one
+                            continue;
+                        }
+                    }
+
+                    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) > TimeUnit.MINUTES.toMillis(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) > TimeUnit.MINUTES.toMillis(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;
+    }
+
+    private static Double getElevation(WayPoint wp) {
+        String value = wp.getString(GpxConstants.PT_ELE);
+        if (value != null && !value.isEmpty()) {
+            try {
+                return Double.valueOf(value);
+            } catch (NumberFormatException e) {
+                Logging.warn(e);
+            }
+        }
+        return null;
+    }
+
+    private static int matchPoints(List<? extends GpxImageEntry> images, WayPoint prevWp, long prevWpTime, WayPoint curWp, long curWpTime,
+            long offset, boolean interpolate, int tagTime, boolean isLast) {
+
+        int ret = 0;
+
+        // i is the index of the timewise last photo that has the same or earlier EXIF time
+        int i;
+        if (isLast) {
+            i = images.size() - 1;
+        } else {
+            i = getLastIndexOfListBefore(images, curWpTime);
+        }
+
+        // no photos match
+        if (i < 0)
+            return 0;
+
+        Double speed = null;
+        Double prevElevation = null;
+
+        if (prevWp != null && interpolate) {
+            double distance = prevWp.getCoor().greatCircleDistance(curWp.getCoor());
+            // This is in km/h, 3.6 * m/s
+            if (curWpTime > prevWpTime) {
+                speed = 3600 * distance / (curWpTime - prevWpTime);
+            }
+            prevElevation = getElevation(prevWp);
+        }
+
+        Double curElevation = getElevation(curWp);
+
+        if (!interpolate || isLast) {
+            final long half = Math.abs(curWpTime - prevWpTime) / 2;
+            while (i >= 0) {
+                final GpxImageEntry curImg = images.get(i);
+                final GpxImageEntry curTmp = curImg.getTmp();
+                final long time = curImg.getExifTime().getTime();
+                if ((!isLast && time > curWpTime) || time < prevWpTime) {
+                    break;
+                }
+                long tagms = TimeUnit.MINUTES.toMillis(tagTime);
+                if (curTmp.getPos() == null &&
+                        (Math.abs(time - curWpTime) <= tagms
+                        || Math.abs(prevWpTime - time) <= tagms)) {
+                    if (prevWp != null && time < curWpTime - half) {
+                        curTmp.setPos(prevWp.getCoor());
+                    } else {
+                        curTmp.setPos(curWp.getCoor());
+                    }
+                    curTmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
+                    curTmp.flagNewGpsData();
+                    ret++;
+                }
+                i--;
+            }
+        } 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) {
+                GpxImageEntry curImg = images.get(i);
+                GpxImageEntry curTmp = curImg.getTmp();
+                final long imgTime = curImg.getExifTime().getTime();
+                if (imgTime < prevWpTime) {
+                    break;
+                }
+                if (curTmp.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);
+                    curTmp.setPos(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
+                    curTmp.setSpeed(speed);
+                    if (curElevation != null && prevElevation != null) {
+                        curTmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);
+                    }
+                    curTmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
+                    curTmp.flagNewGpsData();
+
+                    ret++;
+                }
+                i--;
+            }
+        }
+        return ret;
+    }
+
+    private static int getLastIndexOfListBefore(List<? extends GpxImageEntry> images, long searchedTime) {
+        int lstSize = images.size();
+
+        // No photos or the first photo taken is later than the search period
+        if (lstSize == 0 || searchedTime < images.get(0).getExifTime().getTime())
+            return -1;
+
+        // The search period is later than the last photo
+        if (searchedTime > images.get(lstSize - 1).getExifTime().getTime())
+            return lstSize-1;
+
+        // The searched index is somewhere in the middle, do a binary search from the beginning
+        int curIndex;
+        int startIndex = 0;
+        int endIndex = lstSize-1;
+        while (endIndex - startIndex > 1) {
+            curIndex = (endIndex + startIndex) / 2;
+            if (searchedTime > images.get(curIndex).getExifTime().getTime()) {
+                startIndex = curIndex;
+            } else {
+                endIndex = curIndex;
+            }
+        }
+        if (searchedTime < images.get(endIndex).getExifTime().getTime())
+            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()
+                == images.get(endIndex + 1).getExifTime().getTime())) {
+            endIndex++;
+        }
+        return endIndex;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java	(revision 14205)
+++ /trunk/src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java	(revision 14205)
@@ -0,0 +1,531 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.gpx;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+
+import org.openstreetmap.josm.data.coor.CachedLatLon;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.tools.ExifReader;
+import org.openstreetmap.josm.tools.JosmRuntimeException;
+import org.openstreetmap.josm.tools.Logging;
+
+import com.drew.imaging.jpeg.JpegMetadataReader;
+import com.drew.lang.CompoundException;
+import com.drew.metadata.Directory;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.MetadataException;
+import com.drew.metadata.exif.ExifIFD0Directory;
+import com.drew.metadata.exif.GpsDirectory;
+import com.drew.metadata.jpeg.JpegDirectory;
+
+/**
+ * Stores info about each image
+ * @since 14205 (extracted from gui.layer.geoimage.ImageEntry)
+ */
+public class GpxImageEntry implements Comparable<GpxImageEntry>, Cloneable {
+    private File file;
+    private Integer exifOrientation;
+    private LatLon exifCoor;
+    private Double exifImgDir;
+    private Date exifTime;
+    /**
+     * Flag isNewGpsData indicates that the GPS data of the image is new or has changed.
+     * GPS data includes the position, speed, elevation, time (e.g. as extracted from the GPS track).
+     * The flag can used to decide for which image file the EXIF GPS data is (re-)written.
+     */
+    private boolean isNewGpsData;
+    /** Temporary source of GPS time if not correlated with GPX track. */
+    private Date exifGpsTime;
+
+    /**
+     * The following values are computed from the correlation with the gpx track
+     * or extracted from the image EXIF data.
+     */
+    private CachedLatLon pos;
+    /** Speed in kilometer per hour */
+    private Double speed;
+    /** Elevation (altitude) in meters */
+    private Double elevation;
+    /** The time after correlation with a gpx track */
+    private Date gpsTime;
+
+    private int width;
+    private int height;
+
+    /**
+     * When the correlation dialog is open, we like to show the image position
+     * for the current time offset on the map in real time.
+     * On the other hand, when the user aborts this operation, the old values
+     * should be restored. We have a temporary copy, that overrides
+     * the normal values if it is not null. (This may be not the most elegant
+     * solution for this, but it works.)
+     */
+    private GpxImageEntry tmp;
+
+    /**
+     * Constructs a new {@code GpxImageEntry}.
+     */
+    public GpxImageEntry() {}
+
+    /**
+     * Constructs a new {@code GpxImageEntry}.
+     * @param file Path to image file on disk
+     */
+    public GpxImageEntry(File file) {
+        setFile(file);
+    }
+
+    /**
+     * Returns width of the image this GpxImageEntry represents.
+     * @return width of the image this GpxImageEntry represents
+     * @since 13220
+     */
+    public int getWidth() {
+        return width;
+    }
+
+    /**
+     * Returns height of the image this GpxImageEntry represents.
+     * @return height of the image this GpxImageEntry represents
+     * @since 13220
+     */
+    public int getHeight() {
+        return height;
+    }
+
+    /**
+     * Returns the position value. The position value from the temporary copy
+     * is returned if that copy exists.
+     * @return the position value
+     */
+    public CachedLatLon getPos() {
+        if (tmp != null)
+            return tmp.pos;
+        return pos;
+    }
+
+    /**
+     * Returns the speed value. The speed value from the temporary copy is
+     * returned if that copy exists.
+     * @return the speed value
+     */
+    public Double getSpeed() {
+        if (tmp != null)
+            return tmp.speed;
+        return speed;
+    }
+
+    /**
+     * Returns the elevation value. The elevation value from the temporary
+     * copy is returned if that copy exists.
+     * @return the elevation value
+     */
+    public Double getElevation() {
+        if (tmp != null)
+            return tmp.elevation;
+        return elevation;
+    }
+
+    /**
+     * Returns the GPS time value. The GPS time value from the temporary copy
+     * is returned if that copy exists.
+     * @return the GPS time value
+     */
+    public Date getGpsTime() {
+        if (tmp != null)
+            return getDefensiveDate(tmp.gpsTime);
+        return getDefensiveDate(gpsTime);
+    }
+
+    /**
+     * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy.
+     * @return {@code true} if this entry has a GPS time
+     * @since 6450
+     */
+    public boolean hasGpsTime() {
+        return (tmp != null && tmp.gpsTime != null) || gpsTime != null;
+    }
+
+    /**
+     * Returns associated file.
+     * @return associated file
+     */
+    public File getFile() {
+        return file;
+    }
+
+    /**
+     * Returns EXIF orientation
+     * @return EXIF orientation
+     */
+    public Integer getExifOrientation() {
+        return exifOrientation != null ? exifOrientation : 1;
+    }
+
+    /**
+     * Returns EXIF time
+     * @return EXIF time
+     */
+    public Date getExifTime() {
+        return getDefensiveDate(exifTime);
+    }
+
+    /**
+     * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy.
+     * @return {@code true} if this entry has a EXIF time
+     * @since 6450
+     */
+    public boolean hasExifTime() {
+        return exifTime != null;
+    }
+
+    /**
+     * Returns the EXIF GPS time.
+     * @return the EXIF GPS time
+     * @since 6392
+     */
+    public Date getExifGpsTime() {
+        return getDefensiveDate(exifGpsTime);
+    }
+
+    /**
+     * Convenient way to determine if this entry has a EXIF GPS time, without the cost of building a defensive copy.
+     * @return {@code true} if this entry has a EXIF GPS time
+     * @since 6450
+     */
+    public boolean hasExifGpsTime() {
+        return exifGpsTime != null;
+    }
+
+    private static Date getDefensiveDate(Date date) {
+        if (date == null)
+            return null;
+        return new Date(date.getTime());
+    }
+
+    public LatLon getExifCoor() {
+        return exifCoor;
+    }
+
+    public Double getExifImgDir() {
+        if (tmp != null)
+            return tmp.exifImgDir;
+        return exifImgDir;
+    }
+
+    /**
+     * Sets the width of this GpxImageEntry.
+     * @param width set the width of this GpxImageEntry
+     * @since 13220
+     */
+    public void setWidth(int width) {
+        this.width = width;
+    }
+
+    /**
+     * Sets the height of this GpxImageEntry.
+     * @param height set the height of this GpxImageEntry
+     * @since 13220
+     */
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    /**
+     * Sets the position.
+     * @param pos cached position
+     */
+    public void setPos(CachedLatLon pos) {
+        this.pos = pos;
+    }
+
+    /**
+     * Sets the position.
+     * @param pos position (will be cached)
+     */
+    public void setPos(LatLon pos) {
+        setPos(pos != null ? new CachedLatLon(pos) : null);
+    }
+
+    /**
+     * Sets the speed.
+     * @param speed speed
+     */
+    public void setSpeed(Double speed) {
+        this.speed = speed;
+    }
+
+    /**
+     * Sets the elevation.
+     * @param elevation elevation
+     */
+    public void setElevation(Double elevation) {
+        this.elevation = elevation;
+    }
+
+    /**
+     * Sets associated file.
+     * @param file associated file
+     */
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+    /**
+     * Sets EXIF orientation.
+     * @param exifOrientation EXIF orientation
+     */
+    public void setExifOrientation(Integer exifOrientation) {
+        this.exifOrientation = exifOrientation;
+    }
+
+    /**
+     * Sets EXIF time.
+     * @param exifTime EXIF time
+     */
+    public void setExifTime(Date exifTime) {
+        this.exifTime = getDefensiveDate(exifTime);
+    }
+
+    /**
+     * Sets the EXIF GPS time.
+     * @param exifGpsTime the EXIF GPS time
+     * @since 6392
+     */
+    public void setExifGpsTime(Date exifGpsTime) {
+        this.exifGpsTime = getDefensiveDate(exifGpsTime);
+    }
+
+    public void setGpsTime(Date gpsTime) {
+        this.gpsTime = getDefensiveDate(gpsTime);
+    }
+
+    public void setExifCoor(LatLon exifCoor) {
+        this.exifCoor = exifCoor;
+    }
+
+    public void setExifImgDir(Double exifDir) {
+        this.exifImgDir = exifDir;
+    }
+
+    @Override
+    public GpxImageEntry clone() {
+        try {
+            return (GpxImageEntry) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public int compareTo(GpxImageEntry image) {
+        if (exifTime != null && image.exifTime != null)
+            return exifTime.compareTo(image.exifTime);
+        else if (exifTime == null && image.exifTime == null)
+            return 0;
+        else if (exifTime == null)
+            return -1;
+        else
+            return 1;
+    }
+
+    /**
+     * Make a fresh copy and save it in the temporary variable. Use
+     * {@link #applyTmp()} or {@link #discardTmp()} if the temporary variable
+     * is not needed anymore.
+     */
+    public void createTmp() {
+        tmp = clone();
+        tmp.tmp = null;
+    }
+
+    /**
+     * Get temporary variable that is used for real time parameter
+     * adjustments. The temporary variable is created if it does not exist
+     * yet. Use {@link #applyTmp()} or {@link #discardTmp()} if the temporary
+     * variable is not needed anymore.
+     * @return temporary variable
+     */
+    public GpxImageEntry getTmp() {
+        if (tmp == null) {
+            createTmp();
+        }
+        return tmp;
+    }
+
+    /**
+     * Copy the values from the temporary variable to the main instance. The
+     * temporary variable is deleted.
+     * @see #discardTmp()
+     */
+    public void applyTmp() {
+        if (tmp != null) {
+            pos = tmp.pos;
+            speed = tmp.speed;
+            elevation = tmp.elevation;
+            gpsTime = tmp.gpsTime;
+            exifImgDir = tmp.exifImgDir;
+            isNewGpsData = tmp.isNewGpsData;
+            tmp = null;
+        }
+    }
+
+    /**
+     * Delete the temporary variable. Temporary modifications are lost.
+     * @see #applyTmp()
+     */
+    public void discardTmp() {
+        tmp = null;
+    }
+
+    /**
+     * If it has been tagged i.e. matched to a gpx track or retrieved lat/lon from exif
+     * @return {@code true} if it has been tagged
+     */
+    public boolean isTagged() {
+        return pos != null;
+    }
+
+    /**
+     * String representation. (only partial info)
+     */
+    @Override
+    public String toString() {
+        return file.getName()+": "+
+        "pos = "+pos+" | "+
+        "exifCoor = "+exifCoor+" | "+
+        (tmp == null ? " tmp==null" :
+            " [tmp] pos = "+tmp.pos);
+    }
+
+    /**
+     * Indicates that the image has new GPS data.
+     * That flag is set by new GPS data providers.  It is used e.g. by the photo_geotagging plugin
+     * to decide for which image file the EXIF GPS data needs to be (re-)written.
+     * @since 6392
+     */
+    public void flagNewGpsData() {
+        isNewGpsData = true;
+   }
+
+    /**
+     * Remove the flag that indicates new GPS data.
+     * The flag is cleared by a new GPS data consumer.
+     */
+    public void unflagNewGpsData() {
+        isNewGpsData = false;
+    }
+
+    /**
+     * Queries whether the GPS data changed. The flag value from the temporary
+     * copy is returned if that copy exists.
+     * @return {@code true} if GPS data changed, {@code false} otherwise
+     * @since 6392
+     */
+    public boolean hasNewGpsData() {
+        if (tmp != null)
+            return tmp.isNewGpsData;
+        return isNewGpsData;
+    }
+
+    /**
+     * Extract GPS metadata from image EXIF. Has no effect if the image file is not set
+     *
+     * If successful, fills in the LatLon, speed, elevation, image direction, and other attributes
+     * @since 9270
+     */
+    public void extractExif() {
+
+        Metadata metadata;
+
+        if (file == null) {
+            return;
+        }
+
+        try {
+            metadata = JpegMetadataReader.readMetadata(file);
+        } catch (CompoundException | IOException ex) {
+            Logging.error(ex);
+            setExifTime(null);
+            setExifCoor(null);
+            setPos(null);
+            return;
+        }
+
+        // Changed to silently cope with no time info in exif. One case
+        // of person having time that couldn't be parsed, but valid GPS info
+        try {
+            setExifTime(ExifReader.readTime(metadata));
+        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
+            Logging.warn(ex);
+            setExifTime(null);
+        }
+
+        final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class);
+        final Directory dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
+        final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
+
+        try {
+            if (dirExif != null) {
+                int orientation = dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION);
+                setExifOrientation(orientation);
+            }
+        } catch (MetadataException ex) {
+            Logging.debug(ex);
+        }
+
+        try {
+            if (dir != null) {
+                // there are cases where these do not match width and height stored in dirExif
+                int width = dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH);
+                int height = dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT);
+                setWidth(width);
+                setHeight(height);
+            }
+        } catch (MetadataException ex) {
+            Logging.debug(ex);
+        }
+
+        if (dirGps == null) {
+            setExifCoor(null);
+            setPos(null);
+            return;
+        }
+
+        final Double speed = ExifReader.readSpeed(dirGps);
+        if (speed != null) {
+            setSpeed(speed);
+        }
+
+        final Double ele = ExifReader.readElevation(dirGps);
+        if (ele != null) {
+            setElevation(ele);
+        }
+
+        try {
+            final LatLon latlon = ExifReader.readLatLon(dirGps);
+            setExifCoor(latlon);
+            setPos(getExifCoor());
+        } catch (MetadataException | IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
+            Logging.error("Error reading EXIF from file: " + ex);
+            setExifCoor(null);
+            setPos(null);
+        }
+
+        try {
+            final Double direction = ExifReader.readDirection(dirGps);
+            if (direction != null) {
+                setExifImgDir(direction);
+            }
+        } catch (IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
+            Logging.debug(ex);
+        }
+
+        final Date gpsDate = dirGps.getGpsDate();
+        if (gpsDate != null) {
+            setExifGpsTime(gpsDate);
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/data/gpx/GpxTimeOffset.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/gpx/GpxTimeOffset.java	(revision 14205)
+++ /trunk/src/org/openstreetmap/josm/data/gpx/GpxTimeOffset.java	(revision 14205)
@@ -0,0 +1,145 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.gpx;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.text.ParseException;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider;
+import org.openstreetmap.josm.tools.Pair;
+
+/**
+ * Time offset of GPX correlation.
+ * @since 14205 (extracted from {@code CorrelateGpxWithImages})
+ */
+public final class GpxTimeOffset {
+
+    /**
+     * The time offset 0.
+     */
+    public static final GpxTimeOffset ZERO = new GpxTimeOffset(0);
+    private final long milliseconds;
+
+    private GpxTimeOffset(long milliseconds) {
+        this.milliseconds = milliseconds;
+    }
+
+    /**
+     * Constructs a new {@code GpxTimeOffset} from milliseconds.
+     * @param milliseconds time offset in milliseconds.
+     * @return new {@code GpxTimeOffset}
+     */
+    public static GpxTimeOffset milliseconds(long milliseconds) {
+        return new GpxTimeOffset(milliseconds);
+    }
+
+    /**
+     * Constructs a new {@code GpxTimeOffset} from seconds.
+     * @param seconds time offset in seconds.
+     * @return new {@code GpxTimeOffset}
+     */
+    public static GpxTimeOffset seconds(long seconds) {
+        return new GpxTimeOffset(1000 * seconds);
+    }
+
+    /**
+     * Get time offset in milliseconds.
+     * @return time offset in milliseconds
+     */
+    public long getMilliseconds() {
+        return milliseconds;
+    }
+
+    /**
+     * Get time offset in seconds.
+     * @return time offset in seconds
+     */
+    public long getSeconds() {
+        return milliseconds / 1000;
+    }
+
+    /**
+     * Formats time offset.
+     * @return formatted time offset. Format: decimal number
+     */
+    public String formatOffset() {
+        if (milliseconds % 1000 == 0) {
+            return Long.toString(milliseconds / 1000);
+        } else if (milliseconds % 100 == 0) {
+            return String.format(Locale.ENGLISH, "%.1f", milliseconds / 1000.);
+        } else {
+            return String.format(Locale.ENGLISH, "%.3f", milliseconds / 1000.);
+        }
+    }
+
+    /**
+     * Parses time offset.
+     * @param offset time offset. Format: decimal number
+     * @return time offset
+     * @throws ParseException if time offset can't be parsed
+     */
+    public static GpxTimeOffset parseOffset(String offset) throws ParseException {
+        String error = tr("Error while parsing offset.\nExpected format: {0}", "number");
+
+        if (!offset.isEmpty()) {
+            try {
+                if (offset.startsWith("+")) {
+                    offset = offset.substring(1);
+                }
+                return GpxTimeOffset.milliseconds(Math.round(JosmDecimalFormatSymbolsProvider.parseDouble(offset) * 1000));
+            } catch (NumberFormatException nfe) {
+                throw (ParseException) new ParseException(error, 0).initCause(nfe);
+            }
+        } else {
+            return GpxTimeOffset.ZERO;
+        }
+    }
+
+    /**
+     * Returns the day difference.
+     * @return the day difference
+     */
+    public int getDayOffset() {
+        // Find day difference
+        return (int) Math.round(((double) getMilliseconds()) / TimeUnit.DAYS.toMillis(1));
+    }
+
+    /**
+     * Returns offset without day difference.
+     * @return offset without day difference
+     */
+    public GpxTimeOffset withoutDayOffset() {
+        return milliseconds(getMilliseconds() - TimeUnit.DAYS.toMillis(getDayOffset()));
+    }
+
+    /**
+     * Split out timezone and offset.
+     * @return pair of timezone and offset
+     */
+    public Pair<GpxTimezone, GpxTimeOffset> splitOutTimezone() {
+        // In hours
+        final double tz = ((double) withoutDayOffset().getSeconds()) / TimeUnit.HOURS.toSeconds(1);
+
+        // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with
+        // -2 minutes offset. This determines the real timezone and finds offset.
+        final double timezone = (double) Math.round(tz * 2) / 2; // hours, rounded to one decimal place
+        final long delta = Math.round(getMilliseconds() - timezone * TimeUnit.HOURS.toMillis(1));
+        return Pair.create(new GpxTimezone(timezone), GpxTimeOffset.milliseconds(delta));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof GpxTimeOffset)) return false;
+        GpxTimeOffset offset = (GpxTimeOffset) o;
+        return milliseconds == offset.milliseconds;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(milliseconds);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/data/gpx/GpxTimezone.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/gpx/GpxTimezone.java	(revision 14205)
+++ /trunk/src/org/openstreetmap/josm/data/gpx/GpxTimezone.java	(revision 14205)
@@ -0,0 +1,102 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.gpx;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.text.ParseException;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Timezone in hours.<p>
+ * TODO: should probably be replaced by {@link java.util.TimeZone}.
+ * @since 14205 (extracted from {@code CorrelateGpxWithImages})
+ */
+public final class GpxTimezone {
+
+    /**
+     * The timezone 0.
+     */
+    public static final GpxTimezone ZERO = new GpxTimezone(0.0);
+    private final double timezone;
+
+    /**
+     * Construcs a new {@code GpxTimezone}.
+     * @param hours timezone in hours
+     */
+    public GpxTimezone(double hours) {
+        this.timezone = hours;
+    }
+
+    /**
+     * Returns the timezone in hours.
+     * @return the timezone in hours
+     */
+    public double getHours() {
+        return timezone;
+    }
+
+    /**
+     * Formats time zone.
+     * @return formatted time zone. Format: ±HH:MM
+     */
+    public String formatTimezone() {
+        StringBuilder ret = new StringBuilder();
+
+        double timezone = this.timezone;
+        if (timezone < 0) {
+            ret.append('-');
+            timezone = -timezone;
+        } else {
+            ret.append('+');
+        }
+        ret.append((long) timezone).append(':');
+        int minutes = (int) ((timezone % 1) * 60);
+        if (minutes < 10) {
+            ret.append('0');
+        }
+        ret.append(minutes);
+
+        return ret.toString();
+    }
+
+    /**
+     * Parses timezone.
+     * @param timezone timezone. Expected format: ±HH:MM
+     * @return timezone
+     * @throws ParseException if timezone can't be parsed
+     */
+    public static GpxTimezone parseTimezone(String timezone) throws ParseException {
+        if (timezone.isEmpty())
+            return ZERO;
+
+        Matcher m = Pattern.compile("^([\\+\\-]?)(\\d{1,2})(?:\\:([0-5]\\d))?$").matcher(timezone);
+
+        ParseException pe = new ParseException(tr("Error while parsing timezone.\nExpected format: {0}", "±HH:MM"), 0);
+        try {
+            if (m.find()) {
+                int sign = "-".equals(m.group(1)) ? -1 : 1;
+                int hour = Integer.parseInt(m.group(2));
+                int min = m.group(3) == null ? 0 : Integer.parseInt(m.group(3));
+                return new GpxTimezone(sign * (hour + min / 60.0));
+            }
+        } catch (IndexOutOfBoundsException | NumberFormatException ex) {
+            pe.initCause(ex);
+        }
+        throw pe;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof GpxTimezone)) return false;
+        GpxTimezone timezone1 = (GpxTimezone) o;
+        return Double.compare(timezone1.timezone, timezone) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(timezone);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 14204)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 14205)
@@ -6,4 +6,5 @@
 
 import java.awt.BorderLayout;
+import java.awt.Component;
 import java.awt.Cursor;
 import java.awt.Dimension;
@@ -35,4 +36,5 @@
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.TimeZone;
@@ -44,4 +46,5 @@
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
+import javax.swing.JComponent;
 import javax.swing.JFileChooser;
 import javax.swing.JLabel;
@@ -52,7 +55,10 @@
 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;
@@ -62,6 +68,8 @@
 import org.openstreetmap.josm.actions.DiskAccessAction;
 import org.openstreetmap.josm.actions.ExtensionFileFilter;
-import org.openstreetmap.josm.data.gpx.GpxConstants;
 import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxImageCorrelation;
+import org.openstreetmap.josm.data.gpx.GpxTimeOffset;
+import org.openstreetmap.josm.data.gpx.GpxTimezone;
 import org.openstreetmap.josm.data.gpx.GpxTrack;
 import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
@@ -75,4 +83,8 @@
 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;
@@ -84,4 +96,5 @@
 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;
@@ -101,6 +114,7 @@
 
     private final transient GeoImageLayer yLayer;
-    private transient Timezone timezone;
-    private transient Offset delta;
+    private transient GpxTimezone timezone;
+    private transient GpxTimeOffset delta;
+    private static boolean forceTags = false;
 
     /**
@@ -112,4 +126,5 @@
         new ImageProvider("dialogs/geoimage/gpx2img").getResource().attachImageIcon(this, true);
         this.yLayer = layer;
+        MainApplication.getLayerManager().addLayerChangeListener(new GpxLayerAddedListener());
     }
 
@@ -130,5 +145,5 @@
             // Parse values again, to display an error if the format is not recognized
             try {
-                timezone = Timezone.parseTimezone(tfTimezone.getText().trim());
+                timezone = GpxTimezone.parseTimezone(tfTimezone.getText().trim());
             } catch (ParseException e) {
                 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), e.getMessage(),
@@ -138,5 +153,5 @@
 
             try {
-                delta = Offset.parseOffset(tfOffset.getText().trim());
+                delta = GpxTimeOffset.parseOffset(tfOffset.getText().trim());
             } catch (ParseException e) {
                 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), e.getMessage(),
@@ -323,4 +338,202 @@
     }
 
+    private class AdvancedSettingsActionListener implements ActionListener {
+
+        private class CheckBoxActionListener implements ActionListener {
+            private final JComponent[] comps;
+
+            CheckBoxActionListener(JComponent... c) {
+                comps = Objects.requireNonNull(c);
+            }
+
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                setEnabled((JCheckBox) e.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 e) {
+
+            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 # minutes apart:"),
+                    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);
+            JPanel pInterpolSegTime = new JPanel(layout);
+            pInterpolSegTime.add(cInterpolSegTime);
+            pInterpolSegTime.add(sInterpolSegTime);
+            pInterpolSegTime.setBorder(border);
+            p.add(pInterpolSegTime, GBC.eol());
+
+            JCheckBox cInterpolSegDist = new JCheckBox(tr("only when the segments are less than # meters apart:"),
+                    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);
+            JPanel pInterpolSegDist = new JPanel(layout);
+            pInterpolSegDist.add(cInterpolSegDist);
+            pInterpolSegDist.add(sInterpolSegDist);
+            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 # minutes of the closest trackpoint:"),
+                    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);
+            JPanel pTagSegTime = new JPanel(layout);
+            pTagSegTime.add(cTagSegTime);
+            pTagSegTime.add(sTagSegTime);
+            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 # minutes apart:"),
+                    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);
+            JPanel pInterpolTrackTime = new JPanel(layout);
+            pInterpolTrackTime.add(cInterpolTrackTime);
+            pInterpolTrackTime.add(sInterpolTrackTime);
+            pInterpolTrackTime.setBorder(border);
+            p.add(pInterpolTrackTime, GBC.eol());
+
+            JCheckBox cInterpolTrackDist = new JCheckBox(tr("only when the tracks are less than # meters apart:"),
+                    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);
+            JPanel pInterpolTrackDist = new JPanel(layout);
+            pInterpolTrackDist.add(cInterpolTrackDist);
+            pInterpolTrackDist.add(sInterpolTrackDist);
+            pInterpolTrackDist.setBorder(border);
+            p.add(pInterpolTrackDist, GBC.eol());
+
+            JCheckBox cTagTrack = new JCheckBox("<html>" +
+                    tr("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 # minutes of the closest trackpoint:"),
+                    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);
+            JPanel pTagTrackTime = new JPanel(layout);
+            pTagTrackTime.add(cTagTrackTime);
+            pTagTrackTime.add(sTagTrackTime);
+            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("<html>" +
+                    tr("Force tagging of all pictures (temporarily overrides the settings above).") + "<br>" +
+                    tr("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
@@ -399,5 +612,5 @@
 
                 String tzDesc = tzStr + " (" +
-                        new Timezone(((double) tz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() +
+                        new GpxTimezone(((double) tz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() +
                         ')';
                 vtTimezones.add(tzDesc);
@@ -417,5 +630,5 @@
 
             cbTimezones.setSelectedItem(defaultTz.getID() + " (" +
-                    new Timezone(((double) defaultTz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() +
+                    new GpxTimezone(((double) defaultTz.getRawOffset()) / TimeUnit.HOURS.toMillis(1)).formatTimezone() +
                     ')');
 
@@ -515,5 +728,5 @@
 
                 Config.getPref().put("geoimage.timezoneid", tzId);
-                tfOffset.setText(Offset.milliseconds(delta).formatOffset());
+                tfOffset.setText(GpxTimeOffset.milliseconds(delta).formatOffset());
                 tfTimezone.setText(tzValue);
 
@@ -524,4 +737,30 @@
             yLayer.updateBufferAndRepaint();
         }
+    }
+
+    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) {}
     }
 
@@ -578,7 +817,8 @@
 
         try {
-            timezone = Timezone.parseTimezone(Optional.ofNullable(Config.getPref().get("geoimage.timezone", "0:00")).orElse("0:00"));
+            timezone = GpxTimezone.parseTimezone(Optional.ofNullable(Config.getPref().get("geoimage.timezone", "0:00")).orElse("0:00"));
         } catch (ParseException e) {
-            timezone = Timezone.ZERO;
+            timezone = GpxTimezone.ZERO;
+            Logging.trace(e);
         }
 
@@ -587,7 +827,8 @@
 
         try {
-            delta = Offset.parseOffset(Config.getPref().get("geoimage.delta", "0"));
+            delta = GpxTimeOffset.parseOffset(Config.getPref().get("geoimage.delta", "0"));
         } catch (ParseException e) {
-            delta = Offset.ZERO;
+            delta = GpxTimeOffset.ZERO;
+            Logging.trace(e);
         }
 
@@ -606,4 +847,7 @@
         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: "));
@@ -668,7 +912,10 @@
 
         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);
 
@@ -716,4 +963,5 @@
 
         statusBarUpdater.updateStatusBar();
+        yLayer.updateBufferAndRepaint();
 
         outerPanel = new JPanel(new BorderLayout());
@@ -782,6 +1030,6 @@
         private String statusText() {
             try {
-                timezone = Timezone.parseTimezone(tfTimezone.getText().trim());
-                delta = Offset.parseOffset(tfOffset.getText().trim());
+                timezone = GpxTimezone.parseTimezone(tfTimezone.getText().trim());
+                delta = GpxTimeOffset.parseOffset(tfOffset.getText().trim());
             } catch (ParseException e) {
                 return e.getMessage();
@@ -801,5 +1049,5 @@
             for (ImageEntry ie : dateImgLst) {
                 ie.createTmp();
-                ie.tmp.setPos(null);
+                ie.getTmp().setPos(null);
             }
 
@@ -808,6 +1056,6 @@
                 return tr("No gpx selected");
 
-            final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(1))) + delta.getMilliseconds(); // in milliseconds
-            lastNumMatched = matchGpxTrack(dateImgLst, selGpx.data, offsetMs);
+            final long offsetMs = ((long) (timezone.getHours() * TimeUnit.HOURS.toMillis(-1))) + delta.getMilliseconds(); // in milliseconds
+            lastNumMatched = GpxImageCorrelation.matchGpxTrack(dateImgLst, selGpx.data, offsetMs, forceTags);
 
             return trn("<html>Matched <b>{0}</b> of <b>{1}</b> photo to GPX track.</html>",
@@ -838,8 +1086,8 @@
         public void actionPerformed(ActionEvent arg0) {
 
-            final Offset offset = Offset.milliseconds(
+            final GpxTimeOffset offset = GpxTimeOffset.milliseconds(
                     delta.getMilliseconds() + Math.round(timezone.getHours() * TimeUnit.HOURS.toMillis(1)));
             final int dayOffset = offset.getDayOffset();
-            final Pair<Timezone, Offset> timezoneOffsetPair = offset.withoutDayOffset().splitOutTimezone();
+            final Pair<GpxTimezone, GpxTimeOffset> timezoneOffsetPair = offset.withoutDayOffset().splitOutTimezone();
 
             // Info Labels
@@ -854,5 +1102,5 @@
             // CHECKSTYLE.OFF: ParenPad
             for (int i = -12; i <= 12; i += 6) {
-                labelTable.put(i * 2, new JLabel(new Timezone(i).formatTimezone()));
+                labelTable.put(i * 2, new JLabel(new GpxTimezone(i).formatTimezone()));
             }
             // CHECKSTYLE.ON: ParenPad
@@ -872,5 +1120,5 @@
             // CHECKSTYLE.OFF: ParenPad
             for (int i = -60; i <= 60; i += 30) {
-                labelTable.put(i * 10, new JLabel(Offset.seconds(i).formatOffset()));
+                labelTable.put(i * 10, new JLabel(GpxTimeOffset.seconds(i).formatOffset()));
             }
             // CHECKSTYLE.ON: ParenPad
@@ -883,11 +1131,11 @@
                 @Override
                 public void stateChanged(ChangeEvent e) {
-                    timezone = new Timezone(sldTimezone.getValue() / 2.);
+                    timezone = new GpxTimezone(sldTimezone.getValue() / 2.);
 
                     lblTimezone.setText(tr("Timezone: {0}", timezone.formatTimezone()));
                     lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue()));
-                    lblSeconds.setText(tr("Seconds: {0}", Offset.milliseconds(100L * sldSeconds.getValue()).formatOffset()));
-
-                    delta = Offset.milliseconds(100L * sldSeconds.getValue()
+                    lblSeconds.setText(tr("Seconds: {0}", GpxTimeOffset.milliseconds(100L * sldSeconds.getValue()).formatOffset()));
+
+                    delta = GpxTimeOffset.milliseconds(100L * sldSeconds.getValue()
                             + TimeUnit.MINUTES.toMillis(sldMinutes.getValue())
                             + TimeUnit.DAYS.toMillis(dayOffset));
@@ -968,5 +1216,5 @@
      * @throws NoGpxTimestamps when the gpx track does not contain a timestamp
      */
-    static Pair<Timezone, Offset> autoGuess(List<ImageEntry> imgs, GpxData gpx) throws NoGpxTimestamps {
+    static Pair<GpxTimezone, GpxTimeOffset> autoGuess(List<ImageEntry> imgs, GpxData gpx) throws NoGpxTimestamps {
 
         // Init variables
@@ -991,5 +1239,5 @@
         }
 
-        return Offset.milliseconds(firstExifDate - firstGPXDate).splitOutTimezone();
+        return GpxTimeOffset.milliseconds(firstExifDate - firstGPXDate).splitOutTimezone();
     }
 
@@ -1006,5 +1254,5 @@
 
             try {
-                final Pair<Timezone, Offset> r = autoGuess(imgs, gpx);
+                final Pair<GpxTimezone, GpxTimeOffset> r = autoGuess(imgs, gpx);
                 timezone = r.a;
                 delta = r.b;
@@ -1088,160 +1336,3 @@
     }
 
-    /**
-     * Match a list of photos to a gpx track with a given offset.
-     * All images need a exifTime attribute and the List must be sorted according to these times.
-     * @param images images to match
-     * @param selectedGpx selected GPX data
-     * @param offset offset
-     * @return number of matched points
-     */
-    static int matchGpxTrack(List<ImageEntry> images, GpxData selectedGpx, long offset) {
-        int ret = 0;
-
-        for (GpxTrack trk : selectedGpx.tracks) {
-            for (GpxTrackSegment segment : trk.getSegments()) {
-
-                long prevWpTime = 0;
-                WayPoint prevWp = null;
-
-                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);
-
-                        prevWp = curWp;
-                        prevWpTime = curWpTime;
-                        continue;
-                    }
-                    prevWp = null;
-                    prevWpTime = 0;
-                }
-            }
-        }
-        return ret;
-    }
-
-    private static Double getElevation(WayPoint wp) {
-        String value = wp.getString(GpxConstants.PT_ELE);
-        if (value != null && !value.isEmpty()) {
-            try {
-                return Double.valueOf(value);
-            } catch (NumberFormatException e) {
-                Logging.warn(e);
-            }
-        }
-        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);
-        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);
-
-        // no photos match
-        if (i < 0)
-            return 0;
-
-        Double speed = null;
-        Double prevElevation = null;
-
-        if (prevWp != null) {
-            double distance = prevWp.getCoor().greatCircleDistance(curWp.getCoor());
-            // This is in km/h, 3.6 * m/s
-            if (curWpTime > prevWpTime) {
-                speed = 3600 * distance / (curWpTime - prevWpTime);
-            }
-            prevElevation = getElevation(prevWp);
-        }
-
-        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) {
-            while (i >= 0) {
-                final ImageEntry curImg = images.get(i);
-                long time = curImg.getExifTime().getTime();
-                if (time > curWpTime || time < curWpTime - interval) {
-                    break;
-                }
-                if (curImg.tmp.getPos() == null) {
-                    curImg.tmp.setPos(curWp.getCoor());
-                    curImg.tmp.setSpeed(speed);
-                    curImg.tmp.setElevation(curElevation);
-                    curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
-                    curImg.tmp.flagNewGpsData();
-                    ret++;
-                }
-                i--;
-            }
-            return ret;
-        }
-
-        // 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);
-                }
-                curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
-                curImg.tmp.flagNewGpsData();
-
-                ret++;
-            }
-            i--;
-        }
-        return ret;
-    }
-
-    private static int getLastIndexOfListBefore(List<ImageEntry> images, long searchedTime) {
-        int lstSize = images.size();
-
-        // No photos or the first photo taken is later than the search period
-        if (lstSize == 0 || searchedTime < images.get(0).getExifTime().getTime())
-            return -1;
-
-        // The search period is later than the last photo
-        if (searchedTime > images.get(lstSize - 1).getExifTime().getTime())
-            return lstSize-1;
-
-        // The searched index is somewhere in the middle, do a binary search from the beginning
-        int curIndex;
-        int startIndex = 0;
-        int endIndex = lstSize-1;
-        while (endIndex - startIndex > 1) {
-            curIndex = (endIndex + startIndex) / 2;
-            if (searchedTime > images.get(curIndex).getExifTime().getTime()) {
-                startIndex = curIndex;
-            } else {
-                endIndex = curIndex;
-            }
-        }
-        if (searchedTime < images.get(endIndex).getExifTime().getTime())
-            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()
-                == images.get(endIndex + 1).getExifTime().getTime())) {
-            endIndex++;
-        }
-        return endIndex;
-    }
 }
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java	(revision 14204)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java	(revision 14205)
@@ -4,71 +4,21 @@
 import java.awt.Image;
 import java.io.File;
-import java.io.IOException;
 import java.util.Collections;
-import java.util.Date;
 
-import org.openstreetmap.josm.data.coor.CachedLatLon;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.tools.ExifReader;
-import org.openstreetmap.josm.tools.JosmRuntimeException;
-import org.openstreetmap.josm.tools.Logging;
-
-import com.drew.imaging.jpeg.JpegMetadataReader;
-import com.drew.lang.CompoundException;
-import com.drew.metadata.Directory;
-import com.drew.metadata.Metadata;
-import com.drew.metadata.MetadataException;
-import com.drew.metadata.exif.ExifIFD0Directory;
-import com.drew.metadata.exif.GpsDirectory;
-import com.drew.metadata.jpeg.JpegDirectory;
+import org.openstreetmap.josm.data.gpx.GpxImageEntry;
 
 /**
- * Stores info about each image
+ * Stores info about each image, with an optional thumbnail
+ * @since 2662
  */
-public final class ImageEntry implements Comparable<ImageEntry>, Cloneable {
-    private File file;
-    private Integer exifOrientation;
-    private LatLon exifCoor;
-    private Double exifImgDir;
-    private Date exifTime;
-    /**
-     * Flag isNewGpsData indicates that the GPS data of the image is new or has changed.
-     * GPS data includes the position, speed, elevation, time (e.g. as extracted from the GPS track).
-     * The flag can used to decide for which image file the EXIF GPS data is (re-)written.
-     */
-    private boolean isNewGpsData;
-    /** Temporary source of GPS time if not correlated with GPX track. */
-    private Date exifGpsTime;
+public final class ImageEntry extends GpxImageEntry {
+
     private Image thumbnail;
-
-    /**
-     * The following values are computed from the correlation with the gpx track
-     * or extracted from the image EXIF data.
-     */
-    private CachedLatLon pos;
-    /** Speed in kilometer per hour */
-    private Double speed;
-    /** Elevation (altitude) in meters */
-    private Double elevation;
-    /** The time after correlation with a gpx track */
-    private Date gpsTime;
-
-    private int width;
-    private int height;
-
-    /**
-     * When the correlation dialog is open, we like to show the image position
-     * for the current time offset on the map in real time.
-     * On the other hand, when the user aborts this operation, the old values
-     * should be restored. We have a temporary copy, that overrides
-     * the normal values if it is not null. (This may be not the most elegant
-     * solution for this, but it works.)
-     */
-    ImageEntry tmp;
 
     /**
      * Constructs a new {@code ImageEntry}.
      */
-    public ImageEntry() {}
+    public ImageEntry() {
+    }
 
     /**
@@ -77,143 +27,5 @@
      */
     public ImageEntry(File file) {
-        setFile(file);
-    }
-
-    /**
-     * Returns width of the image this ImageEntry represents.
-     * @return width of the image this ImageEntry represents
-     * @since 13220
-     */
-    public int getWidth() {
-        return width;
-    }
-
-    /**
-     * Returns height of the image this ImageEntry represents.
-     * @return height of the image this ImageEntry represents
-     * @since 13220
-     */
-    public int getHeight() {
-        return height;
-    }
-
-    /**
-     * Returns the position value. The position value from the temporary copy
-     * is returned if that copy exists.
-     * @return the position value
-     */
-    public CachedLatLon getPos() {
-        if (tmp != null)
-            return tmp.pos;
-        return pos;
-    }
-
-    /**
-     * Returns the speed value. The speed value from the temporary copy is
-     * returned if that copy exists.
-     * @return the speed value
-     */
-    public Double getSpeed() {
-        if (tmp != null)
-            return tmp.speed;
-        return speed;
-    }
-
-    /**
-     * Returns the elevation value. The elevation value from the temporary
-     * copy is returned if that copy exists.
-     * @return the elevation value
-     */
-    public Double getElevation() {
-        if (tmp != null)
-            return tmp.elevation;
-        return elevation;
-    }
-
-    /**
-     * Returns the GPS time value. The GPS time value from the temporary copy
-     * is returned if that copy exists.
-     * @return the GPS time value
-     */
-    public Date getGpsTime() {
-        if (tmp != null)
-            return getDefensiveDate(tmp.gpsTime);
-        return getDefensiveDate(gpsTime);
-    }
-
-    /**
-     * Convenient way to determine if this entry has a GPS time, without the cost of building a defensive copy.
-     * @return {@code true} if this entry has a GPS time
-     * @since 6450
-     */
-    public boolean hasGpsTime() {
-        return (tmp != null && tmp.gpsTime != null) || gpsTime != null;
-    }
-
-    /**
-     * Returns associated file.
-     * @return associated file
-     */
-    public File getFile() {
-        return file;
-    }
-
-    /**
-     * Returns EXIF orientation
-     * @return EXIF orientation
-     */
-    public Integer getExifOrientation() {
-        return exifOrientation != null ? exifOrientation : 1;
-    }
-
-    /**
-     * Returns EXIF time
-     * @return EXIF time
-     */
-    public Date getExifTime() {
-        return getDefensiveDate(exifTime);
-    }
-
-    /**
-     * Convenient way to determine if this entry has a EXIF time, without the cost of building a defensive copy.
-     * @return {@code true} if this entry has a EXIF time
-     * @since 6450
-     */
-    public boolean hasExifTime() {
-        return exifTime != null;
-    }
-
-    /**
-     * Returns the EXIF GPS time.
-     * @return the EXIF GPS time
-     * @since 6392
-     */
-    public Date getExifGpsTime() {
-        return getDefensiveDate(exifGpsTime);
-    }
-
-    /**
-     * Convenient way to determine if this entry has a EXIF GPS time, without the cost of building a defensive copy.
-     * @return {@code true} if this entry has a EXIF GPS time
-     * @since 6450
-     */
-    public boolean hasExifGpsTime() {
-        return exifGpsTime != null;
-    }
-
-    private static Date getDefensiveDate(Date date) {
-        if (date == null)
-            return null;
-        return new Date(date.getTime());
-    }
-
-    public LatLon getExifCoor() {
-        return exifCoor;
-    }
-
-    public Double getExifImgDir() {
-        if (tmp != null)
-            return tmp.exifImgDir;
-        return exifImgDir;
+        super(file);
     }
 
@@ -251,317 +63,3 @@
         }
     }
-
-    /**
-     * Sets the width of this ImageEntry.
-     * @param width set the width of this ImageEntry
-     * @since 13220
-     */
-    public void setWidth(int width) {
-        this.width = width;
-    }
-
-    /**
-     * Sets the height of this ImageEntry.
-     * @param height set the height of this ImageEntry
-     * @since 13220
-     */
-    public void setHeight(int height) {
-        this.height = height;
-    }
-
-    /**
-     * Sets the position.
-     * @param pos cached position
-     */
-    public void setPos(CachedLatLon pos) {
-        this.pos = pos;
-    }
-
-    /**
-     * Sets the position.
-     * @param pos position (will be cached)
-     */
-    public void setPos(LatLon pos) {
-        setPos(pos != null ? new CachedLatLon(pos) : null);
-    }
-
-    /**
-     * Sets the speed.
-     * @param speed speed
-     */
-    public void setSpeed(Double speed) {
-        this.speed = speed;
-    }
-
-    /**
-     * Sets the elevation.
-     * @param elevation elevation
-     */
-    public void setElevation(Double elevation) {
-        this.elevation = elevation;
-    }
-
-    /**
-     * Sets associated file.
-     * @param file associated file
-     */
-    public void setFile(File file) {
-        this.file = file;
-    }
-
-    /**
-     * Sets EXIF orientation.
-     * @param exifOrientation EXIF orientation
-     */
-    public void setExifOrientation(Integer exifOrientation) {
-        this.exifOrientation = exifOrientation;
-    }
-
-    /**
-     * Sets EXIF time.
-     * @param exifTime EXIF time
-     */
-    public void setExifTime(Date exifTime) {
-        this.exifTime = getDefensiveDate(exifTime);
-    }
-
-    /**
-     * Sets the EXIF GPS time.
-     * @param exifGpsTime the EXIF GPS time
-     * @since 6392
-     */
-    public void setExifGpsTime(Date exifGpsTime) {
-        this.exifGpsTime = getDefensiveDate(exifGpsTime);
-    }
-
-    public void setGpsTime(Date gpsTime) {
-        this.gpsTime = getDefensiveDate(gpsTime);
-    }
-
-    public void setExifCoor(LatLon exifCoor) {
-        this.exifCoor = exifCoor;
-    }
-
-    public void setExifImgDir(Double exifDir) {
-        this.exifImgDir = exifDir;
-    }
-
-    @Override
-    public ImageEntry clone() {
-        try {
-            return (ImageEntry) super.clone();
-        } catch (CloneNotSupportedException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public int compareTo(ImageEntry image) {
-        if (exifTime != null && image.exifTime != null)
-            return exifTime.compareTo(image.exifTime);
-        else if (exifTime == null && image.exifTime == null)
-            return 0;
-        else if (exifTime == null)
-            return -1;
-        else
-            return 1;
-    }
-
-    /**
-     * Make a fresh copy and save it in the temporary variable. Use
-     * {@link #applyTmp()} or {@link #discardTmp()} if the temporary variable
-     * is not needed anymore.
-     */
-    public void createTmp() {
-        tmp = clone();
-        tmp.tmp = null;
-    }
-
-    /**
-     * Get temporary variable that is used for real time parameter
-     * adjustments. The temporary variable is created if it does not exist
-     * yet. Use {@link #applyTmp()} or {@link #discardTmp()} if the temporary
-     * variable is not needed anymore.
-     * @return temporary variable
-     */
-    public ImageEntry getTmp() {
-        if (tmp == null) {
-            createTmp();
-        }
-        return tmp;
-    }
-
-    /**
-     * Copy the values from the temporary variable to the main instance. The
-     * temporary variable is deleted.
-     * @see #discardTmp()
-     */
-    public void applyTmp() {
-        if (tmp != null) {
-            pos = tmp.pos;
-            speed = tmp.speed;
-            elevation = tmp.elevation;
-            gpsTime = tmp.gpsTime;
-            exifImgDir = tmp.exifImgDir;
-            isNewGpsData = tmp.isNewGpsData;
-            tmp = null;
-        }
-    }
-
-    /**
-     * Delete the temporary variable. Temporary modifications are lost.
-     * @see #applyTmp()
-     */
-    public void discardTmp() {
-        tmp = null;
-    }
-
-    /**
-     * If it has been tagged i.e. matched to a gpx track or retrieved lat/lon from exif
-     * @return {@code true} if it has been tagged
-     */
-    public boolean isTagged() {
-        return pos != null;
-    }
-
-    /**
-     * String representation. (only partial info)
-     */
-    @Override
-    public String toString() {
-        return file.getName()+": "+
-        "pos = "+pos+" | "+
-        "exifCoor = "+exifCoor+" | "+
-        (tmp == null ? " tmp==null" :
-            " [tmp] pos = "+tmp.pos);
-    }
-
-    /**
-     * Indicates that the image has new GPS data.
-     * That flag is set by new GPS data providers.  It is used e.g. by the photo_geotagging plugin
-     * to decide for which image file the EXIF GPS data needs to be (re-)written.
-     * @since 6392
-     */
-    public void flagNewGpsData() {
-        isNewGpsData = true;
-   }
-
-    /**
-     * Remove the flag that indicates new GPS data.
-     * The flag is cleared by a new GPS data consumer.
-     */
-    public void unflagNewGpsData() {
-        isNewGpsData = false;
-    }
-
-    /**
-     * Queries whether the GPS data changed. The flag value from the temporary
-     * copy is returned if that copy exists.
-     * @return {@code true} if GPS data changed, {@code false} otherwise
-     * @since 6392
-     */
-    public boolean hasNewGpsData() {
-        if (tmp != null)
-            return tmp.isNewGpsData;
-        return isNewGpsData;
-    }
-
-    /**
-     * Extract GPS metadata from image EXIF. Has no effect if the image file is not set
-     *
-     * If successful, fills in the LatLon, speed, elevation, image direction, and other attributes
-     * @since 9270
-     */
-    public void extractExif() {
-
-        Metadata metadata;
-
-        if (file == null) {
-            return;
-        }
-
-        try {
-            metadata = JpegMetadataReader.readMetadata(file);
-        } catch (CompoundException | IOException ex) {
-            Logging.error(ex);
-            setExifTime(null);
-            setExifCoor(null);
-            setPos(null);
-            return;
-        }
-
-        // Changed to silently cope with no time info in exif. One case
-        // of person having time that couldn't be parsed, but valid GPS info
-        try {
-            setExifTime(ExifReader.readTime(metadata));
-        } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException ex) {
-            Logging.warn(ex);
-            setExifTime(null);
-        }
-
-        final Directory dir = metadata.getFirstDirectoryOfType(JpegDirectory.class);
-        final Directory dirExif = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
-        final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
-
-        try {
-            if (dirExif != null) {
-                int orientation = dirExif.getInt(ExifIFD0Directory.TAG_ORIENTATION);
-                setExifOrientation(orientation);
-            }
-        } catch (MetadataException ex) {
-            Logging.debug(ex);
-        }
-
-        try {
-            if (dir != null) {
-                // there are cases where these do not match width and height stored in dirExif
-                int width = dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH);
-                int height = dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT);
-                setWidth(width);
-                setHeight(height);
-            }
-        } catch (MetadataException ex) {
-            Logging.debug(ex);
-        }
-
-        if (dirGps == null) {
-            setExifCoor(null);
-            setPos(null);
-            return;
-        }
-
-        final Double speed = ExifReader.readSpeed(dirGps);
-        if (speed != null) {
-            setSpeed(speed);
-        }
-
-        final Double ele = ExifReader.readElevation(dirGps);
-        if (ele != null) {
-            setElevation(ele);
-        }
-
-        try {
-            final LatLon latlon = ExifReader.readLatLon(dirGps);
-            setExifCoor(latlon);
-            setPos(getExifCoor());
-        } catch (MetadataException | IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
-            Logging.error("Error reading EXIF from file: " + ex);
-            setExifCoor(null);
-            setPos(null);
-        }
-
-        try {
-            final Double direction = ExifReader.readDirection(dirGps);
-            if (direction != null) {
-                setExifImgDir(direction);
-            }
-        } catch (IndexOutOfBoundsException ex) { // (other exceptions, e.g. #5271)
-            Logging.debug(ex);
-        }
-
-        final Date gpsDate = dirGps.getGpsDate();
-        if (gpsDate != null) {
-            setExifGpsTime(gpsDate);
-        }
-    }
 }
Index: unk/src/org/openstreetmap/josm/gui/layer/geoimage/Offset.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/Offset.java	(revision 14204)
+++ 	(revision )
@@ -1,102 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.layer.geoimage;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.text.ParseException;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-import org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider;
-import org.openstreetmap.josm.tools.Pair;
-
-/**
- * Time offset of GPX correlation.
- * @since 11914 (extracted from {@link CorrelateGpxWithImages})
- */
-public final class Offset {
-
-    static final Offset ZERO = new Offset(0);
-    private final long milliseconds;
-
-    private Offset(long milliseconds) {
-        this.milliseconds = milliseconds;
-    }
-
-    static Offset milliseconds(long milliseconds) {
-        return new Offset(milliseconds);
-    }
-
-    static Offset seconds(long seconds) {
-        return new Offset(1000 * seconds);
-    }
-
-    long getMilliseconds() {
-        return milliseconds;
-    }
-
-    long getSeconds() {
-        return milliseconds / 1000;
-    }
-
-    String formatOffset() {
-        if (milliseconds % 1000 == 0) {
-            return Long.toString(milliseconds / 1000);
-        } else if (milliseconds % 100 == 0) {
-            return String.format(Locale.ENGLISH, "%.1f", milliseconds / 1000.);
-        } else {
-            return String.format(Locale.ENGLISH, "%.3f", milliseconds / 1000.);
-        }
-    }
-
-    static Offset parseOffset(String offset) throws ParseException {
-        String error = tr("Error while parsing offset.\nExpected format: {0}", "number");
-
-        if (!offset.isEmpty()) {
-            try {
-                if (offset.startsWith("+")) {
-                    offset = offset.substring(1);
-                }
-                return Offset.milliseconds(Math.round(JosmDecimalFormatSymbolsProvider.parseDouble(offset) * 1000));
-            } catch (NumberFormatException nfe) {
-                throw (ParseException) new ParseException(error, 0).initCause(nfe);
-            }
-        } else {
-            return Offset.ZERO;
-        }
-    }
-
-    int getDayOffset() {
-        // Find day difference
-        return (int) Math.round(((double) getMilliseconds()) / TimeUnit.DAYS.toMillis(1));
-    }
-
-    Offset withoutDayOffset() {
-        return milliseconds(getMilliseconds() - TimeUnit.DAYS.toMillis(getDayOffset()));
-    }
-
-    Pair<Timezone, Offset> splitOutTimezone() {
-        // In hours
-        final double tz = ((double) withoutDayOffset().getSeconds()) / TimeUnit.HOURS.toSeconds(1);
-
-        // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with
-        // -2 minutes offset. This determines the real timezone and finds offset.
-        final double timezone = (double) Math.round(tz * 2) / 2; // hours, rounded to one decimal place
-        final long delta = Math.round(getMilliseconds() - timezone * TimeUnit.HOURS.toMillis(1));
-        return Pair.create(new Timezone(timezone), Offset.milliseconds(delta));
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof Offset)) return false;
-        Offset offset = (Offset) o;
-        return milliseconds == offset.milliseconds;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(milliseconds);
-    }
-}
Index: unk/src/org/openstreetmap/josm/gui/layer/geoimage/Timezone.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/Timezone.java	(revision 14204)
+++ 	(revision )
@@ -1,142 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.layer.geoimage;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.text.ParseException;
-import java.util.Objects;
-
-/**
- * Timezone in hours.<p>
- * TODO: should probably be replaced by {@link java.util.TimeZone}.
- * @since 11914 (extracted from {@link CorrelateGpxWithImages})
- */
-public final class Timezone {
-
-    static final Timezone ZERO = new Timezone(0.0);
-    private final double timezone;
-
-    Timezone(double hours) {
-        this.timezone = hours;
-    }
-
-    /**
-     * Returns the timezone in hours.
-     * @return the timezone in hours
-     */
-    public double getHours() {
-        return timezone;
-    }
-
-    String formatTimezone() {
-        StringBuilder ret = new StringBuilder();
-
-        double timezone = this.timezone;
-        if (timezone < 0) {
-            ret.append('-');
-            timezone = -timezone;
-        } else {
-            ret.append('+');
-        }
-        ret.append((long) timezone).append(':');
-        int minutes = (int) ((timezone % 1) * 60);
-        if (minutes < 10) {
-            ret.append('0');
-        }
-        ret.append(minutes);
-
-        return ret.toString();
-    }
-
-    static Timezone parseTimezone(String timezone) throws ParseException {
-
-        if (timezone.isEmpty())
-            return ZERO;
-
-        String error = tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM");
-
-        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;
-        try {
-            h = Integer.parseInt(hTimezone.toString());
-            if (mTimezone.length() > 0) {
-                m = Integer.parseInt(mTimezone.toString());
-            }
-        } catch (NumberFormatException nfe) {
-            // Invalid timezone
-            throw (ParseException) new ParseException(error, 0).initCause(nfe);
-        }
-
-        if (h > 12 || m > 59)
-            throw new ParseException(error, 0);
-        else
-            return new Timezone((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1));
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof Timezone)) return false;
-        Timezone timezone1 = (Timezone) o;
-        return Double.compare(timezone1.timezone, timezone) == 0;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(timezone);
-    }
-}
Index: /trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java	(revision 14204)
+++ /trunk/src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java	(revision 14205)
@@ -11,4 +11,5 @@
 
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.gpx.GpxImageEntry;
 import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.gui.layer.Layer;
@@ -71,5 +72,5 @@
     }
 
-    private static void handleElement(ImageEntry entry, Element attrElem) {
+    private static void handleElement(GpxImageEntry entry, Element attrElem) {
         try {
             switch(attrElem.getTagName()) {
Index: /trunk/test/data/ImageCorrelationTest.gpx
===================================================================
--- /trunk/test/data/ImageCorrelationTest.gpx	(revision 14205)
+++ /trunk/test/data/ImageCorrelationTest.gpx	(revision 14205)
@@ -0,0 +1,1354 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<gpx version="1.1" creator="JOSM GPX export" xmlns="http://www.topografix.com/GPX/1/1"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
+  <metadata>
+    <bounds minlat="47.1849004" minlon="8.7719297" maxlat="47.2014997" maxlon="8.7984188"/>
+  </metadata>
+  <trk>
+    <trkseg>
+      <trkpt lat="47.19286847859621" lon="8.79732714034617">
+        <ele>471.86000000000001</ele>
+        <time>2016-01-03T11:59:58.000Z</time>
+      </trkpt>
+      <trkpt lat="47.192921955138445" lon="8.797342479228973">
+        <ele>471.43000000000001</ele>
+        <time>2016-01-03T11:59:59.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19297501258552" lon="8.79734423942864">
+        <ele>470.69</ele>
+        <time>2016-01-03T12:00:00.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19302739948034" lon="8.797332588583231">
+        <ele>470.94</ele>
+        <time>2016-01-03T12:00:01.000Z</time>
+      </trkpt>
+      <trkpt lat="47.193073416128755" lon="8.797307442873716">
+        <ele>471.5</ele>
+        <time>2016-01-03T12:00:02.000Z</time>
+      </trkpt>
+      <trkpt lat="47.193113481625915" lon="8.797272993251681">
+        <ele>471.67000000000002</ele>
+        <time>2016-01-03T12:00:03.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19313560985029" lon="8.797232424840331">
+        <ele>471.58999999999997</ele>
+        <time>2016-01-03T12:00:04.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19333660788834" lon="8.796738646924496">
+        <ele>472.44999999999999</ele>
+        <time>2016-01-03T12:00:15.000Z</time>
+      </trkpt>
+      <trkpt lat="47.193504832684994" lon="8.796404711902142">
+        <ele>472.54000000000002</ele>
+        <time>2016-01-03T12:00:22.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19353936612606" lon="8.79635970108211">
+        <ele>472.45999999999998</ele>
+        <time>2016-01-03T12:00:23.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19381898641586" lon="8.796017384156585">
+        <ele>471.38</ele>
+        <time>2016-01-03T12:00:30.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19386676326394" lon="8.795985281467438">
+        <ele>471.26999999999998</ele>
+        <time>2016-01-03T12:00:31.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19391797669232" lon="8.795968098565936">
+        <ele>471.36000000000001</ele>
+        <time>2016-01-03T12:00:32.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19474418088794" lon="8.795836586505175">
+        <ele>473.19999999999999</ele>
+        <time>2016-01-03T12:00:50.000Z</time>
+      </trkpt>
+      <trkpt lat="47.195586981251836" lon="8.795750420540571">
+        <ele>475.06</ele>
+        <time>2016-01-03T12:01:09.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19563358463347" lon="8.795769028365612">
+        <ele>474.75</ele>
+        <time>2016-01-03T12:01:10.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19567717052996" lon="8.795795682817698">
+        <ele>474.62</ele>
+        <time>2016-01-03T12:01:11.000Z</time>
+      </trkpt>
+      <trkpt lat="47.195715978741646" lon="8.79583046771586">
+        <ele>474.74000000000001</ele>
+        <time>2016-01-03T12:01:12.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19575093127787" lon="8.79587598145008">
+        <ele>474.06</ele>
+        <time>2016-01-03T12:01:13.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19578169286251" lon="8.795927111059427">
+        <ele>474</ele>
+        <time>2016-01-03T12:01:14.000Z</time>
+      </trkpt>
+      <trkpt lat="47.195808347314596" lon="8.795980839058757">
+        <ele>474.58999999999997</ele>
+        <time>2016-01-03T12:01:15.000Z</time>
+      </trkpt>
+      <trkpt lat="47.195833241567016" lon="8.796036075800657">
+        <ele>474.81999999999999</ele>
+        <time>2016-01-03T12:01:16.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19628603197634" lon="8.797110635787249">
+        <ele>476.29000000000002</ele>
+        <time>2016-01-03T12:01:35.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19658995978534" lon="8.797826115041971">
+        <ele>478.48000000000002</ele>
+        <time>2016-01-03T12:01:50.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19703386537731" lon="8.798356018960476">
+        <ele>481.02999999999997</ele>
+        <time>2016-01-03T12:02:06.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19723687507212" lon="8.79841879941523">
+        <ele>480</ele>
+        <time>2016-01-03T12:02:12.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19727886840701" lon="8.79838845692575">
+        <ele>479.58999999999997</ele>
+        <time>2016-01-03T12:02:13.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19731541350484" lon="8.798339758068323">
+        <ele>478.99000000000001</ele>
+        <time>2016-01-03T12:02:14.000Z</time>
+      </trkpt>
+      <trkpt lat="47.197333350777626" lon="8.798282761126757">
+        <ele>479.58999999999997</ele>
+        <time>2016-01-03T12:02:15.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19734919257462" lon="8.798161642625928">
+        <ele>481.18000000000001</ele>
+        <time>2016-01-03T12:02:18.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19734827056527" lon="8.798118894919753">
+        <ele>481.88</ele>
+        <time>2016-01-03T12:02:20.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19730585813522" lon="8.79802887327969">
+        <ele>483.11000000000001</ele>
+        <time>2016-01-03T12:02:24.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19727643765509" lon="8.79797455854714">
+        <ele>484.49000000000001</ele>
+        <time>2016-01-03T12:02:27.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19720158725977" lon="8.797768447548151">
+        <ele>486.88</ele>
+        <time>2016-01-03T12:02:35.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19708633609116" lon="8.79746401682496">
+        <ele>488.05000000000001</ele>
+        <time>2016-01-03T12:02:48.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19707007519901" lon="8.79741758108139">
+        <ele>487.76999999999998</ele>
+        <time>2016-01-03T12:02:51.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19702917151153" lon="8.797250529751182">
+        <ele>488.95999999999998</ele>
+        <time>2016-01-03T12:02:57.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19699899666011" lon="8.797172158956528">
+        <ele>488.36000000000001</ele>
+        <time>2016-01-03T12:03:01.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19691224396229" lon="8.796978034079075">
+        <ele>488.35000000000002</ele>
+        <time>2016-01-03T12:03:09.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19690545462072" lon="8.796931263059378">
+        <ele>487.19</ele>
+        <time>2016-01-03T12:03:10.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19690595753491" lon="8.796886671334505">
+        <ele>487.63999999999999</ele>
+        <time>2016-01-03T12:03:11.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19690511934459" lon="8.796837385743856">
+        <ele>487.55000000000001</ele>
+        <time>2016-01-03T12:03:12.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19690562225878" lon="8.796793716028333">
+        <ele>488.39999999999998</ele>
+        <time>2016-01-03T12:03:13.000Z</time>
+      </trkpt>
+      <trkpt lat="47.196887601166964" lon="8.796751135960221">
+        <ele>489.18000000000001</ele>
+        <time>2016-01-03T12:03:15.000Z</time>
+      </trkpt>
+      <trkpt lat="47.1968892775476" lon="8.796689361333847">
+        <ele>490.73000000000002</ele>
+        <time>2016-01-03T12:03:18.000Z</time>
+      </trkpt>
+      <trkpt lat="47.196868155151606" lon="8.79647453315556">
+        <ele>491.24000000000001</ele>
+        <time>2016-01-03T12:03:33.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19686932861805" lon="8.796442430466413">
+        <ele>490.25</ele>
+        <time>2016-01-03T12:03:34.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19686849042773" lon="8.796406639739871">
+        <ele>490.00999999999999</ele>
+        <time>2016-01-03T12:03:35.000Z</time>
+      </trkpt>
+      <trkpt lat="47.196873016655445" lon="8.796368166804314">
+        <ele>489.54000000000002</ele>
+        <time>2016-01-03T12:03:36.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19687905162573" lon="8.796329610049725">
+        <ele>489.66000000000003</ele>
+        <time>2016-01-03T12:03:37.000Z</time>
+      </trkpt>
+      <trkpt lat="47.196886762976646" lon="8.796280743554235">
+        <ele>489.06</ele>
+        <time>2016-01-03T12:03:38.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19695775769651" lon="8.795829126611352">
+        <ele>490.08999999999997</ele>
+        <time>2016-01-03T12:03:48.000Z</time>
+      </trkpt>
+      <trkpt lat="47.1969928778708" lon="8.795709516853094">
+        <ele>489.41000000000003</ele>
+        <time>2016-01-03T12:03:52.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19698550179601" lon="8.795667607337236">
+        <ele>489.17000000000002</ele>
+        <time>2016-01-03T12:03:53.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19697644934058" lon="8.795624608173966">
+        <ele>488.87</ele>
+        <time>2016-01-03T12:03:54.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19696890562773" lon="8.795590326189995">
+        <ele>489.37</ele>
+        <time>2016-01-03T12:03:55.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19696664251387" lon="8.795558391138911">
+        <ele>488.94999999999999</ele>
+        <time>2016-01-03T12:03:56.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19695256091654" lon="8.795536765828729">
+        <ele>488.93000000000001</ele>
+        <time>2016-01-03T12:03:57.000Z</time>
+      </trkpt>
+      <trkpt lat="47.196956500411034" lon="8.795507680624723">
+        <ele>488.88999999999999</ele>
+        <time>2016-01-03T12:03:58.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19695859588683" lon="8.79548110999167">
+        <ele>488.86000000000001</ele>
+        <time>2016-01-03T12:03:59.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19696907326579" lon="8.795452527701855">
+        <ele>489.19999999999999</ele>
+        <time>2016-01-03T12:04:00.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19697988592088" lon="8.79541271366179">
+        <ele>489.29000000000002</ele>
+        <time>2016-01-03T12:04:01.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19698726199567" lon="8.795355297625065">
+        <ele>488.95999999999998</ele>
+        <time>2016-01-03T12:04:02.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19698960892856" lon="8.795302575454116">
+        <ele>489.10000000000002</ele>
+        <time>2016-01-03T12:04:03.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19690981321037" lon="8.79494265653193">
+        <ele>492.19</ele>
+        <time>2016-01-03T12:04:14.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19696622341871" lon="8.794532027095556">
+        <ele>492.12</ele>
+        <time>2016-01-03T12:04:25.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19697737134993" lon="8.79450285807252">
+        <ele>493.06999999999999</ele>
+        <time>2016-01-03T12:04:26.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19699212349951" lon="8.794450974091887">
+        <ele>492.58999999999997</ele>
+        <time>2016-01-03T12:04:27.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19699405133724" lon="8.794399006292224">
+        <ele>492.38999999999999</ele>
+        <time>2016-01-03T12:04:28.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19698860310018" lon="8.794350139796734">
+        <ele>492.94</ele>
+        <time>2016-01-03T12:04:29.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19698214903474" lon="8.794298926368356">
+        <ele>492.47000000000003</ele>
+        <time>2016-01-03T12:04:30.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19697737134993" lon="8.794246790930629">
+        <ele>492.50999999999999</ele>
+        <time>2016-01-03T12:04:31.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19698206521571" lon="8.794189291074872">
+        <ele>492.73000000000002</ele>
+        <time>2016-01-03T12:04:32.000Z</time>
+      </trkpt>
+      <trkpt lat="47.196985417976975" lon="8.794133719056845">
+        <ele>492.63</ele>
+        <time>2016-01-03T12:04:33.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19698768109083" lon="8.794077644124627">
+        <ele>493</ele>
+        <time>2016-01-03T12:04:34.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19699136912823" lon="8.794022323563695">
+        <ele>493.00999999999999</ele>
+        <time>2016-01-03T12:04:35.000Z</time>
+      </trkpt>
+      <trkpt lat="47.196987848728895" lon="8.793976558372378">
+        <ele>493.19999999999999</ele>
+        <time>2016-01-03T12:04:36.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19698550179601" lon="8.793926937505603">
+        <ele>493.18000000000001</ele>
+        <time>2016-01-03T12:04:37.000Z</time>
+      </trkpt>
+      <trkpt lat="47.196980556473136" lon="8.793885698541999">
+        <ele>493</ele>
+        <time>2016-01-03T12:04:38.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19698307104409" lon="8.793837334960699">
+        <ele>493.20999999999998</ele>
+        <time>2016-01-03T12:04:39.000Z</time>
+      </trkpt>
+      <trkpt lat="47.196992291137576" lon="8.793786959722638">
+        <ele>493.11000000000001</ele>
+        <time>2016-01-03T12:04:40.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19699547626078" lon="8.793743625283241">
+        <ele>493.30000000000001</ele>
+        <time>2016-01-03T12:04:41.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19700268469751" lon="8.793694507330656">
+        <ele>493.50999999999999</ele>
+        <time>2016-01-03T12:04:42.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19700511544943" lon="8.793357806280255">
+        <ele>493</ele>
+        <time>2016-01-03T12:04:50.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19702162779868" lon="8.793316148221493">
+        <ele>492.37</ele>
+        <time>2016-01-03T12:04:51.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19704459421337" lon="8.793262336403131">
+        <ele>492.30000000000001</ele>
+        <time>2016-01-03T12:04:52.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19706906937063" lon="8.793201064690948">
+        <ele>492.04000000000002</ele>
+        <time>2016-01-03T12:04:53.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19708985649049" lon="8.793123783543706">
+        <ele>491.37</ele>
+        <time>2016-01-03T12:04:54.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19710913486779" lon="8.79304625093937">
+        <ele>491.06999999999999</ele>
+        <time>2016-01-03T12:04:55.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19713117927313" lon="8.792974585667253">
+        <ele>490.39999999999998</ele>
+        <time>2016-01-03T12:04:56.000Z</time>
+      </trkpt>
+      <trkpt lat="47.197186248376966" lon="8.792809881269932">
+        <ele>489.75</ele>
+        <time>2016-01-03T12:04:58.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19720753841102" lon="8.79271550104022">
+        <ele>489.87</ele>
+        <time>2016-01-03T12:04:59.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19722296111286" lon="8.792620282620192">
+        <ele>488.95999999999998</ele>
+        <time>2016-01-03T12:05:00.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19733930192888" lon="8.792043440043926">
+        <ele>485.85000000000002</ele>
+        <time>2016-01-03T12:05:06.000Z</time>
+      </trkpt>
+      <trkpt lat="47.1974522061646" lon="8.791516218334436">
+        <ele>482.81</ele>
+        <time>2016-01-03T12:05:11.000Z</time>
+      </trkpt>
+      <trkpt lat="47.197455475106835" lon="8.79138101823628">
+        <ele>481.99000000000001</ele>
+        <time>2016-01-03T12:05:12.000Z</time>
+      </trkpt>
+      <trkpt lat="47.197490092366934" lon="8.79095789976418">
+        <ele>478.92</ele>
+      </trkpt>
+      <trkpt lat="47.19753225333989" lon="8.790533943101764">
+        <ele>476</ele>
+      </trkpt>
+      <trkpt lat="47.19762864522636" lon="8.789889458566904">
+        <ele>474.38</ele>
+        <time>2016-01-03T12:05:23.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19763744622469" lon="8.789792899042368">
+        <ele>474.35000000000002</ele>
+        <time>2016-01-03T12:05:24.000Z</time>
+      </trkpt>
+      <trkpt lat="47.1976438164711" lon="8.789720982313156">
+        <ele>474.12</ele>
+        <time>2016-01-03T12:05:25.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19764859415591" lon="8.789367768913507">
+        <ele>472.52999999999997</ele>
+        <time>2016-01-03T12:05:33.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19765613786876" lon="8.78931068815291">
+        <ele>472.75</ele>
+        <time>2016-01-03T12:05:34.000Z</time>
+      </trkpt>
+      <trkpt lat="47.1976903360337" lon="8.789105415344238">
+        <ele>472.19</ele>
+        <time>2016-01-03T12:05:37.000Z</time>
+      </trkpt>
+      <trkpt lat="47.197700729593635" lon="8.78903266042471">
+        <ele>471.92000000000002</ele>
+        <time>2016-01-03T12:05:38.000Z</time>
+      </trkpt>
+      <trkpt lat="47.197715900838375" lon="8.78876687027514">
+        <ele>471.98000000000002</ele>
+        <time>2016-01-03T12:05:41.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19775462523103" lon="8.788432851433754">
+        <ele>473.49000000000001</ele>
+        <time>2016-01-03T12:05:47.000Z</time>
+      </trkpt>
+      <trkpt lat="47.197777507826686" lon="8.78841600380838">
+        <ele>473.44999999999999</ele>
+        <time>2016-01-03T12:05:48.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19780064187944" lon="8.788405358791351">
+        <ele>473.45999999999998</ele>
+        <time>2016-01-03T12:05:49.000Z</time>
+      </trkpt>
+      <trkpt lat="47.197963669896126" lon="8.788386499509215">
+        <ele>470.27999999999997</ele>
+        <time>2016-01-03T12:05:53.000Z</time>
+      </trkpt>
+      <trkpt lat="47.1981308888644" lon="8.788295891135931">
+        <ele>467.20999999999998</ele>
+        <time>2016-01-03T12:05:57.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19813633710146" lon="8.788262866437435">
+        <ele>466.89999999999998</ele>
+        <time>2016-01-03T12:05:58.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19812661409378" lon="8.788229255005717">
+        <ele>466.76999999999998</ele>
+        <time>2016-01-03T12:05:59.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19810306094587" lon="8.788186172023416">
+        <ele>466.25</ele>
+        <time>2016-01-03T12:06:00.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19808369874954" lon="8.788127414882183">
+        <ele>465.23000000000002</ele>
+        <time>2016-01-03T12:06:01.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19807816669345" lon="8.788059940561652">
+        <ele>464.12</ele>
+        <time>2016-01-03T12:06:02.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19808881171048" lon="8.787991376593709">
+        <ele>463.68000000000001</ele>
+        <time>2016-01-03T12:06:03.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19811093993485" lon="8.787914346903563">
+        <ele>462.5</ele>
+        <time>2016-01-03T12:06:04.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19815100543201" lon="8.78783798776567">
+        <ele>462.45999999999998</ele>
+        <time>2016-01-03T12:06:05.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19819492660463" lon="8.787756934762001">
+        <ele>461.56999999999999</ele>
+        <time>2016-01-03T12:06:06.000Z</time>
+      </trkpt>
+      <trkpt lat="47.1982860378921" lon="8.787589464336634">
+        <ele>461.14999999999998</ele>
+        <time>2016-01-03T12:06:08.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19831814058125" lon="8.7874944973737">
+        <ele>460.19</ele>
+        <time>2016-01-03T12:06:09.000Z</time>
+      </trkpt>
+      <trkpt lat="47.1983309648931" lon="8.787389053031802">
+        <ele>459.83999999999997</ele>
+        <time>2016-01-03T12:06:10.000Z</time>
+      </trkpt>
+      <trkpt lat="47.198331551626325" lon="8.787284530699253">
+        <ele>460.49000000000001</ele>
+        <time>2016-01-03T12:06:11.000Z</time>
+      </trkpt>
+      <trkpt lat="47.198325265198946" lon="8.787185624241829">
+        <ele>460.23000000000002</ele>
+        <time>2016-01-03T12:06:12.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19824245199561" lon="8.78635254688561">
+        <ele>460.56999999999999</ele>
+        <time>2016-01-03T12:06:22.000Z</time>
+      </trkpt>
+      <trkpt lat="47.198192747309804" lon="8.78586145117879">
+        <ele>457.79000000000002</ele>
+        <time>2016-01-03T12:06:27.000Z</time>
+      </trkpt>
+      <trkpt lat="47.19819249585271" lon="8.78536943346262">
+        <ele>454.66000000000003</ele>
+        <time>2016-01-03T12:06:31.000Z</time>
+      </trkpt>
+    </trkseg>
+  </trk>
+  <trk>
+    <trkseg>
+      <trkpt lat="47.18660652637482" lon="8.796921372413635">
+        <ele>624.7421875</ele>
+      </trkpt>
+      <trkpt lat="47.186644077301025" lon="8.7968248128891">
+        <ele>626.015625</ele>
+      </trkpt>
+      <trkpt lat="47.18669772148132" lon="8.796712160110474">
+        <ele>627.0625</ele>
+      </trkpt>
+      <trkpt lat="47.18676209449768" lon="8.796610236167908">
+        <ele>627.3671875</ele>
+      </trkpt>
+      <trkpt lat="47.1868371963501" lon="8.796519041061401">
+        <ele>626.90234375</ele>
+      </trkpt>
+      <trkpt lat="47.186912298202515" lon="8.796401023864746">
+        <ele>626.11328125</ele>
+      </trkpt>
+      <trkpt lat="47.18698740005493" lon="8.796277642250061">
+        <ele>625.33203125</ele>
+      </trkpt>
+      <trkpt lat="47.18707859516144" lon="8.796148896217346">
+        <ele>623.90625</ele>
+      </trkpt>
+      <trkpt lat="47.18710541725159" lon="8.796095252037048">
+        <ele>623.66796875</ele>
+      </trkpt>
+      <trkpt lat="47.187126874923706" lon="8.796030879020691">
+        <ele>623.71484375</ele>
+      </trkpt>
+      <trkpt lat="47.187132239341736" lon="8.795977234840393">
+        <ele>623.75</ele>
+      </trkpt>
+      <trkpt lat="47.187132239341736" lon="8.795923590660095">
+        <ele>623.9375</ele>
+      </trkpt>
+      <trkpt lat="47.187132239341736" lon="8.795859217643738">
+        <ele>624.1640625</ele>
+      </trkpt>
+      <trkpt lat="47.18711078166962" lon="8.795735836029053">
+        <ele>625.234375</ele>
+      </trkpt>
+      <trkpt lat="47.18710005283356" lon="8.795607089996338">
+        <ele>626</ele>
+      </trkpt>
+      <trkpt lat="47.18709468841553" lon="8.795521259307861">
+        <ele>626.3515625</ele>
+      </trkpt>
+      <trkpt lat="47.18710541725159" lon="8.795440793037415">
+        <ele>626.14453125</ele>
+      </trkpt>
+      <trkpt lat="47.187126874923706" lon="8.795360326766968">
+        <ele>625.6640625</ele>
+      </trkpt>
+      <trkpt lat="47.187164425849915" lon="8.79528522491455">
+        <ele>624.7890625</ele>
+      </trkpt>
+      <trkpt lat="47.1872341632843" lon="8.795183300971985">
+        <ele>623.23046875</ele>
+      </trkpt>
+      <trkpt lat="47.18729317188263" lon="8.795086741447449">
+        <ele>622.09375</ele>
+      </trkpt>
+      <trkpt lat="47.18736290931702" lon="8.794984817504883">
+        <ele>620.9765625</ele>
+      </trkpt>
+      <trkpt lat="47.187416553497314" lon="8.794904351234436">
+        <ele>620.23828125</ele>
+      </trkpt>
+      <trkpt lat="47.18746483325958" lon="8.794845342636108">
+        <ele>619.42578125</ele>
+      </trkpt>
+      <trkpt lat="47.18752384185791" lon="8.79479706287384">
+        <ele>617.7734375</ele>
+      </trkpt>
+      <trkpt lat="47.18756139278412" lon="8.794743418693542">
+        <ele>616.8671875</ele>
+      </trkpt>
+      <trkpt lat="47.1875935792923" lon="8.794695138931274">
+        <ele>616.09765625</ele>
+      </trkpt>
+      <trkpt lat="47.18760967254639" lon="8.794630765914917">
+        <ele>615.87890625</ele>
+      </trkpt>
+      <trkpt lat="47.187620401382446" lon="8.7945556640625">
+        <ele>615.3984375</ele>
+      </trkpt>
+      <trkpt lat="47.187636494636536" lon="8.794485926628113">
+        <ele>614.76953125</ele>
+      </trkpt>
+      <trkpt lat="47.187674045562744" lon="8.794400095939636">
+        <ele>613.47265625</ele>
+      </trkpt>
+      <trkpt lat="47.18769550323486" lon="8.79430890083313">
+        <ele>612.72265625</ele>
+      </trkpt>
+      <trkpt lat="47.18770086765289" lon="8.794185519218445">
+        <ele>612.4609375</ele>
+      </trkpt>
+      <trkpt lat="47.18770086765289" lon="8.79407823085785">
+        <ele>611.85546875</ele>
+      </trkpt>
+      <trkpt lat="47.187684774398804" lon="8.793997764587402">
+        <ele>611.90625</ele>
+      </trkpt>
+      <trkpt lat="47.187663316726685" lon="8.793933391571045">
+        <ele>612.27734375</ele>
+      </trkpt>
+      <trkpt lat="47.18761503696442" lon="8.793869018554688">
+        <ele>613.66796875</ele>
+      </trkpt>
+      <trkpt lat="47.18758285045624" lon="8.79381537437439">
+        <ele>614.62890625</ele>
+      </trkpt>
+      <trkpt lat="47.18756675720215" lon="8.793767094612122">
+        <ele>615.0703125</ele>
+      </trkpt>
+      <trkpt lat="47.18757212162018" lon="8.793713450431824">
+        <ele>614.72265625</ele>
+      </trkpt>
+      <trkpt lat="47.18758821487427" lon="8.793681263923645">
+        <ele>614.26171875</ele>
+      </trkpt>
+      <trkpt lat="47.187641859054565" lon="8.793681263923645">
+        <ele>612.06640625</ele>
+      </trkpt>
+      <trkpt lat="47.18770086765289" lon="8.793708086013794">
+        <ele>609.6875</ele>
+      </trkpt>
+      <trkpt lat="47.18778133392334" lon="8.793751001358032">
+        <ele>606.83984375</ele>
+      </trkpt>
+      <trkpt lat="47.18785107135773" lon="8.79380464553833">
+        <ele>604.671875</ele>
+      </trkpt>
+      <trkpt lat="47.187920808792114" lon="8.793842196464539">
+        <ele>602.52734375</ele>
+      </trkpt>
+      <trkpt lat="47.18800127506256" lon="8.793911933898926">
+        <ele>600.78515625</ele>
+      </trkpt>
+      <trkpt lat="47.18811392784119" lon="8.794013857841492">
+        <ele>598.078125</ele>
+      </trkpt>
+      <trkpt lat="47.188151478767395" lon="8.79406213760376">
+        <ele>597.1953125</ele>
+      </trkpt>
+      <trkpt lat="47.1881890296936" lon="8.794153332710266">
+        <ele>596.5234375</ele>
+      </trkpt>
+      <trkpt lat="47.18821585178375" lon="8.794239163398743">
+        <ele>595.74609375</ele>
+      </trkpt>
+      <trkpt lat="47.1882426738739" lon="8.794351816177368">
+        <ele>594.82421875</ele>
+      </trkpt>
+      <trkpt lat="47.18826413154602" lon="8.794426918029785">
+        <ele>594.10546875</ele>
+      </trkpt>
+      <trkpt lat="47.18829095363617" lon="8.794475197792053">
+        <ele>593.2109375</ele>
+      </trkpt>
+      <trkpt lat="47.18830704689026" lon="8.794523477554321">
+        <ele>592.6875</ele>
+      </trkpt>
+      <trkpt lat="47.18831241130829" lon="8.794614672660828">
+        <ele>592.5390625</ele>
+      </trkpt>
+      <trkpt lat="47.18830704689026" lon="8.79479706287384">
+        <ele>592.7578125</ele>
+      </trkpt>
+      <trkpt lat="47.18830704689026" lon="8.794888257980347">
+        <ele>592.77734375</ele>
+      </trkpt>
+      <trkpt lat="47.18831241130829" lon="8.794941902160645">
+        <ele>592.63671875</ele>
+      </trkpt>
+      <trkpt lat="47.18833386898041" lon="8.794995546340942">
+        <ele>592.06640625</ele>
+      </trkpt>
+      <trkpt lat="47.188366055488586" lon="8.7950599193573">
+        <ele>591.2578125</ele>
+      </trkpt>
+      <trkpt lat="47.188403606414795" lon="8.795113563537598">
+        <ele>590.46875</ele>
+      </trkpt>
+      <trkpt lat="47.188435792922974" lon="8.795167207717896">
+        <ele>589.7578125</ele>
+      </trkpt>
+      <trkpt lat="47.18845188617706" lon="8.795226216316223">
+        <ele>589.359375</ele>
+      </trkpt>
+      <trkpt lat="47.18846261501312" lon="8.79529058933258">
+        <ele>589.05859375</ele>
+      </trkpt>
+      <trkpt lat="47.18845188617706" lon="8.795354962348938">
+        <ele>589.21875</ele>
+      </trkpt>
+      <trkpt lat="47.188430428504944" lon="8.795451521873474">
+        <ele>589.59375</ele>
+      </trkpt>
+      <trkpt lat="47.188408970832825" lon="8.7955641746521">
+        <ele>589.98046875</ele>
+      </trkpt>
+      <trkpt lat="47.188403606414795" lon="8.795655369758606">
+        <ele>589.51953125</ele>
+      </trkpt>
+      <trkpt lat="47.188408970832825" lon="8.795746564865112">
+        <ele>588.796875</ele>
+      </trkpt>
+      <trkpt lat="47.188435792922974" lon="8.795859217643738">
+        <ele>587.4375</ele>
+      </trkpt>
+      <trkpt lat="47.18847870826721" lon="8.795971870422363">
+        <ele>585.70703125</ele>
+      </trkpt>
+      <trkpt lat="47.18850553035736" lon="8.79605233669281">
+        <ele>584.48046875</ele>
+      </trkpt>
+      <trkpt lat="47.18852162361145" lon="8.796132802963257">
+        <ele>583.328125</ele>
+      </trkpt>
+      <trkpt lat="47.18852162361145" lon="8.796197175979614">
+        <ele>582.69921875</ele>
+      </trkpt>
+      <trkpt lat="47.18851625919342" lon="8.79628300666809">
+        <ele>581.96875</ele>
+      </trkpt>
+      <trkpt lat="47.18850016593933" lon="8.796379566192627">
+        <ele>581.3359375</ele>
+      </trkpt>
+      <trkpt lat="47.18848943710327" lon="8.796465396881104">
+        <ele>580.6796875</ele>
+      </trkpt>
+      <trkpt lat="47.18848943710327" lon="8.796524405479431">
+        <ele>580.33984375</ele>
+      </trkpt>
+      <trkpt lat="47.18850016593933" lon="8.796578049659729">
+        <ele>580.0234375</ele>
+      </trkpt>
+      <trkpt lat="47.18852162361145" lon="8.796631693840027">
+        <ele>579.5078125</ele>
+      </trkpt>
+      <trkpt lat="47.1885484457016" lon="8.796685338020325">
+        <ele>578.890625</ele>
+      </trkpt>
+      <trkpt lat="47.18856990337372" lon="8.796749711036682">
+        <ele>578.34375</ele>
+      </trkpt>
+      <trkpt lat="47.18858063220978" lon="8.79681944847107">
+        <ele>577.984375</ele>
+      </trkpt>
+      <trkpt lat="47.18857526779175" lon="8.796899914741516">
+        <ele>577.90234375</ele>
+      </trkpt>
+      <trkpt lat="47.18858599662781" lon="8.796991109848022">
+        <ele>577.50390625</ele>
+      </trkpt>
+      <trkpt lat="47.18859672546387" lon="8.79704475402832">
+        <ele>577.20703125</ele>
+      </trkpt>
+      <trkpt lat="47.188623547554016" lon="8.797119855880737">
+        <ele>576.6171875</ele>
+      </trkpt>
+      <trkpt lat="47.188666462898254" lon="8.797189593315125">
+        <ele>575.82421875</ele>
+      </trkpt>
+      <trkpt lat="47.18870937824249" lon="8.797211050987244">
+        <ele>575.125</ele>
+      </trkpt>
+      <trkpt lat="47.18871474266052" lon="8.797157406806946">
+        <ele>575.0546875</ele>
+      </trkpt>
+      <trkpt lat="47.18872547149658" lon="8.79705548286438">
+        <ele>574.89453125</ele>
+      </trkpt>
+      <trkpt lat="47.18873620033264" lon="8.796937465667725">
+        <ele>574.75</ele>
+      </trkpt>
+      <trkpt lat="47.18875765800476" lon="8.79681408405304">
+        <ele>574.6484375</ele>
+      </trkpt>
+      <trkpt lat="47.18879520893097" lon="8.796685338020325">
+        <ele>574.26953125</ele>
+      </trkpt>
+      <trkpt lat="47.18883812427521" lon="8.796588778495789">
+        <ele>573.72265625</ele>
+      </trkpt>
+      <trkpt lat="47.188907861709595" lon="8.796438574790955">
+        <ele>572.4296875</ele>
+      </trkpt>
+      <trkpt lat="47.18901515007019" lon="8.796240091323853">
+        <ele>570.828125</ele>
+      </trkpt>
+      <trkpt lat="47.18906342983246" lon="8.796154260635376">
+        <ele>570.1015625</ele>
+      </trkpt>
+      <trkpt lat="47.189154624938965" lon="8.795987963676453">
+        <ele>568.7734375</ele>
+      </trkpt>
+      <trkpt lat="47.18921899795532" lon="8.795869946479797">
+        <ele>567.8125</ele>
+      </trkpt>
+      <trkpt lat="47.18926191329956" lon="8.795778751373291">
+        <ele>567.25390625</ele>
+      </trkpt>
+      <trkpt lat="47.18928337097168" lon="8.795714378356934">
+        <ele>567.09765625</ele>
+      </trkpt>
+      <trkpt lat="47.18929409980774" lon="8.795660734176636">
+        <ele>567.1640625</ele>
+      </trkpt>
+      <trkpt lat="47.18929946422577" lon="8.795580267906189">
+        <ele>567.546875</ele>
+      </trkpt>
+      <trkpt lat="47.18929946422577" lon="8.795451521873474">
+        <ele>567.86328125</ele>
+      </trkpt>
+      <trkpt lat="47.18929409980774" lon="8.7953120470047">
+        <ele>568.2890625</ele>
+      </trkpt>
+      <trkpt lat="47.18929409980774" lon="8.795210123062134">
+        <ele>568.49609375</ele>
+      </trkpt>
+      <trkpt lat="47.1893048286438" lon="8.795135021209717">
+        <ele>568.33203125</ele>
+      </trkpt>
+      <trkpt lat="47.18934237957001" lon="8.794904351234436">
+        <ele>566.84765625</ele>
+      </trkpt>
+      <trkpt lat="47.18934774398804" lon="8.794845342636108">
+        <ele>566.609375</ele>
+      </trkpt>
+      <trkpt lat="47.18933701515198" lon="8.794738054275513">
+        <ele>566.94140625</ele>
+      </trkpt>
+      <trkpt lat="47.18933701515198" lon="8.794657588005066">
+        <ele>566.88671875</ele>
+      </trkpt>
+      <trkpt lat="47.18935310840607" lon="8.79457712173462">
+        <ele>566.27734375</ele>
+      </trkpt>
+      <trkpt lat="47.189374566078186" lon="8.794502019882202">
+        <ele>565.48046875</ele>
+      </trkpt>
+      <trkpt lat="47.189428210258484" lon="8.794384002685547">
+        <ele>563.5</ele>
+      </trkpt>
+      <trkpt lat="47.18945503234863" lon="8.7943035364151">
+        <ele>562.51953125</ele>
+      </trkpt>
+      <trkpt lat="47.18947112560272" lon="8.794206976890564">
+        <ele>561.9453125</ele>
+      </trkpt>
+      <trkpt lat="47.18948185443878" lon="8.79407286643982">
+        <ele>561.85546875</ele>
+      </trkpt>
+      <trkpt lat="47.18949794769287" lon="8.793911933898926">
+        <ele>561.7578125</ele>
+      </trkpt>
+      <trkpt lat="47.18950867652893" lon="8.79378855228424">
+        <ele>561.765625</ele>
+      </trkpt>
+      <trkpt lat="47.18952476978302" lon="8.793686628341675">
+        <ele>561.453125</ele>
+      </trkpt>
+      <trkpt lat="47.18955159187317" lon="8.79356861114502">
+        <ele>560.6015625</ele>
+      </trkpt>
+      <trkpt lat="47.18958377838135" lon="8.793466687202454">
+        <ele>559.65234375</ele>
+      </trkpt>
+      <trkpt lat="47.189615964889526" lon="8.793386220932007">
+        <ele>558.7578125</ele>
+      </trkpt>
+      <trkpt lat="47.189658880233765" lon="8.793278932571411">
+        <ele>557.66015625</ele>
+      </trkpt>
+      <trkpt lat="47.18971252441406" lon="8.793187737464905">
+        <ele>556.35546875</ele>
+      </trkpt>
+      <trkpt lat="47.18976080417633" lon="8.793118000030518">
+        <ele>555.2265625</ele>
+      </trkpt>
+      <trkpt lat="47.18983054161072" lon="8.79304826259613">
+        <ele>553.53125</ele>
+      </trkpt>
+      <trkpt lat="47.189921736717224" lon="8.792962431907654">
+        <ele>551.265625</ele>
+      </trkpt>
+      <trkpt lat="47.18997538089752" lon="8.792903423309326">
+        <ele>549.92578125</ele>
+      </trkpt>
+      <trkpt lat="47.1900075674057" lon="8.792855143547058">
+        <ele>549.140625</ele>
+      </trkpt>
+      <trkpt lat="47.19003438949585" lon="8.792780041694641">
+        <ele>548.48828125</ele>
+      </trkpt>
+      <trkpt lat="47.19004511833191" lon="8.792726397514343">
+        <ele>548.0078125</ele>
+      </trkpt>
+      <trkpt lat="47.19005584716797" lon="8.792635202407837">
+        <ele>547.39453125</ele>
+      </trkpt>
+      <trkpt lat="47.19005584716797" lon="8.79256546497345">
+        <ele>547.15234375</ele>
+      </trkpt>
+      <trkpt lat="47.19005048274994" lon="8.792501091957092">
+        <ele>547.08203125</ele>
+      </trkpt>
+      <trkpt lat="47.19001829624176" lon="8.792366981506348">
+        <ele>547.55078125</ele>
+      </trkpt>
+      <trkpt lat="47.18994319438934" lon="8.792104125022888">
+        <ele>548.69921875</ele>
+      </trkpt>
+      <trkpt lat="47.189932465553284" lon="8.792034387588501">
+        <ele>548.76953125</ele>
+      </trkpt>
+      <trkpt lat="47.189932465553284" lon="8.791970014572144">
+        <ele>548.51953125</ele>
+      </trkpt>
+      <trkpt lat="47.18994319438934" lon="8.791900277137756">
+        <ele>547.88671875</ele>
+      </trkpt>
+      <trkpt lat="47.18997001647949" lon="8.79183053970337">
+        <ele>546.7265625</ele>
+      </trkpt>
+      <trkpt lat="47.19000220298767" lon="8.791755437850952">
+        <ele>545.3984375</ele>
+      </trkpt>
+      <trkpt lat="47.19003975391388" lon="8.791680335998535">
+        <ele>543.87890625</ele>
+      </trkpt>
+      <trkpt lat="47.190120220184326" lon="8.791535496711731">
+        <ele>540.6484375</ele>
+      </trkpt>
+      <trkpt lat="47.19023287296295" lon="8.79129409790039">
+        <ele>536.4296875</ele>
+      </trkpt>
+      <trkpt lat="47.19025433063507" lon="8.791224360466003">
+        <ele>536.046875</ele>
+      </trkpt>
+      <trkpt lat="47.19027042388916" lon="8.791149258613586">
+        <ele>535.765625</ele>
+      </trkpt>
+      <trkpt lat="47.19027578830719" lon="8.791015148162842">
+        <ele>535.71875</ele>
+      </trkpt>
+      <trkpt lat="47.19027578830719" lon="8.790768384933472">
+        <ele>535.55859375</ele>
+      </trkpt>
+      <trkpt lat="47.19026505947113" lon="8.790435791015625">
+        <ele>535.359375</ele>
+      </trkpt>
+      <trkpt lat="47.1902596950531" lon="8.790162205696106">
+        <ele>534.5234375</ele>
+      </trkpt>
+      <trkpt lat="47.1902596950531" lon="8.789920806884766">
+        <ele>534.2578125</ele>
+      </trkpt>
+      <trkpt lat="47.19025433063507" lon="8.789674043655396">
+        <ele>535.1328125</ele>
+      </trkpt>
+      <trkpt lat="47.19025433063507" lon="8.78955602645874">
+        <ele>535.46875</ele>
+      </trkpt>
+      <trkpt lat="47.19025433063507" lon="8.789491653442383">
+        <ele>535.04296875</ele>
+      </trkpt>
+      <trkpt lat="47.19027042388916" lon="8.789438009262085">
+        <ele>534.04296875</ele>
+      </trkpt>
+      <trkpt lat="47.19030797481537" lon="8.789384365081787">
+        <ele>532.234375</ele>
+      </trkpt>
+      <trkpt lat="47.19035625457764" lon="8.789357542991638">
+        <ele>530.2109375</ele>
+      </trkpt>
+      <trkpt lat="47.190393805503845" lon="8.789357542991638">
+        <ele>528.7890625</ele>
+      </trkpt>
+      <trkpt lat="47.190425992012024" lon="8.789373636245728">
+        <ele>527.69140625</ele>
+      </trkpt>
+      <trkpt lat="47.1904581785202" lon="8.789411187171936">
+        <ele>526.76171875</ele>
+      </trkpt>
+      <trkpt lat="47.19048500061035" lon="8.789459466934204">
+        <ele>526.1171875</ele>
+      </trkpt>
+      <trkpt lat="47.19050645828247" lon="8.789529204368591">
+        <ele>525.83984375</ele>
+      </trkpt>
+      <trkpt lat="47.19052255153656" lon="8.789609670639038">
+        <ele>525.51171875</ele>
+      </trkpt>
+      <trkpt lat="47.19053328037262" lon="8.789727687835693">
+        <ele>525.34375</ele>
+      </trkpt>
+      <trkpt lat="47.19054400920868" lon="8.78982961177826">
+        <ele>525.18359375</ele>
+      </trkpt>
+      <trkpt lat="47.19058692455292" lon="8.790022730827332">
+        <ele>524.3515625</ele>
+      </trkpt>
+      <trkpt lat="47.190635204315186" lon="8.790242671966553">
+        <ele>524.41015625</ele>
+      </trkpt>
+      <trkpt lat="47.190672755241394" lon="8.790398240089417">
+        <ele>524.47265625</ele>
+      </trkpt>
+      <trkpt lat="47.19072639942169" lon="8.79054844379425">
+        <ele>524.02734375</ele>
+      </trkpt>
+      <trkpt lat="47.19078004360199" lon="8.790677189826965">
+        <ele>523.09375</ele>
+      </trkpt>
+      <trkpt lat="47.19083368778229" lon="8.79082202911377">
+        <ele>521.92578125</ele>
+      </trkpt>
+      <trkpt lat="47.190876603126526" lon="8.790961503982544">
+        <ele>520.87109375</ele>
+      </trkpt>
+      <trkpt lat="47.190908789634705" lon="8.791095614433289">
+        <ele>520.51953125</ele>
+      </trkpt>
+      <trkpt lat="47.19093561172485" lon="8.791192173957825">
+        <ele>520.2578125</ele>
+      </trkpt>
+      <trkpt lat="47.19097316265106" lon="8.791267275810242">
+        <ele>519.6640625</ele>
+      </trkpt>
+      <trkpt lat="47.19103217124939" lon="8.791337013244629">
+        <ele>518.5703125</ele>
+      </trkpt>
+      <trkpt lat="47.191112637519836" lon="8.791401386260986">
+        <ele>517.04296875</ele>
+      </trkpt>
+      <trkpt lat="47.19128429889679" lon="8.791524767875671">
+        <ele>513.03125</ele>
+      </trkpt>
+      <trkpt lat="47.191407680511475" lon="8.791610598564148">
+        <ele>510.19921875</ele>
+      </trkpt>
+      <trkpt lat="47.19148814678192" lon="8.791669607162476">
+        <ele>508.5</ele>
+      </trkpt>
+      <trkpt lat="47.1915203332901" lon="8.791696429252625">
+        <ele>507.8828125</ele>
+      </trkpt>
+      <trkpt lat="47.19157934188843" lon="8.791739344596863">
+        <ele>506.71875</ele>
+      </trkpt>
+      <trkpt lat="47.191627621650696" lon="8.79179835319519">
+        <ele>506.03125</ele>
+      </trkpt>
+      <trkpt lat="47.191659808158875" lon="8.791851997375488">
+        <ele>505.5390625</ele>
+      </trkpt>
+      <trkpt lat="47.19168663024902" lon="8.791916370391846">
+        <ele>504.96875</ele>
+      </trkpt>
+      <trkpt lat="47.1917188167572" lon="8.792018294334412">
+        <ele>504.28125</ele>
+      </trkpt>
+      <trkpt lat="47.19175100326538" lon="8.792141675949097">
+        <ele>503.6640625</ele>
+      </trkpt>
+      <trkpt lat="47.19179928302765" lon="8.79229724407196">
+        <ele>502.578125</ele>
+      </trkpt>
+      <trkpt lat="47.19184219837189" lon="8.792458176612854">
+        <ele>500.921875</ele>
+      </trkpt>
+      <trkpt lat="47.191869020462036" lon="8.79257082939148">
+        <ele>499.85546875</ele>
+      </trkpt>
+      <trkpt lat="47.191879749298096" lon="8.792662024497986">
+        <ele>499.40234375</ele>
+      </trkpt>
+      <trkpt lat="47.191890478134155" lon="8.792763948440552">
+        <ele>498.96875</ele>
+      </trkpt>
+      <trkpt lat="47.191895842552185" lon="8.792887330055237">
+        <ele>497.8828125</ele>
+      </trkpt>
+      <trkpt lat="47.191895842552185" lon="8.79306435585022">
+        <ele>496.2265625</ele>
+      </trkpt>
+      <trkpt lat="47.191901206970215" lon="8.793182373046875">
+        <ele>494.9609375</ele>
+      </trkpt>
+      <trkpt lat="47.191906571388245" lon="8.793257474899292">
+        <ele>494.13671875</ele>
+      </trkpt>
+      <trkpt lat="47.191928029060364" lon="8.793343305587769">
+        <ele>493.6015625</ele>
+      </trkpt>
+      <trkpt lat="47.19196021556854" lon="8.793472051620483">
+        <ele>492.828125</ele>
+      </trkpt>
+      <trkpt lat="47.19199282117188" lon="8.793616723269224">
+        <ele>492.1015625</ele>
+      </trkpt>
+      <trkpt lat="47.19200849533081" lon="8.793686628341675">
+        <ele>491.76953125</ele>
+      </trkpt>
+      <trkpt lat="47.19204068183899" lon="8.793852925300598">
+        <ele>490.83984375</ele>
+      </trkpt>
+      <trkpt lat="47.19206213951111" lon="8.793992400169373">
+        <ele>490.23828125</ele>
+      </trkpt>
+      <trkpt lat="47.19207286834717" lon="8.7940514087677">
+        <ele>489.96484375</ele>
+      </trkpt>
+      <trkpt lat="47.19208359718323" lon="8.794147968292236">
+        <ele>489.703125</ele>
+      </trkpt>
+      <trkpt lat="47.192121148109436" lon="8.795000910758972">
+        <ele>484.7109375</ele>
+      </trkpt>
+      <trkpt lat="47.192137241363525" lon="8.79530131816864">
+        <ele>482.328125</ele>
+      </trkpt>
+      <trkpt lat="47.192137241363525" lon="8.795451521873474">
+        <ele>481.04296875</ele>
+      </trkpt>
+      <trkpt lat="47.192137241363525" lon="8.79553735256195">
+        <ele>480.3046875</ele>
+      </trkpt>
+      <trkpt lat="47.192126512527466" lon="8.795607089996338">
+        <ele>479.70703125</ele>
+      </trkpt>
+      <trkpt lat="47.19211041927338" lon="8.795682191848755">
+        <ele>478.984375</ele>
+      </trkpt>
+      <trkpt lat="47.19208896160126" lon="8.795746564865112">
+        <ele>478.4609375</ele>
+      </trkpt>
+      <trkpt lat="47.19203531742096" lon="8.795832395553589">
+        <ele>479.1015625</ele>
+      </trkpt>
+      <trkpt lat="47.19198167324066" lon="8.795896768569946">
+        <ele>480.359375</ele>
+      </trkpt>
+      <trkpt lat="47.19194948673248" lon="8.795934319496155">
+        <ele>481.19140625</ele>
+      </trkpt>
+      <trkpt lat="47.191906571388245" lon="8.795993328094482">
+        <ele>482.2890625</ele>
+      </trkpt>
+      <trkpt lat="47.191869020462036" lon="8.79606306552887">
+        <ele>483.28125</ele>
+      </trkpt>
+      <trkpt lat="47.19184756278992" lon="8.796138167381287">
+        <ele>483.69921875</ele>
+      </trkpt>
+      <trkpt lat="47.19184219837189" lon="8.796213269233704">
+        <ele>483.4609375</ele>
+      </trkpt>
+      <trkpt lat="47.19184219837189" lon="8.79628300666809">
+        <ele>483.0390625</ele>
+      </trkpt>
+      <trkpt lat="47.19185829162598" lon="8.796374201774597">
+        <ele>481.9140625</ele>
+      </trkpt>
+      <trkpt lat="47.191885113716125" lon="8.796454668045044">
+        <ele>480.515625</ele>
+      </trkpt>
+      <trkpt lat="47.191917300224304" lon="8.796513676643372">
+        <ele>479.16796875</ele>
+      </trkpt>
+      <trkpt lat="47.19198703765869" lon="8.796631693840027">
+        <ele>476.74609375</ele>
+      </trkpt>
+      <trkpt lat="47.19206750392914" lon="8.796760439872742">
+        <ele>474.3515625</ele>
+      </trkpt>
+      <trkpt lat="47.192131876945496" lon="8.796835541725159">
+        <ele>473.28515625</ele>
+      </trkpt>
+      <trkpt lat="47.19219088554382" lon="8.796894550323486">
+        <ele>472.39453125</ele>
+      </trkpt>
+      <trkpt lat="47.19225525856018" lon="8.796948194503784">
+        <ele>471.3671875</ele>
+      </trkpt>
+      <trkpt lat="47.19234645366669" lon="8.797001838684082">
+        <ele>470.0390625</ele>
+      </trkpt>
+      <trkpt lat="47.192426919937134" lon="8.79704475402832">
+        <ele>468.76171875</ele>
+      </trkpt>
+      <trkpt lat="47.19250738620758" lon="8.797082304954529">
+        <ele>467.3671875</ele>
+      </trkpt>
+      <trkpt lat="47.19255566596985" lon="8.797114491462708">
+        <ele>466.60546875</ele>
+      </trkpt>
+      <trkpt lat="47.1927273273468" lon="8.797253966331482">
+        <ele>464.65625</ele>
+      </trkpt>
+      <trkpt lat="47.19280779361725" lon="8.79730761051178">
+        <ele>463.62109375</ele>
+      </trkpt>
+      <trkpt lat="47.19286604784429" lon="8.79733837209642">
+        <ele>462.84375</ele>
+      </trkpt>
+      <trkpt lat="47.19286596402526" lon="8.797338204458356">
+        <ele>462.84765625</ele>
+      </trkpt>
+    </trkseg>
+  </trk>
+  <trk>
+    <trkseg>
+      <trkpt lat="47.18561486341059" lon="8.796475538983941">
+        <ele>647.81640625</ele>
+      </trkpt>
+      <trkpt lat="47.185668759047985" lon="8.796514933928847">
+        <ele>646.87109375</ele>
+      </trkpt>
+      <trkpt lat="47.18571729026735" lon="8.796522812917829">
+        <ele>646.1953125</ele>
+      </trkpt>
+      <trkpt lat="47.185757691040635" lon="8.796562207862735">
+        <ele>645.5859375</ele>
+      </trkpt>
+      <trkpt lat="47.18580354005098" lon="8.796577882021666">
+        <ele>644.8203125</ele>
+      </trkpt>
+      <trkpt lat="47.185914013534784" lon="8.796581821516156">
+        <ele>642.9765625</ele>
+      </trkpt>
+      <trkpt lat="47.18596254475415" lon="8.796625155955553">
+        <ele>641.796875</ele>
+      </trkpt>
+      <trkpt lat="47.186081148684025" lon="8.796644853428006">
+        <ele>639.4296875</ele>
+      </trkpt>
+      <trkpt lat="47.186186257749796" lon="8.7967315223068">
+        <ele>636.4921875</ele>
+      </trkpt>
+      <trkpt lat="47.186264377087355" lon="8.796790530905128">
+        <ele>634.1328125</ele>
+      </trkpt>
+      <trkpt lat="47.18639915809035" lon="8.796892957761884">
+        <ele>629.55859375</ele>
+      </trkpt>
+      <trkpt lat="47.186458418145776" lon="8.7969284132123">
+        <ele>627.55078125</ele>
+      </trkpt>
+      <trkpt lat="47.18660661019385" lon="8.796955989673734">
+        <ele>624.0234375</ele>
+      </trkpt>
+    </trkseg>
+  </trk>
+  <trk>
+    <trkseg>
+      <trkpt lat="47.18490038998425" lon="8.796944171190262">
+        <ele>663.90234375</ele>
+      </trkpt>
+      <trkpt lat="47.18495160341263" lon="8.796790530905128">
+        <ele>662.74609375</ele>
+      </trkpt>
+      <trkpt lat="47.18495696783066" lon="8.796640913933516">
+        <ele>662.1328125</ele>
+      </trkpt>
+      <trkpt lat="47.184981275349855" lon="8.796550389379263">
+        <ele>661.39453125</ele>
+      </trkpt>
+      <trkpt lat="47.18511060811579" lon="8.796479478478432">
+        <ele>658.98828125</ele>
+      </trkpt>
+      <trkpt lat="47.18521839939058" lon="8.7964085675776">
+        <ele>657.47265625</ele>
+      </trkpt>
+      <trkpt lat="47.18536122702062" lon="8.796420386061072">
+        <ele>654.22265625</ele>
+      </trkpt>
+      <trkpt lat="47.18553640879691" lon="8.796440083533525">
+        <ele>650.0703125</ele>
+      </trkpt>
+      <trkpt lat="47.185609163716435" lon="8.796475538983941">
+        <ele>647.890625</ele>
+      </trkpt>
+    </trkseg>
+  </trk>
+  <trk>
+    <trkseg>
+      <trkpt lat="47.20138901844621" lon="8.774476982653141">
+        <ele>421.52999999999997</ele>
+        <time>2016-01-03T12:08:53.000Z</time>
+      </trkpt>
+      <trkpt lat="47.201492534950376" lon="8.773281471803784">
+        <ele>423.57999999999998</ele>
+        <time>2016-01-03T12:09:10.000Z</time>
+      </trkpt>
+      <trkpt lat="47.2014878410846" lon="8.772208672016859">
+        <ele>423</ele>
+        <time>2016-01-03T12:09:26.000Z</time>
+      </trkpt>
+      <trkpt lat="47.2014997433871" lon="8.772082105278969">
+        <ele>423.07999999999998</ele>
+        <time>2016-01-03T12:09:28.000Z</time>
+      </trkpt>
+      <trkpt lat="47.2014896851033" lon="8.772034915164113">
+        <ele>423.02999999999997</ele>
+        <time>2016-01-03T12:09:29.000Z</time>
+      </trkpt>
+      <trkpt lat="47.20144324935973" lon="8.77200884744525">
+        <ele>422.26999999999998</ele>
+        <time>2016-01-03T12:09:31.000Z</time>
+      </trkpt>
+      <trkpt lat="47.201405949890614" lon="8.7720338255167">
+        <ele>421.35000000000002</ele>
+        <time>2016-01-03T12:09:42.000Z</time>
+      </trkpt>
+      <trkpt lat="47.20132657326758" lon="8.771982276812196">
+        <ele>421.75</ele>
+        <time>2016-01-03T12:10:04.000Z</time>
+      </trkpt>
+      <trkpt lat="47.20126815140247" lon="8.77192972227931">
+        <ele>421.48000000000002</ele>
+        <time>2016-01-03T12:10:23.000Z</time>
+      </trkpt>
+    </trkseg>
+  </trk>
+</gpx>
Index: /trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java	(revision 14205)
+++ /trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxImageCorrelationTest.java	(revision 14205)
@@ -0,0 +1,269 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.gpx;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.data.coor.CachedLatLon;
+import org.openstreetmap.josm.io.GpxReaderTest;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.spi.preferences.IPreferences;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.tools.date.DateUtils;
+import org.openstreetmap.josm.tools.date.DateUtilsTest;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Unit tests of {@link GpxImageCorrelation} class.
+ */
+public class GpxImageCorrelationTest {
+
+    /**
+     * Setup test.
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules();
+
+    /**
+     * Setup test.
+     */
+    @BeforeClass
+    public static void setUp() {
+        DateUtilsTest.setTimeZone(DateUtils.UTC);
+    }
+
+    /**
+     * Tests matching of images to a GPX track.
+     * @throws Exception if the track cannot be parsed
+     */
+    @Test
+    public void testMatchGpxTrack() throws Exception {
+        IPreferences s = Config.getPref();
+        final GpxData gpx = GpxReaderTest.parseGpxData(TestUtils.getTestDataRoot() + "ImageCorrelationTest.gpx");
+        assertEquals(5, gpx.tracks.size());
+        assertEquals(1, gpx.tracks.iterator().next().getSegments().size());
+        assertEquals(128, gpx.tracks.iterator().next().getSegments().iterator().next().getWayPoints().size());
+
+        final GpxImageEntry ib = new GpxImageEntry();
+        ib.setExifTime(DateUtils.fromString("2016:01:03 11:54:58")); // 5 minutes before start of GPX
+        ib.createTmp();
+        final GpxImageEntry i0 = new GpxImageEntry();
+        i0.setExifTime(DateUtils.fromString("2016:01:03 11:59:54")); // 4 sec before start of GPX
+        i0.createTmp();
+        final GpxImageEntry i1 = new GpxImageEntry();
+        i1.setExifTime(DateUtils.fromString("2016:01:03 12:04:01"));
+        i1.createTmp();
+        final GpxImageEntry i2 = new GpxImageEntry();
+        i2.setExifTime(DateUtils.fromString("2016:01:03 12:04:57"));
+        i2.createTmp();
+        final GpxImageEntry i3 = new GpxImageEntry();
+        i3.setExifTime(DateUtils.fromString("2016:01:03 12:05:05"));
+        i3.createTmp();
+        final GpxImageEntry i4 = new GpxImageEntry(); //Image close to two points with elevation, but without time
+        i4.setExifTime(DateUtils.fromString("2016:01:03 12:05:20"));
+        i4.createTmp();
+        final GpxImageEntry i5 = new GpxImageEntry(); //between two tracks, closer to first
+        i5.setExifTime(DateUtils.fromString("2016:01:03 12:07:00"));
+        i5.createTmp();
+        final GpxImageEntry i6 = new GpxImageEntry(); //between two tracks, closer to second (more than 1 minute from any track)
+        i6.setExifTime(DateUtils.fromString("2016:01:03 12:07:45"));
+        i6.createTmp();
+
+        List<GpxImageEntry> images = Arrays.asList(ib, i0, i1, i2, i3, i4, i5, i6);
+
+        // TEST #1: default settings
+        // tag images within 2 minutes to tracks/segments, interpolate between segments only
+        assertEquals(7, GpxImageCorrelation.matchGpxTrack(images, gpx, 0, false));
+        assertEquals(null, ib.getPos());
+        assertEquals(new CachedLatLon(47.19286847859621, 8.79732714034617), i0.getPos()); // start of track
+        assertEquals(new CachedLatLon(47.196979885920882, 8.79541271366179), i1.getPos()); // exact match
+        assertEquals(new CachedLatLon(47.197319911792874, 8.792139580473304), i3.getPos()); // exact match
+        assertEquals(new CachedLatLon((47.197131179273129 + 47.197186248376966) / 2, (8.792974585667253 + 8.792809881269932) / 2),
+                i2.getPos()); // interpolated
+        assertEquals(new CachedLatLon(47.197568312311816, 8.790292849679897),
+                i4.getPos()); // interpolated between points without timestamp
+        assertEquals(new CachedLatLon(47.19819249585271, 8.78536943346262),
+                i5.getPos()); // tagged to last WP of first track, because closer and within 2 min (default setting)
+        assertEquals(new CachedLatLon(47.20138901844621, 8.774476982653141),
+                i6.getPos()); // tagged to first WP of second track, because closer and within 2 min (default setting)
+        assertFalse(ib.hasNewGpsData());
+        assertTrue(i0.hasNewGpsData() && i1.hasNewGpsData() && i2.hasNewGpsData() && i3.hasNewGpsData()
+                && i4.hasNewGpsData() && i5.hasNewGpsData() && i6.hasNewGpsData());
+        // First waypoint has no speed in matchGpxTrack(). Speed is calculated
+        // and not taken from GPX track.
+        assertEquals(null, ib.getSpeed());
+        assertEquals(null, i0.getSpeed());
+        assertEquals(Double.valueOf(11.675317966018756), i1.getSpeed(), 0.000001);
+        assertEquals(Double.valueOf(24.992418392716967), i2.getSpeed(), 0.000001);
+        assertEquals(Double.valueOf(27.307968754679223), i3.getSpeed(), 0.000001);
+        assertEquals(null, ib.getElevation());
+        assertEquals(null, i0.getElevation());
+        assertEquals(Double.valueOf(489.29), i1.getElevation(), 0.000001);
+        assertEquals(Double.valueOf((490.40 + 489.75) / 2), i2.getElevation(), 0.000001);
+        assertEquals(Double.valueOf(486.368333333), i3.getElevation(), 0.000001);
+        // interpolated elevation between trackpoints with interpolated timestamps
+        assertEquals(Double.valueOf(475.393978719), i4.getElevation(), 0.000001);
+        assertEquals(null, i5.getElevation());
+        assertEquals(null, i6.getElevation());
+
+        assertEquals(null, ib.getGpsTime());
+        assertEquals(DateUtils.fromString("2016:01:03 11:59:54"), i0.getGpsTime()); // original time is kept
+        assertEquals(DateUtils.fromString("2016:01:03 12:04:01"), i1.getGpsTime());
+        assertEquals(DateUtils.fromString("2016:01:03 12:04:57"), i2.getGpsTime());
+        assertEquals(DateUtils.fromString("2016:01:03 12:05:05"), i3.getGpsTime());
+
+        clearTmp(images);
+
+        // TEST #2: Disable all interpolation and tagging close to tracks. Only i1-i4 are tagged
+
+        s.putBoolean("geoimage.trk.tag", false);
+        s.putBoolean("geoimage.trk.int", false);
+        s.putBoolean("geoimage.seg.tag", false);
+        s.putBoolean("geoimage.seg.int", false);
+
+        assertEquals(4, GpxImageCorrelation.matchGpxTrack(images, gpx, 0, false));
+        assertEquals(null, ib.getPos());
+        assertEquals(null, i0.getPos());
+        assertEquals(new CachedLatLon(47.196979885920882, 8.79541271366179), i1.getPos());
+        assertEquals(new CachedLatLon((47.197131179273129 + 47.197186248376966) / 2, (8.792974585667253 + 8.792809881269932) / 2), i2.getPos());
+        assertEquals(new CachedLatLon(47.197319911792874, 8.792139580473304), i3.getPos());
+        assertEquals(new CachedLatLon(47.197568312311816, 8.790292849679897), i4.getPos());
+        assertEquals(null, i5.getPos());
+        assertEquals(null, i6.getPos());
+
+        clearTmp(images);
+
+        // TEST #3: Disable all interpolation and allow tagging within 1 minute of a track. i0-i5 are tagged.
+        // i6 will not be tagged, because it's 68 seconds away from the next waypoint in either direction
+
+        s.putBoolean("geoimage.trk.tag", true);
+        s.putBoolean("geoimage.trk.tag.time", true);
+        s.putInt("geoimage.trk.tag.time.val", 1);
+
+        s.putBoolean("geoimage.trk.int", false);
+        s.putBoolean("geoimage.seg.tag", false);
+        s.putBoolean("geoimage.seg.int", false);
+
+        assertEquals(6, GpxImageCorrelation.matchGpxTrack(images, gpx, 0, false));
+        assertEquals(null, ib.getPos());
+        assertEquals(new CachedLatLon(47.19286847859621, 8.79732714034617), i0.getPos());
+        assertEquals(new CachedLatLon(47.196979885920882, 8.79541271366179), i1.getPos());
+        assertEquals(new CachedLatLon((47.197131179273129 + 47.197186248376966) / 2, (8.792974585667253 + 8.792809881269932) / 2), i2.getPos());
+        assertEquals(new CachedLatLon(47.197319911792874, 8.792139580473304), i3.getPos());
+        assertEquals(new CachedLatLon(47.197568312311816, 8.790292849679897), i4.getPos());
+        assertEquals(new CachedLatLon(47.19819249585271, 8.78536943346262), i5.getPos());
+        assertEquals(null, i6.getPos());
+
+        clearTmp(images);
+
+        // TEST #4: Force tagging (parameter forceTags=true, no change of configuration). All images will be tagged
+        // i5-i6 will now be interpolated, therefore it will have an elevation and different coordinates than in tests above
+
+        assertEquals(8, GpxImageCorrelation.matchGpxTrack(images, gpx, 0, true));
+        assertEquals(new CachedLatLon(47.19286847859621, 8.79732714034617), ib.getPos());
+        assertEquals(new CachedLatLon(47.19286847859621, 8.79732714034617), i0.getPos());
+        assertEquals(new CachedLatLon(47.196979885920882, 8.79541271366179), i1.getPos());
+        assertEquals(new CachedLatLon((47.197131179273129 + 47.197186248376966) / 2, (8.792974585667253 + 8.792809881269932) / 2), i2.getPos());
+        assertEquals(new CachedLatLon(47.197319911792874, 8.792139580473304), i3.getPos());
+        assertEquals(new CachedLatLon(47.197568312311816, 8.790292849679897), i4.getPos());
+        assertEquals(new CachedLatLon(47.198845306804905, 8.783144918860685), i5.getPos()); // interpolated between tracks
+        assertEquals(new CachedLatLon(47.19985828931693, 8.77969308585768), i6.getPos()); // different values than in tests #1 and #3!
+
+        assertEquals(Double.valueOf(447.894014085), i5.getElevation(), 0.000001);
+        assertEquals(Double.valueOf(437.395070423), i6.getElevation(), 0.000001);
+
+        clearTmp(images);
+
+        // TEST #5: Force tagging (parameter forceTags=false, but configuration changed).
+        // Results same as #4
+
+        s.putBoolean("geoimage.trk.tag", true);
+        s.putBoolean("geoimage.trk.tag.time", false);
+        s.putBoolean("geoimage.trk.int", true);
+        s.putBoolean("geoimage.trk.int.time", false);
+        s.putBoolean("geoimage.trk.int.dist", false);
+        s.putBoolean("geoimage.seg.tag", false);
+        s.putBoolean("geoimage.seg.int", false);
+        s.putBoolean("geoimage.seg.tag.time", false);
+        s.putBoolean("geoimage.seg.int", true);
+        s.putBoolean("geoimage.seg.int.time", false);
+        s.putBoolean("geoimage.seg.int.dist", false);
+
+        assertEquals(8, GpxImageCorrelation.matchGpxTrack(images, gpx, 0, false));
+        assertEquals(new CachedLatLon(47.19286847859621, 8.79732714034617), ib.getPos());
+        assertEquals(new CachedLatLon(47.19286847859621, 8.79732714034617), i0.getPos());
+        assertEquals(new CachedLatLon(47.196979885920882, 8.79541271366179), i1.getPos());
+        assertEquals(new CachedLatLon((47.197131179273129 + 47.197186248376966) / 2, (8.792974585667253 + 8.792809881269932) / 2), i2.getPos());
+        assertEquals(new CachedLatLon(47.197319911792874, 8.792139580473304), i3.getPos());
+        assertEquals(new CachedLatLon(47.197568312311816, 8.790292849679897), i4.getPos());
+        assertEquals(new CachedLatLon(47.198845306804905, 8.783144918860685), i5.getPos());
+        assertEquals(new CachedLatLon(47.19985828931693, 8.77969308585768), i6.getPos());
+
+        assertEquals(Double.valueOf(447.894014085), i5.getElevation(), 0.000001);
+        assertEquals(Double.valueOf(437.395070423), i6.getElevation(), 0.000001);
+
+        clearTmp(images);
+
+        // TEST #6: Disable tagging but allow interpolation when tracks are less than 500m apart. i0-i4 are tagged.
+        // i5-i6 will not be tagged, because the tracks are 897m apart.
+        // not checking all the coordinates again, did that 5 times already, just the number of matched images
+
+        s.putBoolean("geoimage.trk.tag", false);
+        s.putBoolean("geoimage.trk.int", true);
+        s.putBoolean("geoimage.trk.int.time", false);
+        s.putBoolean("geoimage.trk.int.dist", true);
+        s.putInt("geoimage.trk.int.dist.val", 500);
+        s.putBoolean("geoimage.seg.tag", false);
+        s.putBoolean("geoimage.seg.int", false);
+
+        assertEquals(4, GpxImageCorrelation.matchGpxTrack(images, gpx, 0, false));
+        clearTmp(images);
+
+        // TEST #7: Disable tagging but allow interpolation when tracks are less than 1000m apart. i0-i6 are tagged.
+        // i5-i6 will be tagged, because the tracks are 897m apart.
+
+        s.putInt("geoimage.trk.int.dist.val", 1000);
+
+        assertEquals(6, GpxImageCorrelation.matchGpxTrack(images, gpx, 0, false));
+        clearTmp(images);
+
+        // TEST #8: Disable tagging but allow interpolation when tracks are less than 2 min apart. i0-i4 are tagged.
+        // i5-i6 will not be tagged, because the tracks are 2.5min apart.
+
+        s.putBoolean("geoimage.trk.tag", false);
+        s.putBoolean("geoimage.trk.int", true);
+        s.putBoolean("geoimage.trk.int.time", true);
+        s.putInt("geoimage.trk.int.time.val", 2);
+        s.putBoolean("geoimage.trk.int.dist", false);
+        s.putBoolean("geoimage.seg.tag", false);
+        s.putBoolean("geoimage.seg.int", false);
+
+        assertEquals(4, GpxImageCorrelation.matchGpxTrack(images, gpx, 0, false));
+        clearTmp(images);
+
+        // TEST #9: Disable tagging but allow interpolation when tracks are less than 3 min apart. i0-i6 are tagged.
+        // i5-i6 will be tagged, because the tracks are 2.5min apart.
+
+        s.putInt("geoimage.trk.int.time.val", 3);
+
+        assertEquals(6, GpxImageCorrelation.matchGpxTrack(images, gpx, 0, false));
+
+    }
+
+    private void clearTmp(List<GpxImageEntry> imgs) {
+        for (GpxImageEntry i : imgs) {
+            i.discardTmp();
+            i.createTmp();
+        }
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxOffsetTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxOffsetTest.java	(revision 14205)
+++ /trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxOffsetTest.java	(revision 14205)
@@ -0,0 +1,84 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.gpx;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.ParseException;
+
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.tools.date.DateUtils;
+import org.openstreetmap.josm.tools.date.DateUtilsTest;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Unit tests of {@link GpxTimeOffset} class.
+ */
+public class GpxOffsetTest {
+
+    /**
+     * Setup test.
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules();
+
+    /**
+     * Setup test.
+     */
+    @BeforeClass
+    public static void setUp() {
+        DateUtilsTest.setTimeZone(DateUtils.UTC);
+    }
+
+    /**
+     * Unit test of {@link GpxTimeOffset#formatOffset}.
+     */
+    @Test
+    public void testFormatOffset() {
+        assertEquals("0", GpxTimeOffset.seconds(0).formatOffset());
+        assertEquals("123", GpxTimeOffset.seconds(123).formatOffset());
+        assertEquals("-4242", GpxTimeOffset.seconds(-4242).formatOffset());
+        assertEquals("0.1", GpxTimeOffset.milliseconds(100).formatOffset());
+        assertEquals("0.120", GpxTimeOffset.milliseconds(120).formatOffset());
+        assertEquals("0.123", GpxTimeOffset.milliseconds(123).formatOffset());
+        assertEquals("1.2", GpxTimeOffset.milliseconds(1200).formatOffset());
+        assertEquals("1.234", GpxTimeOffset.milliseconds(1234).formatOffset());
+    }
+
+    /**
+     * Unit test of {@link GpxTimeOffset#parseOffset}.
+     * @throws ParseException in case of parsing error
+     */
+    @Test
+    public void testParseOffest() throws ParseException {
+        assertEquals(0, GpxTimeOffset.parseOffset("0").getSeconds());
+        assertEquals(4242L, GpxTimeOffset.parseOffset("4242").getSeconds());
+        assertEquals(-4242L, GpxTimeOffset.parseOffset("-4242").getSeconds());
+        assertEquals(0L, GpxTimeOffset.parseOffset("-0").getSeconds());
+        assertEquals(100L, GpxTimeOffset.parseOffset("0.1").getMilliseconds());
+        assertEquals(123L, GpxTimeOffset.parseOffset("0.123").getMilliseconds());
+        assertEquals(-42420L, GpxTimeOffset.parseOffset("-42.42").getMilliseconds());
+    }
+
+    /**
+     * Unit test of {@link GpxTimeOffset#splitOutTimezone}.
+     */
+    @Test
+    public void testSplitOutTimezone() {
+        assertEquals("+1:00", GpxTimeOffset.seconds(3602).splitOutTimezone().a.formatTimezone());
+        assertEquals("2", GpxTimeOffset.seconds(3602).splitOutTimezone().b.formatOffset());
+        assertEquals("-7:00", GpxTimeOffset.seconds(-7 * 3600 + 123).splitOutTimezone().a.formatTimezone());
+        assertEquals("123", GpxTimeOffset.seconds(-7 * 3600 + 123).splitOutTimezone().b.formatOffset());
+        assertEquals(1, GpxTimeOffset.seconds(35 * 3600 + 421).getDayOffset());
+        assertEquals(11 * 3600 + 421, GpxTimeOffset.seconds(35 * 3600 + 421).withoutDayOffset().getSeconds());
+        assertEquals("+11:00", GpxTimeOffset.seconds(35 * 3600 + 421).splitOutTimezone().a.formatTimezone());
+        assertEquals(86400 + 421, GpxTimeOffset.seconds(35 * 3600 + 421).splitOutTimezone().b.getSeconds());
+        assertEquals(421, GpxTimeOffset.seconds(35 * 3600 + 421).withoutDayOffset().splitOutTimezone().b.getSeconds());
+        assertEquals("+1:00", GpxTimeOffset.milliseconds(3602987).splitOutTimezone().a.formatTimezone());
+        assertEquals("2.987", GpxTimeOffset.milliseconds(3602987).splitOutTimezone().b.formatOffset());
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxTimezoneTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxTimezoneTest.java	(revision 14205)
+++ /trunk/test/unit/org/openstreetmap/josm/data/gpx/GpxTimezoneTest.java	(revision 14205)
@@ -0,0 +1,61 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.gpx;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.ParseException;
+
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+import org.openstreetmap.josm.tools.date.DateUtils;
+import org.openstreetmap.josm.tools.date.DateUtilsTest;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Unit tests of {@link GpxTimezone} class.
+ */
+public class GpxTimezoneTest {
+
+    /**
+     * Setup test.
+     */
+    @Rule
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules();
+
+    /**
+     * Setup test.
+     */
+    @BeforeClass
+    public static void setUp() {
+        DateUtilsTest.setTimeZone(DateUtils.UTC);
+    }
+
+    /**
+     * Unit test of {@link GpxTimezone#formatTimezone}.
+     */
+    @Test
+    public void testFormatTimezone() {
+        assertEquals("+1:00", new GpxTimezone(1).formatTimezone());
+        assertEquals("+6:30", new GpxTimezone(6.5).formatTimezone());
+        assertEquals("-6:30", new GpxTimezone(-6.5).formatTimezone());
+        assertEquals("+3:08", new GpxTimezone(Math.PI).formatTimezone());
+        assertEquals("+2:43", new GpxTimezone(Math.E).formatTimezone());
+    }
+
+    /**
+     * Unit test of {@link GpxTimezone#parseTimezone}.
+     * @throws ParseException in case of parsing error
+     */
+    @Test
+    public void testParseTimezone() throws ParseException {
+        assertEquals(1, GpxTimezone.parseTimezone("+01:00").getHours(), 1e-3);
+        assertEquals(1, GpxTimezone.parseTimezone("+1:00").getHours(), 1e-3);
+        assertEquals(1.5, GpxTimezone.parseTimezone("+01:30").getHours(), 1e-3);
+        assertEquals(11.5, GpxTimezone.parseTimezone("+11:30").getHours(), 1e-3);
+        assertEquals(-11.5, GpxTimezone.parseTimezone("-11:30").getHours(), 1e-3);
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java	(revision 14204)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImagesTest.java	(revision 14205)
@@ -3,8 +3,5 @@
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
-import java.util.Arrays;
 import java.util.Collections;
 
@@ -12,6 +9,7 @@
 import org.junit.Rule;
 import org.junit.Test;
-import org.openstreetmap.josm.data.coor.CachedLatLon;
 import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxTimeOffset;
+import org.openstreetmap.josm.data.gpx.GpxTimezone;
 import org.openstreetmap.josm.io.GpxReaderTest;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
@@ -43,61 +41,4 @@
 
     /**
-     * Tests matching of images to a GPX track.
-     * @throws Exception if the track cannot be parsed
-     */
-    @Test
-    public void testMatchGpxTrack() throws Exception {
-        final GpxData gpx = GpxReaderTest.parseGpxData("data_nodist/2094047.gpx");
-        assertEquals(4, gpx.tracks.size());
-        assertEquals(1, gpx.tracks.iterator().next().getSegments().size());
-        assertEquals(185, gpx.tracks.iterator().next().getSegments().iterator().next().getWayPoints().size());
-
-        final ImageEntry ib = new ImageEntry();
-        ib.setExifTime(DateUtils.fromString("2016:01:03 11:54:58")); // 5 minutes before start of GPX
-        ib.createTmp();
-        final ImageEntry i0 = new ImageEntry();
-        i0.setExifTime(DateUtils.fromString("2016:01:03 11:59:54")); // 4 sec before start of GPX
-        i0.createTmp();
-        final ImageEntry i1 = new ImageEntry();
-        i1.setExifTime(DateUtils.fromString("2016:01:03 12:04:01"));
-        i1.createTmp();
-        final ImageEntry i2 = new ImageEntry();
-        i2.setExifTime(DateUtils.fromString("2016:01:03 12:04:57"));
-        i2.createTmp();
-        final ImageEntry i3 = new ImageEntry();
-        i3.setExifTime(DateUtils.fromString("2016:01:03 12:05:05"));
-        i3.createTmp();
-
-        assertEquals(4, CorrelateGpxWithImages.matchGpxTrack(Arrays.asList(ib, i0, i1, i2, i3), gpx, 0));
-        assertEquals(new CachedLatLon(47.19286847859621, 8.79732714034617), i0.getPos()); // start of track
-        assertEquals(new CachedLatLon(47.196979885920882, 8.79541271366179), i1.getPos()); // exact match
-        assertEquals(new CachedLatLon(47.197319911792874, 8.792139580473304), i3.getPos()); // exact match
-        assertEquals(new CachedLatLon((47.197131179273129 + 47.197186248376966) / 2, (8.792974585667253 + 8.792809881269932) / 2),
-                i2.getPos()); // interpolated
-        assertFalse(ib.hasNewGpsData());
-        assertTrue(i0.hasNewGpsData());
-        assertTrue(i1.hasNewGpsData());
-        assertTrue(i2.hasNewGpsData());
-        assertTrue(i3.hasNewGpsData());
-        // First waypoint has no speed in matchGpxTrack(). Speed is calculated
-        // and not taken from GPX track.
-        assertEquals(null, ib.getSpeed());
-        assertEquals(null, i0.getSpeed());
-        assertEquals(Double.valueOf(11.675317966018756), i1.getSpeed(), 0.000001);
-        assertEquals(Double.valueOf(24.992418392716967), i2.getSpeed(), 0.000001);
-        assertEquals(Double.valueOf(27.307968754679223), i3.getSpeed(), 0.000001);
-        assertEquals(null, ib.getElevation());
-        assertEquals(Double.valueOf(471.86), i0.getElevation(), 0.000001);
-        assertEquals(Double.valueOf(489.29), i1.getElevation(), 0.000001);
-        assertEquals(Double.valueOf((490.40 + 489.75) / 2), i2.getElevation(), 0.000001);
-        assertEquals(Double.valueOf(486.368333333), i3.getElevation(), 0.000001);
-        assertEquals(null, ib.getGpsTime());
-        assertEquals(DateUtils.fromString("2016:01:03 11:59:54"), i0.getGpsTime()); // original time is kept
-        assertEquals(DateUtils.fromString("2016:01:03 12:04:01"), i1.getGpsTime());
-        assertEquals(DateUtils.fromString("2016:01:03 12:04:57"), i2.getGpsTime());
-        assertEquals(DateUtils.fromString("2016:01:03 12:05:05"), i3.getGpsTime());
-    }
-
-    /**
      * Tests automatic guessing of timezone/offset
      * @throws Exception if an error occurs
@@ -109,5 +50,5 @@
         i0.setExifTime(DateUtils.fromString("2016:01:03 11:59:54")); // 4 sec before start of GPX
         i0.createTmp();
-        assertEquals(Pair.create(Timezone.ZERO, Offset.seconds(-4)),
+        assertEquals(Pair.create(GpxTimezone.ZERO, GpxTimeOffset.seconds(-4)),
                 CorrelateGpxWithImages.autoGuess(Collections.singletonList(i0), gpx));
     }
Index: unk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/OffsetTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/OffsetTest.java	(revision 14204)
+++ 	(revision )
@@ -1,84 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.layer.geoimage;
-
-import static org.junit.Assert.assertEquals;
-
-import java.text.ParseException;
-
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
-import org.openstreetmap.josm.tools.date.DateUtils;
-import org.openstreetmap.josm.tools.date.DateUtilsTest;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Unit tests of {@link Offset} class.
- */
-public class OffsetTest {
-
-    /**
-     * Setup test.
-     */
-    @Rule
-    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
-    public JOSMTestRules test = new JOSMTestRules();
-
-    /**
-     * Setup test.
-     */
-    @BeforeClass
-    public static void setUp() {
-        DateUtilsTest.setTimeZone(DateUtils.UTC);
-    }
-
-    /**
-     * Unit test of {@link Offset#formatOffset}.
-     */
-    @Test
-    public void testFormatOffset() {
-        assertEquals("0", Offset.seconds(0).formatOffset());
-        assertEquals("123", Offset.seconds(123).formatOffset());
-        assertEquals("-4242", Offset.seconds(-4242).formatOffset());
-        assertEquals("0.1", Offset.milliseconds(100).formatOffset());
-        assertEquals("0.120", Offset.milliseconds(120).formatOffset());
-        assertEquals("0.123", Offset.milliseconds(123).formatOffset());
-        assertEquals("1.2", Offset.milliseconds(1200).formatOffset());
-        assertEquals("1.234", Offset.milliseconds(1234).formatOffset());
-    }
-
-    /**
-     * Unit test of {@link Offset#parseOffset}.
-     * @throws ParseException in case of parsing error
-     */
-    @Test
-    public void testParseOffest() throws ParseException {
-        assertEquals(0, Offset.parseOffset("0").getSeconds());
-        assertEquals(4242L, Offset.parseOffset("4242").getSeconds());
-        assertEquals(-4242L, Offset.parseOffset("-4242").getSeconds());
-        assertEquals(0L, Offset.parseOffset("-0").getSeconds());
-        assertEquals(100L, Offset.parseOffset("0.1").getMilliseconds());
-        assertEquals(123L, Offset.parseOffset("0.123").getMilliseconds());
-        assertEquals(-42420L, Offset.parseOffset("-42.42").getMilliseconds());
-    }
-
-    /**
-     * Unit test of {@link Offset#splitOutTimezone}.
-     */
-    @Test
-    public void testSplitOutTimezone() {
-        assertEquals("+1:00", Offset.seconds(3602).splitOutTimezone().a.formatTimezone());
-        assertEquals("2", Offset.seconds(3602).splitOutTimezone().b.formatOffset());
-        assertEquals("-7:00", Offset.seconds(-7 * 3600 + 123).splitOutTimezone().a.formatTimezone());
-        assertEquals("123", Offset.seconds(-7 * 3600 + 123).splitOutTimezone().b.formatOffset());
-        assertEquals(1, Offset.seconds(35 * 3600 + 421).getDayOffset());
-        assertEquals(11 * 3600 + 421, Offset.seconds(35 * 3600 + 421).withoutDayOffset().getSeconds());
-        assertEquals("+11:00", Offset.seconds(35 * 3600 + 421).splitOutTimezone().a.formatTimezone());
-        assertEquals(86400 + 421, Offset.seconds(35 * 3600 + 421).splitOutTimezone().b.getSeconds());
-        assertEquals(421, Offset.seconds(35 * 3600 + 421).withoutDayOffset().splitOutTimezone().b.getSeconds());
-        assertEquals("+1:00", Offset.milliseconds(3602987).splitOutTimezone().a.formatTimezone());
-        assertEquals("2.987", Offset.milliseconds(3602987).splitOutTimezone().b.formatOffset());
-    }
-}
Index: unk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/TimezoneTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/TimezoneTest.java	(revision 14204)
+++ 	(revision )
@@ -1,60 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.gui.layer.geoimage;
-
-import static org.junit.Assert.assertEquals;
-
-import java.text.ParseException;
-
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.openstreetmap.josm.testutils.JOSMTestRules;
-import org.openstreetmap.josm.tools.date.DateUtils;
-import org.openstreetmap.josm.tools.date.DateUtilsTest;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Unit tests of {@link Timezone} class.
- */
-public class TimezoneTest {
-
-    /**
-     * Setup test.
-     */
-    @Rule
-    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
-    public JOSMTestRules test = new JOSMTestRules();
-
-    /**
-     * Setup test.
-     */
-    @BeforeClass
-    public static void setUp() {
-        DateUtilsTest.setTimeZone(DateUtils.UTC);
-    }
-
-    /**
-     * Unit test of {@link Timezone#formatTimezone}.
-     */
-    @Test
-    public void testFormatTimezone() {
-        assertEquals("+1:00", new Timezone(1).formatTimezone());
-        assertEquals("+6:30", new Timezone(6.5).formatTimezone());
-        assertEquals("-6:30", new Timezone(-6.5).formatTimezone());
-        assertEquals("+3:08", new Timezone(Math.PI).formatTimezone());
-        assertEquals("+2:43", new Timezone(Math.E).formatTimezone());
-    }
-
-    /**
-     * Unit test of {@link Timezone#parseTimezone}.
-     * @throws ParseException in case of parsing error
-     */
-    @Test
-    public void testParseTimezone() throws ParseException {
-        assertEquals(1, Timezone.parseTimezone("+01:00").getHours(), 1e-3);
-        assertEquals(1, Timezone.parseTimezone("+1:00").getHours(), 1e-3);
-        assertEquals(1.5, Timezone.parseTimezone("+01:30").getHours(), 1e-3);
-        assertEquals(11.5, Timezone.parseTimezone("+11:30").getHours(), 1e-3);
-    }
-}
