Index: src/org/openstreetmap/josm/data/IQuadBucketType.java
===================================================================
--- src/org/openstreetmap/josm/data/IQuadBucketType.java	(nonexistent)
+++ src/org/openstreetmap/josm/data/IQuadBucketType.java	(working copy)
@@ -0,0 +1,18 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+import org.openstreetmap.josm.data.osm.BBox;
+import org.openstreetmap.josm.data.osm.QuadBuckets;
+
+/**
+ * The minimum necessary interface to use {@link QuadBuckets}.
+ * @author Taylor Smock
+ * @since xxx
+ */
+public interface IQuadBucketType {
+    /**
+     * Fetches the bounding box of the primitive.
+     * @return Bounding box of the object
+     */
+    BBox getBBox();
+}
Index: src/org/openstreetmap/josm/data/ImageData.java
===================================================================
--- src/org/openstreetmap/josm/data/ImageData.java	(revision 17451)
+++ src/org/openstreetmap/josm/data/ImageData.java	(working copy)
@@ -11,6 +11,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.geoimage.ImageEntry;
 import org.openstreetmap.josm.tools.ListenerList;
 
@@ -41,6 +42,7 @@
     private final List<Integer> selectedImagesIndex = new ArrayList<>();
 
     private final ListenerList<ImageDataUpdateListener> listeners = ListenerList.create();
