Index: src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(revision 6364)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java	(working copy)
@@ -1208,6 +1208,7 @@
                     curImg.tmp.setSpeed(speed);
                     curImg.tmp.setElevation(curElevation);
                     curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
+                    curImg.flagNewGpsData();
                     ret++;
                 }
                 i--;
@@ -1237,6 +1238,7 @@
                     curImg.tmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);
                 }
                 curImg.tmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
+                curImg.flagNewGpsData();
 
                 ret++;
             }
Index: src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 6364)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(working copy)
@@ -22,13 +22,17 @@
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
+import java.util.TimeZone;
 
 import javax.swing.Action;
 import javax.swing.Icon;
@@ -269,11 +273,45 @@
      * @param gpxLayer The associated GPX layer
      */
     public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer) {
-        super(tr("Geotagged Images"));
+        this(data, gpxLayer, null, false);
+    }
 
+    /**
+     * Constructs a new {@code GeoImageLayer}.
+     * @param data The list of images to display
+     * @param gpxLayer The associated GPX layer
+     * @param name Layer name
+     */
+    public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer,
+                         final String name) {
+        this(data, gpxLayer, name, false);
+    }
+
+    /**
+     * Constructs a new {@code GeoImageLayer}.
+     * @param data The list of images to display
+     * @param gpxLayer The associated GPX layer
+     * @param useThumbs Thumbnail display flag
+     */
+    public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer,
+                         boolean useThumbs) {
+        this(data, gpxLayer, null, useThumbs);
+    }
+
+    /**
+     * Constructs a new {@code GeoImageLayer}.
+     * @param data The list of images to display
+     * @param gpxLayer The associated GPX layer
+     * @param name Layer name
+     * @param useThumbs Thumbnail display flag
+     */
+    public GeoImageLayer(final List<ImageEntry> data, GpxLayer gpxLayer,
+                         final String name, boolean useThumbs) {
+        super(name != null ? name : tr("Geotagged Images"));
         Collections.sort(data);
         this.data = data;
         this.gpxLayer = gpxLayer;
+        this.useThumbs = useThumbs;
     }
 
     @Override
@@ -293,6 +331,7 @@
         entries.add(LayerListDialog.getInstance().createShowHideLayerAction());
         entries.add(LayerListDialog.getInstance().createDeleteLayerAction());
         entries.add(new RenameLayerAction(null, this));
