Index: src/org/openstreetmap/josm/data/ImageData.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/data/ImageData.java b/src/org/openstreetmap/josm/data/ImageData.java
--- a/src/org/openstreetmap/josm/data/ImageData.java	(revision 18577)
+++ b/src/org/openstreetmap/josm/data/ImageData.java	(date 1666022956923)
@@ -10,6 +10,7 @@
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.gpx.GpxImageEntry;
 import org.openstreetmap.josm.data.osm.QuadBuckets;
+import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.geoimage.ImageEntry;
 import org.openstreetmap.josm.tools.ListenerList;
 
@@ -41,6 +42,7 @@
 
     private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create();
     private final QuadBuckets<ImageEntry> geoImages = new QuadBuckets<>();
+    private Layer layer;
 
     /**
      * Construct a new image container without images
@@ -375,6 +377,24 @@
         notifyImageUpdate();
     }
 
+    /**
+     * Set the layer for use with {@link org.openstreetmap.josm.gui.layer.geoimage.ImageViewerDialog#displayImages(Layer, List)}
+     * @param layer The layer to use for organization
+     * @since xxx
+     */
+    public void setLayer(Layer layer) {
+        this.layer = layer;
+    }
+
+    /**
+     * Get the layer that this data is associated with. May be {@code null}.
+     * @return The layer this data is associated with.
+     * @since xxx
+     */
+    public Layer getLayer() {
+        return this.layer;
+    }
+
     /**
      * Add a listener that listens to image data changes
      * @param listener the {@link ImageDataUpdateListener}
Index: src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
--- a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 18577)
+++ b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(date 1666022956938)
@@ -171,6 +171,7 @@
         this.gpxData = gpxData;
         this.useThumbs = useThumbs;
         this.data.addImageDataUpdateListener(this);
+        this.data.setLayer(this);
     }
 
     private final class ImageMouseListener extends MouseAdapter {
@@ -231,6 +232,7 @@
                     }
                 } else {
                     data.setSelectedImage(img);
+                    ImageViewerDialog.getInstance().displayImages(GeoImageLayer.this, Collections.singletonList(img));
                 }
             }
         }
@@ -521,9 +523,6 @@
      * Show current photo on map and in image viewer.
      */
     public void showCurrentPhoto() {
-        if (data.getSelectedImage() != null) {
-            clearOtherCurrentPhotos();
-        }
         updateBufferAndRepaint();
     }
 
@@ -628,18 +627,6 @@
         }
     }
 