+    QuadBuckets<ImageEntry> geoImages = new QuadBuckets<>();
 
     /**
      * Construct a new image container without images
@@ -60,6 +62,7 @@
         } else {
             this.data = new ArrayList<>();
         }
+        this.geoImages.addAll(data);
         selectedImagesIndex.add(-1);
     }
 
@@ -85,6 +88,7 @@
      */
     public void mergeFrom(ImageData otherData) {
         data.addAll(otherData.getImages());
+        this.geoImages.addAll(otherData.getImages());
         Collections.sort(data);
 
         final ImageEntry selected = otherData.getSelectedImage();
@@ -153,6 +157,15 @@
     }
 
     /**
+     * Search for images in a bounds
+     * @param bounds The bounds to search
+     * @return images in the bounds
+     */
+    public Collection<ImageEntry> searchImages(Bounds bounds) {
+        return this.geoImages.search(bounds.toBBox());
+    }
+
+    /**
      * Select the next image of the sequence
      */
     public void selectNextImage() {
@@ -265,6 +278,7 @@
         }
         for (ImageEntry img: getSelectedImages()) {
             data.remove(img);
+            this.geoImages.remove(img);
         }
         if (selectedImagesIndex.get(0) >= data.size()) {
             setSelectedImageIndex(data.size() - 1);
@@ -290,6 +304,7 @@
      */
     public void removeImage(ImageEntry img) {
         data.remove(img);
+        this.geoImages.remove(img);
         notifyImageUpdate();
     }
 
@@ -300,6 +315,8 @@
      */
     public void updateImagePosition(ImageEntry img, LatLon newPos) {
         img.setPos(newPos);
+        this.geoImages.remove(img);
+        this.geoImages.add(img);
         afterImageUpdated(img);
     }
 
Index: src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java
===================================================================
--- src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java	(revision 17451)
+++ src/org/openstreetmap/josm/data/gpx/GpxImageEntry.java	(working copy)
@@ -8,8 +8,10 @@
 import java.util.Objects;
 import java.util.function.Consumer;
 
+import org.openstreetmap.josm.data.IQuadBucketType;
 import org.openstreetmap.josm.data.coor.CachedLatLon;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.BBox;
 import org.openstreetmap.josm.tools.ExifReader;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
@@ -28,7 +30,7 @@
  * Stores info about each image
  * @since 14205 (extracted from gui.layer.geoimage.ImageEntry)
  */
-public class GpxImageEntry implements Comparable<GpxImageEntry> {
+public class GpxImageEntry implements Comparable<GpxImageEntry>, IQuadBucketType {
     private File file;
     private Integer exifOrientation;
     private LatLon exifCoor;
@@ -539,6 +541,12 @@
         isNewGpsData = true;
    }
 
+    @Override
+    public BBox getBBox() {
+        // new BBox(LatLon) is null safe.
+        return new BBox(this.getExifCoor());
+    }
+
     /**
      * Remove the flag that indicates new GPS data.
      * The flag is cleared by a new GPS data consumer.
Index: src/org/openstreetmap/josm/data/osm/IPrimitive.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/IPrimitive.java	(revision 17451)
+++ src/org/openstreetmap/josm/data/osm/IPrimitive.java	(working copy)
@@ -5,6 +5,7 @@
 import java.util.List;
 import java.util.Map;
 
+import org.openstreetmap.josm.data.IQuadBucketType;
 import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
 import org.openstreetmap.josm.tools.LanguageInfo;
 
@@ -12,7 +13,7 @@
  * IPrimitive captures the common functions of {@link OsmPrimitive} and {@link PrimitiveData}.
  * @since 4098
  */
-public interface IPrimitive extends Tagged, PrimitiveId, Stylable, Comparable<IPrimitive> {
+public interface IPrimitive extends IQuadBucketType, Tagged, PrimitiveId, Stylable, Comparable<IPrimitive> {
 
     /**
      * Replies <code>true</code> if the object has been modified since it was loaded from
@@ -445,6 +446,7 @@
      * @return Bounding box of the object
      * @since 13764
      */
+    @Override
     BBox getBBox();
 
     /**
Index: src/org/openstreetmap/josm/data/osm/QuadBuckets.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/QuadBuckets.java	(revision 17451)
+++ src/org/openstreetmap/josm/data/osm/QuadBuckets.java	(working copy)
@@ -10,6 +10,7 @@
 import java.util.NoSuchElementException;
 import java.util.stream.IntStream;
 
+import org.openstreetmap.josm.data.IQuadBucketType;
 import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.coor.QuadTiling;
 import org.openstreetmap.josm.tools.Logging;
@@ -19,10 +20,10 @@
  * be removed and re-added.
  *
  * This class is (no longer) thread safe.
- * @param <T> type of primitives
- * @since 2165
+ * @param <T> type of object extending {@link IQuadBucketType}.
+ * @since 2165 ({@link IPrimitive} only), xxx for {@link IQuadBucketType}
  */
-public class QuadBuckets<T extends IPrimitive> implements Collection<T> {
+public class QuadBuckets<T extends IQuadBucketType> implements Collection<T> {
     private static final boolean CONSISTENCY_TESTING = false;
     private static final byte NW_INDEX = 1;
     private static final byte NE_INDEX = 3;
@@ -35,7 +36,7 @@
 
     private static final int MAX_OBJECTS_PER_NODE = 48;
 
-    static class QBLevel<T extends IPrimitive> extends BBox {
+    static class QBLevel<T extends IQuadBucketType> extends BBox {
         private final byte level;
         private final byte index;
         private final long quad;
Index: src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 17451)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(working copy)
@@ -37,9 +37,9 @@
 
 import org.openstreetmap.josm.actions.AutoScaleAction;
 import org.openstreetmap.josm.actions.RenameLayerAction;
-import org.openstreetmap.josm.actions.mapmode.SelectLassoAction;
 import org.openstreetmap.josm.actions.mapmode.MapMode;
 import org.openstreetmap.josm.actions.mapmode.SelectAction;
+import org.openstreetmap.josm.actions.mapmode.SelectLassoAction;
 import org.openstreetmap.josm.data.Bounds;
 import org.openstreetmap.josm.data.Data;
 import org.openstreetmap.josm.data.ImageData;
@@ -561,7 +561,7 @@
                 tempG.fillRect(0, 0, width, height);
                 tempG.setComposite(saveComp);
 
-                for (ImageEntry e : data.getImages()) {
+                for (ImageEntry e : data.searchImages(bounds)) {
                     paintImage(e, mv, clip, tempG);
                 }
                 for (ImageEntry img: this.data.getSelectedImages()) {
@@ -572,7 +572,7 @@
             }
             g.drawImage(offscreenBuffer, 0, 0, null);
         } else {
-            for (ImageEntry e : data.getImages()) {
+            for (ImageEntry e : data.searchImages(bounds)) {
                 if (e.getPos() == null) {
                     continue;
                 }