+        //entries.add(LayerListDialog.getInstance().createMergeLayerAction(this));
         entries.add(SeparatorLayerAction.INSTANCE);
         entries.add(new CorrelateGpxWithImages(this));
         if (!menuAdditions.isEmpty()) {
@@ -402,6 +441,10 @@
         int height = mv.getHeight();
         Rectangle clip = g.getClipBounds();
         if (useThumbs) {
+            if (!thumbsLoaded) {
+                loadThumbs();
+            }
+
             if (null == offscreenBuffer || offscreenBuffer.getWidth() != width  // reuse the old buffer if possible
                     || offscreenBuffer.getHeight() != height) {
                 offscreenBuffer = new BufferedImage(width, height,
@@ -457,7 +500,7 @@
             if (e.getPos() != null) {
                 Point p = mv.getPoint(e.getPos());
 
-                if (e.thumbnail != null) {
+                if (useThumbs && e.thumbnail != null) {
                     Dimension d = scaledDimension(e.thumbnail);
                     g.setColor(new Color(128, 0, 0, 122));
                     g.fillRect(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
@@ -571,6 +614,51 @@
         } catch (Exception ex) { // (CompoundException and other exceptions, e.g. #5271)
             // Do nothing
         }
+
+        /* Time and date.
+         * We can have these cases:
+         * 1) GPS_TIME_STAMP not set -> date/time will be null
+         * 2) GPS_DATE_STAMP not set -> use EXIF date or set to default
+         * 3) GPS_TIME_STAMP and GPS_DATE_STAMP are set
+         */
+        int[] timeStampComps = dirGps.getIntArray(GpsDirectory.TAG_GPS_TIME_STAMP);
+        if (timeStampComps != null) {
+            int gpsHour = timeStampComps[0];
+            int gpsMin = timeStampComps[1];
+            int gpsSec = timeStampComps[2];
+            Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+
+            /* We have the time.  Next step is to check if the GPS
+             * date stamp is set.  dirGps.getString() always succeeds,
+             * but the return value might be null.
+             */
+            String dateStampStr = dirGps.getString(GpsDirectory.TAG_GPS_DATE_STAMP);
+            if (dateStampStr != null && dateStampStr.matches("^\\d+:\\d+:\\d+$")) {
+                String[] dateStampComps = dateStampStr.split(":");
+                cal.set(Calendar.YEAR, Integer.parseInt(dateStampComps[0]));
+                cal.set(Calendar.MONTH, Integer.parseInt(dateStampComps[1]) - 1);
+                cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStampComps[2]));
+            }
+            else {
+                /* No GPS date stamp in EXIF data.  Copy it from EXIF
+                 * time.  Date is not set if EXIF time is not
+                 * available.
+                 */
+                Date exifTime = e.getExifTime();
+                if (exifTime != null) {
+                    /* Time not set yet, so we can copy everything,
+                     * not just date.
+                     */
+                    cal.setTime(exifTime);
+                }
+            }
+
+            cal.set(Calendar.HOUR_OF_DAY, gpsHour);
+            cal.set(Calendar.MINUTE, gpsMin);
+            cal.set(Calendar.SECOND, gpsSec);
+
+            e.setExifGpsTime(cal.getTime());
+        }
     }
 
     public void showNextPhoto() {
@@ -667,6 +755,107 @@
         }
     }
 
+    /**
+     * Remove a photo from the list of images by index.
+     * @param idx Image index
+     */
+    public void removePhotoByIdx(int idx) {
+        if (idx >= 0 && data != null && idx < data.size()) {
+            data.remove(idx);
+        }
+    }
+
+    /**
+     * Return the image that matches the position of the mouse event.
+     * @param evt Mouse event
+     * @return Image at mouse position, or {@code null} if there is no
+     *         image at the mouse position
+     */
+    public ImageEntry getPhotoUnderMouse(MouseEvent evt) {
+        if (data != null) {
+            for (int idx = data.size() - 1; idx >= 0; --idx) {
+                ImageEntry img = data.get(idx);
+                if (img.getPos() == null) {
+                    continue;
+                }
+                Point p = Main.map.mapView.getPoint(img.getPos());
+                Rectangle r;
+                if (useThumbs && img.thumbnail != null) {
+                    Dimension d = scaledDimension(img.thumbnail);
+                    r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
+                } else {
+                    r = new Rectangle(p.x - icon.getIconWidth() / 2,
+                                      p.y - icon.getIconHeight() / 2,
+                                      icon.getIconWidth(),
+                                      icon.getIconHeight());
+                }
+                if (r.contains(evt.getPoint())) {
+                    return img;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Clear the currentPhoto, i.e. remove select marker, and
+     * optionally repaint.
+     * @param repaint Repaint flag
+     */
+    public void clearCurrentPhoto(boolean repaint) {
+        currentPhoto = -1;
+        if (repaint) {
+            updateBufferAndRepaint();
+        }
+    }
+
+    /**
+     * Clear the currentPhoto of the other GeoImageLayer's.  Otherwise
+     * there could be multiple selected photos.
+     */
+    private void clearOtherCurrentPhotos() {
+        for (GeoImageLayer layer:
+                 Main.map.mapView.getLayersOfType(GeoImageLayer.class)) {
+            if (layer != this) {
+                layer.clearCurrentPhoto(false);
+            }
+        }
+    }
+
+    private static List<MapMode> supportedMapModes = null;
+
+    /**
+     * Register a map mode for which the functionality of this layer
+     * should be available.
+     * @param mapMode Map mode to be registered
+     */
+    public static void registerSupportedMapMode(MapMode mapMode) {
+        if (supportedMapModes == null) {
+            supportedMapModes = new ArrayList<MapMode>();
+        }
+        supportedMapModes.add(mapMode);
+    }
+
+    /**
+     * Determine if the functionality of this layer is available in
+     * the specified map mode.  SelectAction is supported by default,
+     * other map modes can be registered.
+     * @param mapMode Map mode to be checked
+     * @return {@code true} if the map mode is supported,
+     *         {@code false} otherwise
+     */
+    private static final boolean isSupportedMapMode(MapMode mapMode) {
+        if (mapMode instanceof SelectAction) return true;
+        if (supportedMapModes != null) {
+            for (MapMode supmmode: supportedMapModes) {
+                if (mapMode == supmmode) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private MouseAdapter mouseAdapter = null;
     private MapModeChangeListener mapModeListener = null;
 
@@ -674,7 +863,7 @@
     public void hookUpMapView() {
         mouseAdapter = new MouseAdapter() {
             private final boolean isMapModeOk() {
-                return Main.map.mapMode == null || Main.map.mapMode instanceof SelectAction;
+                return Main.map.mapMode == null || isSupportedMapMode(Main.map.mapMode);
             }
             @Override public void mousePressed(MouseEvent e) {
 
@@ -698,7 +887,7 @@
                     }
                     Point p = Main.map.mapView.getPoint(e.getPos());
                     Rectangle r;
-                    if (e.thumbnail != null) {
+                    if (useThumbs && e.thumbnail != null) {
                         Dimension d = scaledDimension(e.thumbnail);
                         r = new Rectangle(p.x - d.width / 2, p.y - d.height / 2, d.width, d.height);
                     } else {
@@ -708,6 +897,7 @@
                                 icon.getIconHeight());
                     }
                     if (r.contains(ev.getPoint())) {
+                        clearOtherCurrentPhotos();
                         currentPhoto = i;
                         ImageViewerDialog.showImage(GeoImageLayer.this, e);
                         Main.map.repaint();
@@ -720,7 +910,7 @@
         mapModeListener = new MapModeChangeListener() {
             @Override
             public void mapModeChange(MapMode oldMapMode, MapMode newMapMode) {
-                if (newMapMode == null || (newMapMode instanceof org.openstreetmap.josm.actions.mapmode.SelectAction)) {
+                if (newMapMode == null || isSupportedMapMode(newMapMode)) {
                     Main.map.mapView.addMouseListener(mouseAdapter);
                 } else {
                     Main.map.mapView.removeMouseListener(mouseAdapter);
@@ -815,4 +1005,26 @@
     public void jumpToPreviousMarker() {
         showPreviousPhoto();
     }
+
+    /**
+     * Get the current thumbnail display status.  {@code true}:
+     * thumbnails are displayed, {@code false}: an icon is displayed
+     * instead of thumbnails.
+     * @return Current thumbnail display status
+     */
+    public boolean isUseThumbs() {
+        return useThumbs;
+    }
+
+    /**
+     * Enable or disable the display of thumbnails.  Does not update
+     * the display.
+     * @param useThumbs New thumbnail display status
+     */
+    public void setUseThumbs(boolean useThumbs) {
+        this.useThumbs = useThumbs;
+        if (useThumbs && !thumbsLoaded) {
+            loadThumbs();
+        }
+    }
 }
Index: src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java	(revision 6364)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/ImageEntry.java	(working copy)
@@ -17,9 +17,22 @@
     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 = false;
+    /** Temporary source of GPS time if not correlated with GPX track. */
+    private Date exifGpsTime = null;
     Image thumbnail;
 
-    /** The following values are computed from the correlation with the gpx track */
+    /**
+     * 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 second */
     private Double speed;
@@ -74,6 +87,9 @@
     public Date getExifTime() {
         return exifTime;
     }
+    public Date getExifGpsTime() {
+        return exifGpsTime;
+    }
     public LatLon getExifCoor() {
         return exifCoor;
     }
@@ -109,6 +125,9 @@
     public void setExifTime(Date exifTime) {
         this.exifTime = exifTime;
     }
+    public void setExifGpsTime(Date exifGpsTime) {
+        this.exifGpsTime = exifGpsTime;
+    }
     public void setGpsTime(Date gpsTime) {
         this.gpsTime = gpsTime;
     }
@@ -183,4 +202,42 @@
             " [tmp] pos = "+tmp.pos+"");
         return result;
     }
+
+    /**
+     * Indicate that the image has new GPS data.  That flag is used
+     * e.g. by the photo_geotagging plug-in to decide for which image
+     * file the EXIF GPS data needs to be (re-)written.
+     */
+    public void flagNewGpsData() {
+        isNewGpsData = true;
+        /* We need to set the GPS time to tell the system (mainly the
+         * photo_geotagging plug-in) that the GPS data has changed.
+         * Check for existing GPS time and take EXIF time otherwise.
+         * This can be removed once isNewGpsData is used instead of
+         * the GPS time.
+         */
+        if (gpsTime == null) {
+            Date gpsTime = getExifGpsTime();
+            if (gpsTime == null) {
+                gpsTime = getExifTime();
+                if (gpsTime == null) {
+                    /* Time still not set, take the current time. */
+                    gpsTime = new Date();
+                }
+            }
+            setGpsTime(gpsTime);
+        }
+        if (tmp != null && tmp.getGpsTime() == null) {
+            /* tmp.gpsTime overrides gpsTime, so we set it too. */
+            tmp.setGpsTime(getGpsTime());
+        }
+    }
+
+    /**
+     * Query whether the GPS data changed.
+     * @return {@code true} if GPS data changed, {@code false} otherwise
+     */
+    public boolean hasNewGpsData() {
+        return isNewGpsData;
+    }
 }
Index: src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(revision 6364)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(working copy)
@@ -213,9 +213,13 @@
     private ImageEntry currentEntry = null;
 
     public void displayImage(GeoImageLayer layer, ImageEntry entry) {
+        boolean imageChanged;
+
         synchronized(this) {
             // TODO: pop up image dialog but don't load image again
 
+            imageChanged = currentEntry != entry;
+
             if (centerView && Main.isDisplayingMapView() && entry != null && entry.getPos() != null) {
                 Main.map.mapView.zoomTo(entry.getPos());
             }
@@ -225,7 +229,12 @@
         }
 
         if (entry != null) {
-            imgDisplay.setImage(entry.getFile(), entry.getExifOrientation());
+            if (imageChanged) {
+                /* Set only if the image is new to preserve zoom and
+                 * position if the same image is redisplayed (e.g. to
+                 * update the OSD). */
+                imgDisplay.setImage(entry.getFile(), entry.getExifOrientation());
+            }
             setTitle("Geotagged Images" + (entry.getFile() != null ? " - " + entry.getFile().getName() : ""));
             StringBuffer osd = new StringBuffer(entry.getFile() != null ? entry.getFile().getName() : "");
             if (entry.getElevation() != null) {
@@ -301,4 +310,20 @@
     public boolean hasImage() {
         return currentEntry != null;
     }
+
+    /**
+     * Returns the currently displayed image.
+     * @return Currently displayed image or {@code null}
+     */
+    public static ImageEntry getCurrentImage() {
+        return getInstance().currentEntry;
+    }
+
+    /**
+     * Returns the layer associated with the image.
+     * @return Layer associated with the image
+     */
+    public static GeoImageLayer getCurrentLayer() {
+        return getInstance().currentLayer;
+    }
 }
Index: src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java	(revision 6364)
+++ src/org/openstreetmap/josm/io/session/GeoImageSessionExporter.java	(working copy)
@@ -65,6 +65,7 @@
         Element layerElem = support.createElement("layer");
         layerElem.setAttribute("type", "geoimage");
         layerElem.setAttribute("version", "0.1");
+        addAttr("show-thumbnails", Boolean.toString(layer.isUseThumbs()), layerElem, support);
 
         for (ImageEntry entry : layer.getImages()) {
 
@@ -100,6 +101,9 @@
             if (entry.getExifTime() != null) {
                 addAttr("exif-time", Long.toString(entry.getExifTime().getTime()), imgElem, support);
             }
+            if (entry.getExifGpsTime() != null) {
+                addAttr("exif-gps-time", Long.toString(entry.getExifGpsTime().getTime()), imgElem, support);
+            }
             if (entry.getExifCoor() != null) {
                 Element posElem = support.createElement("exif-coordinates");
                 posElem.setAttribute("lat", Double.toString(entry.getExifCoor().lat()));
@@ -109,6 +113,9 @@
             if (entry.getExifImgDir() != null) {
                 addAttr("exif-image-direction", entry.getExifImgDir().toString(), imgElem, support);
             }
+            if (entry.hasNewGpsData()) {
+                addAttr("is-new-gps-data", Boolean.toString(entry.hasNewGpsData()), imgElem, support);
+            }
 
             layerElem.appendChild(imgElem);
         }
Index: src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java
===================================================================
--- src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java	(revision 6364)
+++ src/org/openstreetmap/josm/io/session/GeoImageSessionImporter.java	(working copy)
@@ -31,6 +31,7 @@
 
         List<ImageEntry> entries = new ArrayList<ImageEntry>();
         NodeList imgNodes = elem.getChildNodes();
+        boolean useThumbs = false;
         for (int i=0; i<imgNodes.getLength(); ++i) {
             Node imgNode = imgNodes.item(i);
             if (imgNode.getNodeType() == Node.ELEMENT_NODE) {
@@ -55,16 +56,22 @@
                                     entry.setElevation(Double.parseDouble(attrElem.getTextContent()));
                                 } else if (attrElem.getTagName().equals("gps-time")) {
                                     entry.setGpsTime(new Date(Long.parseLong(attrElem.getTextContent())));
-                                } else if (attrElem.getTagName().equals("gps-orientation")) {
+                                } else if (attrElem.getTagName().equals("exif-orientation")) {
                                     entry.setExifOrientation(Integer.parseInt(attrElem.getTextContent()));
                                 } else if (attrElem.getTagName().equals("exif-time")) {
                                     entry.setExifTime(new Date(Long.parseLong(attrElem.getTextContent())));
+                                } else if (attrElem.getTagName().equals("exif-gps-time")) {
+                                    entry.setExifGpsTime(new Date(Long.parseLong(attrElem.getTextContent())));
                                 } else if (attrElem.getTagName().equals("exif-coordinates")) {
                                     double lat = Double.parseDouble(attrElem.getAttribute("lat"));
                                     double lon = Double.parseDouble(attrElem.getAttribute("lon"));
                                     entry.setExifCoor(new LatLon(lat, lon));
                                 } else if (attrElem.getTagName().equals("exif-image-direction")) {
                                     entry.setExifImgDir(Double.parseDouble(attrElem.getTextContent()));
+                                } else if (attrElem.getTagName().equals("is-new-gps-data")) {
+                                    if (Boolean.parseBoolean(attrElem.getTextContent())) {
+                                        entry.flagNewGpsData();
+                                    }
                                 }
                                 // TODO: handle thumbnail loading
                             } catch (NumberFormatException e) {
@@ -73,6 +80,8 @@
                         }
                     }
                     entries.add(entry);
+                } else if (imgElem.getTagName().equals("show-thumbnails")) {
+                    useThumbs = Boolean.parseBoolean(imgElem.getTextContent());
                 }
             }
         }
@@ -86,7 +95,7 @@
             }
         }
 
-        return new GeoImageLayer(entries, gpxLayer);
+        return new GeoImageLayer(entries, gpxLayer, useThumbs);
     }
 
 }