-    /**
-     * Clears the currentPhoto of the other GeoImageLayer's. Otherwise there could be multiple selected photos.
-     */
-    private void clearOtherCurrentPhotos() {
-        for (GeoImageLayer layer:
-                 MainApplication.getLayerManager().getLayersOfType(GeoImageLayer.class)) {
-            if (layer != this) {
-                layer.getImageData().clearSelectedImage();
-            }
-        }
-    }
-
     /**
      * Registers a map mode for which the functionality of this layer should be available.
      * @param mapMode Map mode to be registered
Index: src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java
--- a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(revision 18577)
+++ b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java	(date 1666041661054)
@@ -8,34 +8,45 @@
 import java.awt.BorderLayout;
 import java.awt.Component;
 import java.awt.Dimension;
+import java.awt.FlowLayout;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
 import java.awt.event.KeyEvent;
 import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
 import java.io.IOException;
 import java.io.Serializable;
+import java.io.UncheckedIOException;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.time.format.FormatStyle;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.Future;
 import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.swing.AbstractAction;
 import javax.swing.Box;
+import javax.swing.ImageIcon;
 import javax.swing.JButton;
+import javax.swing.JComponent;
 import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JToggleButton;
 import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.actions.JosmAction;
 import org.openstreetmap.josm.data.ImageData;
@@ -52,9 +63,11 @@
 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.layer.MainLayerManager;
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings;
+import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.imagery.Vector3D;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.Logging;
@@ -68,6 +81,9 @@
 public final class ImageViewerDialog extends ToggleDialog implements LayerChangeListener, ActiveLayerChangeListener, ImageDataUpdateListener {
     private static final String GEOIMAGE_FILLER = marktr("Geoimage: {0}");
     private static final String DIALOG_FOLDER = "dialogs";
+    private static final String JOSM_LAYER_COMPONENT = "JOSM LAYER";
+    private static final String JOSM_LAYER_IMAGE_COMPONENT = "JOSM LAYER IImageEntry";
+    private static final String JOSM_LAYER_IMAGE_COMPONENT_DONE = JOSM_LAYER_IMAGE_COMPONENT + ".done";
 
     private final ImageryFilterSettings imageryFilterSettings = new ImageryFilterSettings();
 
@@ -120,6 +136,8 @@
     private JButton btnOpenExternal;
     private JButton btnDeleteFromDisk;
     private JToggleButton tbCentre;
+    /** The layer tab (used to select images when multiple layers provide images, makes for easy switching) */
+    private JPanel layers;
 
     private ImageViewerDialog() {
         super(tr("Geotagged Images"), "geoimage", tr("Display geotagged images"), Shortcut.registerShortcut("tools:geotagged",
@@ -152,6 +170,8 @@
 
     private void build() {
         JPanel content = new JPanel(new BorderLayout());
+        this.layers = new JPanel(new FlowLayout(FlowLayout.LEADING));
+        content.add(layers, BorderLayout.NORTH);
 
         content.add(imgDisplay, BorderLayout.CENTER);
 
@@ -213,6 +233,105 @@
         createLayout(content, false, null);
     }
 
+    private void updateLayers() {
+        if (this.tabbedEntries.size() <= 1) {
+            this.layers.setVisible(false);
+        } else {
+            final IImageEntry<?> current;
+            synchronized (this) {
+                current = this.currentEntry;
+            }
+            this.layers.setVisible(true);
+            // Get the old components
+            Map<Layer, JButton> oldLayers = Stream.of(this.layers.getComponents()).filter(JButton.class::isInstance).map(JButton.class::cast)
+                    .filter(component -> component.getClientProperty(JOSM_LAYER_COMPONENT) instanceof Layer
+                            || component.getClientProperty(JOSM_LAYER_COMPONENT) == null)
+                    .collect(Collectors.toMap(component -> (Layer) component.getClientProperty(JOSM_LAYER_COMPONENT), component -> component));
+            // Remove all old components
+            this.layers.removeAll();
+            List<JButton> layerButtons = new ArrayList<>(this.tabbedEntries.size());
+            MainLayerManager layerManager = MainApplication.getLayerManager();
+            List<Layer> invalidLayers = this.tabbedEntries.keySet().stream().filter(layer -> !layerManager.containsLayer(layer))
+                    .collect(Collectors.toList());
+            // `null` is for anything using the old methods, without telling us what layer it comes from.
+            invalidLayers.remove(null);
+            if (this.tabbedEntries.containsKey(null)) {
+                List<IImageEntry<?>> nullEntries = this.tabbedEntries.get(null);
+                JButton layerButton = createImageLayerButton(oldLayers, null, nullEntries);
+                layerButtons.add(layerButton);
+                layerButton.setEnabled(!nullEntries.contains(current));
+            }
+            // We need to do multiple calls to avoid ConcurrentModificationExceptions
+            invalidLayers.forEach(this.tabbedEntries::remove);
+            for (Map.Entry<Layer, List<IImageEntry<?>>> entry :
+                    this.tabbedEntries.entrySet().stream().filter(entry -> entry.getKey() != null)
+                            .sorted(Comparator.comparing(entry -> entry.getKey().getName())).collect(Collectors.toList())) {
+                JButton layerButton = createImageLayerButton(oldLayers, entry.getKey(), entry.getValue());
+                layerButtons.add(layerButton);
+                layerButton.setEnabled(!entry.getValue().contains(current));
+            }
+            layerButtons.forEach(this.layers::add);
+            int maxPreferredHeight = layerButtons.stream().mapToInt(JComponent::getHeight).max().orElse(Integer.MIN_VALUE);
+            if (maxPreferredHeight > 0) {
+                layerButtons.forEach(button -> button.setPreferredSize(new Dimension(button.getPreferredSize().width, maxPreferredHeight)));
+            }
+            this.layers.invalidate();
+        }
+    }
+
+    /**
+     * Create a button for a specific layer and its entries
+     *
+     * @param oldLayers A map of old layers to {@link JButton}s. If a layer is in the map, there is at least one old {@link JButton}.
+     * @param layer     The layer to switch to
+     * @param entries   The entries to display
+     * @return The button to use to switch to the specified layer
+     */
+    private JButton createImageLayerButton(Map<Layer, JButton> oldLayers, Layer layer, List<IImageEntry<?>> entries) {
+        final JButton layerButton = new JButton(tr(layer != null ? layer.getLabel() : "Default"));
+        layerButton.putClientProperty(JOSM_LAYER_COMPONENT, layer);
+        layerButton.addActionListener(new ImageActionListener(layer, entries));
+        if (!this.isDocked && entries.size() == 1) {
+            IImageEntry<?> entry = entries.get(0);
+            JButton old = oldLayers.get(layer);
+            Object saved = Optional.ofNullable(old).map(button -> button.getClientProperty(JOSM_LAYER_IMAGE_COMPONENT)).orElse(null);
+            boolean done = Optional.ofNullable(old).map(button -> button.getClientProperty(JOSM_LAYER_IMAGE_COMPONENT_DONE))
+                    .map(Boolean.TRUE::equals).orElse(false);
+            // Avoid reloading images if at all possible
+            if (!Objects.equals(entry, saved) || !done) {
+                ImageProvider.ImageSizes size = ImageProvider.ImageSizes.LARGEICON;
+                layerButton.putClientProperty(JOSM_LAYER_IMAGE_COMPONENT, entry);
+                layerButton.setIcon(ImageProvider.getEmpty(size));
+                layerButton.setPreferredSize(new Dimension(layerButton.getPreferredSize().width + 2,
+                        size.getAdjustedHeight()));
+                MainApplication.worker.submit(() -> loadImage(layerButton, entry));
+            } else {
+                layerButton.putClientProperty(JOSM_LAYER_IMAGE_COMPONENT, old.getClientProperty(JOSM_LAYER_IMAGE_COMPONENT));
+                layerButton.putClientProperty(JOSM_LAYER_IMAGE_COMPONENT_DONE, old.getClientProperty(JOSM_LAYER_IMAGE_COMPONENT_DONE));
+                layerButton.setIcon(old.getIcon());
+                layerButton.setPreferredSize(old.getPreferredSize());
+            }
+        }
+        return layerButton;
+    }
+
+    /**
+     * Load an image for a button
+     * @param layerButton The button to load the image into
+     * @param iImageEntry The image entry to load
+     */
+    private static void loadImage(JButton layerButton, IImageEntry<?> iImageEntry) {
+        try {
+            BufferedImage image = iImageEntry.read(ImageProvider.ImageSizes.LARGEICON.getImageDimension());
+            ImageIcon imageIcon = new ImageIcon(image);
+            GuiHelper.runInEDT(() -> layerButton.setIcon(imageIcon));
+            GuiHelper.runInEDT(layerButton::invalidate);
+            layerButton.putClientProperty(JOSM_LAYER_IMAGE_COMPONENT_DONE, true);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
     @Override
     public void destroy() {
         MainApplication.getLayerManager().removeActiveLayerChangeListener(this);
@@ -352,6 +471,26 @@
         }
     }
 
+    /**
+     * A listener that is called to change the viewing layer
+     */
+    private static class ImageActionListener implements ActionListener {
+
+        private final Layer layer;
+        private final List<IImageEntry<?>> entries;
+
+        ImageActionListener(Layer layer, List<IImageEntry<?>> entries) {
+            this.layer = layer;
+            this.entries = entries;
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            ImageViewerDialog.getInstance().displayImages(this.layer, this.entries);
+        }
+    }
+
+
     private class ImageFirstAction extends ImageRememberAction {
         ImageFirstAction() {
             super(null, new ImageProvider(DIALOG_FOLDER, "first"), tr("First"), Shortcut.registerShortcut(
@@ -551,6 +690,8 @@
         return wasEnabled;
     }
 
+    /** Used for tabbed panes */
+    private final transient Map<Layer, List<IImageEntry<?>>> tabbedEntries = new HashMap<>();
     private transient IImageEntry<? extends IImageEntry<?>> currentEntry;
 
     /**
@@ -578,6 +719,16 @@
      * @since 18246
      */
     public void displayImages(List<IImageEntry<?>> entries) {
+        this.displayImages((Layer) null, entries);
+    }
+
+    /**
+     * Displays images for the given layer.
+     * @param layer The layer to use for the tab ui
+     * @param entries image entries
+     * @since xxx
+     */
+    public void displayImages(Layer layer, List<IImageEntry<?>> entries) {
         boolean imageChanged;
         IImageEntry<?> entry = entries != null && entries.size() == 1 ? entries.get(0) : null;
 
@@ -598,6 +749,12 @@
             }
         }
 
+        if (entries == null || entries.isEmpty()) {
+            this.tabbedEntries.remove(layer);
+        } else {
+            this.tabbedEntries.put(layer, entries);
+        }
+        this.updateLayers();
         if (entry != null) {
             this.updateButtonsNonNullEntry(entry, imageChanged);
         } else {
@@ -730,6 +887,7 @@
         if (btnCollapse != null) {
             btnCollapse.setVisible(!isDocked);
         }
+        this.updateLayers();
     }
 
     /**
@@ -797,6 +955,19 @@
         }
     }
 
+    /**
+     * Reload the image. Call this if you load a low-resolution image first, and then get a high-resolution image, or
+     * if you know that the image has changed on disk.
+     * @since xxx
+     */
+    public void refresh() {
+        if (SwingUtilities.isEventDispatchThread()) {
+            this.updateButtonsNonNullEntry(currentEntry, true);
+        } else {
+            GuiHelper.runInEDT(this::refresh);
+        }
+    }
+
     private void registerOnLayer(Layer layer) {
         if (layer instanceof GeoImageLayer) {
             ((GeoImageLayer) layer).getImageData().addImageDataUpdateListener(this);
@@ -819,11 +990,11 @@
 
     @Override
     public void selectedImageChanged(ImageData data) {
-        displayImages(new ArrayList<>(data.getSelectedImages()));
+        displayImages(data.getLayer(), new ArrayList<>(data.getSelectedImages()));
     }
 
     @Override
     public void imageDataUpdated(ImageData data) {
-        displayImages(new ArrayList<>(data.getSelectedImages()));
+        displayImages(data.getLayer(), new ArrayList<>(data.getSelectedImages()));
     }
 }
