Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideLayer.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideLayer.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideLayer.java	(revision 36262)
@@ -12,5 +12,7 @@
 import java.awt.geom.Line2D;
 import java.awt.image.BufferedImage;
+import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 import java.util.logging.Logger;
@@ -28,4 +30,5 @@
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
+import org.openstreetmap.josm.gui.draw.MapViewPath;
 import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
 import org.openstreetmap.josm.gui.layer.Layer;
@@ -279,9 +282,50 @@
         }
 
-        for (var imageAbs : data.getImages()) {
-            if (imageAbs.visible() && mv != null && mv.contains(mv.getPoint(imageAbs))) {
-                drawImageMarker(g, imageAbs);
-            }
-        }
+        if (mv != null && mv.getDist100Pixel() < 100) {
+            for (var imageAbs : data.search(box.toBBox())) {
+                if (imageAbs.visible() && mv.contains(mv.getPoint(imageAbs))) {
+                    drawImageMarker(g, imageAbs);
+                }
+            }
+        } else if (mv != null) {
+            // Generate sequence lines
+            final var sortedImages = new ArrayList<>(data.search(box.toBBox()));
+            sortedImages.sort(Comparator.naturalOrder());
+            final var imagesToPaint = new ArrayList<StreetsideImage>(sortedImages.size());
+            boolean containsSelected = false;
+            for (var image : sortedImages) {
+                if (!imagesToPaint.isEmpty() && imagesToPaint.getLast().greatCircleDistance(image) > 20) {
+                    paintSequence(g, mv, imagesToPaint, containsSelected);
+                    containsSelected = false;
+                    imagesToPaint.clear();
+                }
+                imagesToPaint.add(image);
+                if (image.equals(getData().getHighlightedImage())) {
+                    containsSelected = true;
+                }
+            }
+            if (!imagesToPaint.isEmpty()) {
+                paintSequence(g, mv, imagesToPaint, containsSelected);
+            }
+        }
+    }
+
+    /**
+     * Paint an artificial sequence
+     * @param g The graphics to paint on
+     * @param mv The current mapview
+     * @param images The images to use for the sequence
+     * @param containsSelected {@code true} if the sequence has a selected or highlighted image
+     */
+    private void paintSequence(Graphics2D g, MapView mv, List<StreetsideImage> images, boolean containsSelected) {
+        final var color = containsSelected ? StreetsideColorScheme.SEQ_HIGHLIGHTED
+                : StreetsideColorScheme.SEQ_UNSELECTED;
+        final var path = new MapViewPath(mv);
+        path.moveTo(images.get(0));
+        for (int i = 1; i < images.size(); i++) {
+            path.lineTo(images.get(i));
+        }
+        g.setColor(color);
+        g.draw(path);
     }
 
@@ -322,5 +366,6 @@
         // Paint image marker
         g.setColor(markerC);
-        g.fillOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS);
+        g.fillOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS,
+                2 * IMG_MARKER_RADIUS);
 
         // Paint highlight for selected or highlighted images
@@ -328,5 +373,6 @@
             g.setColor(Color.WHITE);
             g.setStroke(new BasicStroke(2));
-            g.drawOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS);
+            g.drawOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS,
+                    2 * IMG_MARKER_RADIUS);
         }
     }
@@ -441,8 +487,8 @@
 
     private record NearestImgToTargetComparator(StreetsideAbstractImage target) implements Comparator<StreetsideAbstractImage> {
-        @Override
-        public int compare(StreetsideAbstractImage img1, StreetsideAbstractImage img2) {
-            return (int) Math.signum(img1.greatCircleDistance(target) - img2.greatCircleDistance(target));
-        }
-    }
-}
+
+    @Override
+    public int compare(StreetsideAbstractImage img1, StreetsideAbstractImage img2) {
+        return (int) Math.signum(img1.greatCircleDistance(target) - img2.greatCircleDistance(target));
+    }
+}}
Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideWalkAction.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideWalkAction.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideWalkAction.java	(revision 36262)
@@ -41,6 +41,6 @@
      */
     public StreetsideWalkAction() {
-        super(tr("Walk mode"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true), tr("Walk mode"),
-                null, false, "streetsideWalk", true);
+        super(tr("Walk mode"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true),
+                tr("Walk mode"), null, false, "streetsideWalk", true);
     }
 
Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideZoomAction.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideZoomAction.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideZoomAction.java	(revision 36262)
@@ -31,5 +31,6 @@
      */
     public StreetsideZoomAction() {
-        super(tr("Zoom to selected image"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true),
+        super(tr("Zoom to selected image"),
+                new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true),
                 tr("Zoom to the currently selected Streetside image"), null, false, "mapillaryZoom", true);
     }
Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsidePreferenceSetting.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsidePreferenceSetting.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsidePreferenceSetting.java	(revision 36262)
@@ -134,6 +134,6 @@
     @Override
     public boolean ok() {
-        StreetsideProperties.DOWNLOAD_MODE
-                .put(DOWNLOAD_MODE.fromLabel(Objects.requireNonNull(downloadModeComboBox.getSelectedItem()).toString()).getPrefId());
+        StreetsideProperties.DOWNLOAD_MODE.put(DOWNLOAD_MODE
+                .fromLabel(Objects.requireNonNull(downloadModeComboBox.getSelectedItem()).toString()).getPrefId());
         StreetsideProperties.DISPLAY_HOUR.put(displayHour.isSelected());
         StreetsideProperties.TIME_FORMAT_24.put(format24.isSelected());
Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ImageInfoPanel.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ImageInfoPanel.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ImageInfoPanel.java	(revision 36262)
@@ -124,6 +124,6 @@
         final Collection<? extends OsmPrimitive> sel = event.getSelection();
         if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
-            LOGGER.log(Logging.LEVEL_DEBUG,
-                    "Selection changed. {0} primitives are selected.", sel == null ? 0 : sel.size());
+            LOGGER.log(Logging.LEVEL_DEBUG, "Selection changed. {0} primitives are selected.",
+                    sel == null ? 0 : sel.size());
         }
     }
Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/StreetsideViewerPanel.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/StreetsideViewerPanel.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/StreetsideViewerPanel.java	(revision 36262)
@@ -150,5 +150,5 @@
             if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
                 LOGGER.log(Logging.LEVEL_DEBUG, "Privacy link set for Streetside image {0} quadKey {1}",
-                        new Object[] {bubbleId, newImageId});
+                        new Object[] { bubbleId, newImageId });
             }
 
Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ThreeSixtyDegreeViewerPanel.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ThreeSixtyDegreeViewerPanel.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ThreeSixtyDegreeViewerPanel.java	(revision 36262)
@@ -126,9 +126,9 @@
         if (me.isSecondaryButtonDown()) { // JOSM viewer uses right-click for moving.
             cameraTransform.setRy(
-                    ((cameraTransform.ry.getAngle() - mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540)
-                            % 360 - 180); // +
+                    ((cameraTransform.ry.getAngle() - mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360
+                            - 180); // +
             cameraTransform.setRx(
-                    ((cameraTransform.rx.getAngle() + mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540)
-                            % 360 - 180); // -
+                    ((cameraTransform.rx.getAngle() + mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540) % 360
+                            - 180); // -
         } else if (me.isPrimaryButtonDown()) {
             final double z = camera.getTranslateZ();
Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/SequenceDownloadRunnable.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/SequenceDownloadRunnable.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/SequenceDownloadRunnable.java	(revision 36262)
@@ -14,4 +14,5 @@
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Callable;
 import java.util.function.Function;
 import java.util.logging.Level;
@@ -19,7 +20,6 @@
 
 import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.plugins.streetside.StreetsideData;
 import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
-import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
+import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL;
 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL.APIv3;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
@@ -34,8 +34,8 @@
  * Download an area
  */
-public final class SequenceDownloadRunnable extends BoundsDownloadRunnable {
+public final class SequenceDownloadRunnable extends BoundsDownloadRunnable implements Callable<List<StreetsideImage>> {
     private static final Logger LOG = Logger.getLogger(BoundsDownloadRunnable.class.getCanonicalName());
     private static final Function<Bounds, URL> URL_GEN = APIv3::searchStreetsideImages;
-    private final StreetsideData data;
+    private final List<StreetsideImage> images = new ArrayList<>(StreetsideURL.MAX_RETURN);
     private String logo;
     private String copyright;
@@ -43,10 +43,14 @@
     /**
      * Create a new downloader
-     * @param data The data to add to
      * @param bounds The bounds to download
      */
-    public SequenceDownloadRunnable(final StreetsideData data, final Bounds bounds) {
+    public SequenceDownloadRunnable(final Bounds bounds) {
         super(bounds);
-        this.data = data;
+    }
+
+    @Override
+    public List<StreetsideImage> call() {
+        this.run();
+        return this.images;
     }
 
@@ -69,5 +73,5 @@
             final long endTime = System.currentTimeMillis();
             LOG.log(Level.INFO, "Successfully loaded {0} Microsoft Streetside images in {1} seconds.",
-                    new Object[] {this.data.getImages().size(), (endTime - startTime) / 1000});
+                    new Object[] { this.images.size(), (endTime - startTime) / 1000 });
         } catch (DateTimeParseException dateTimeParseException) {
             // Added to debug #23658 -- a valid date string caused an exception
@@ -84,5 +88,6 @@
                 case "brandLogoUri" -> parseBrandLogoUri(parser);
                 case "copyright" -> parseCopyright(parser);
-                default -> { /* Do nothing for now */ }
+                default -> {
+                    /* Do nothing for now */ }
                 }
             }
@@ -106,10 +111,9 @@
             while (parser.hasNext() && parser.next() == JsonParser.Event.START_OBJECT) {
                 while (parser.hasNext() && parser.currentEvent() != JsonParser.Event.END_OBJECT) {
-                    if (parser.next() == JsonParser.Event.KEY_NAME
-                            && "resources".equals(parser.getString())) {
+                    if (parser.next() == JsonParser.Event.KEY_NAME && "resources".equals(parser.getString())) {
                         parser.next();
                         List<StreetsideImage> bubbleImages = new ArrayList<>();
                         parseResource(parser, bubbleImages);
-                        this.data.addAll(bubbleImages, true);
+                        this.images.addAll(bubbleImages);
                     }
                 }
@@ -157,12 +161,8 @@
                 final var imageHeight = node.getInt("imageHeight");
                 final var imageWidth = node.getInt("imageWidth");
-                final var image = new StreetsideImage(id, lat, lon, heading, pitch, roll, vintageStart,
-                        vintageEnd, this.logo, this.copyright, zoomMin, zoomMax, imageHeight, imageWidth,
-                        imageUrlSubdomains);
+                final var image = new StreetsideImage(id, lat, lon, heading, pitch, roll, vintageStart, vintageEnd,
+                        this.logo, this.copyright, zoomMin, zoomMax, imageHeight, imageWidth, imageUrlSubdomains);
                 bubbleImages.add(image);
                 LOG.info(() -> "Added image with id <" + image.id() + ">");
-                if (Boolean.TRUE.equals(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get())) {
-                    this.data.downloadSurroundingCubemaps(image);
-                }
             } else {
                 LOG.info(() -> MessageFormat.format("Unparsable JSON node object: {0}", node));
Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/StreetsideSquareDownloadRunnable.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/StreetsideSquareDownloadRunnable.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/StreetsideSquareDownloadRunnable.java	(revision 36262)
@@ -2,8 +2,14 @@
 package org.openstreetmap.josm.plugins.streetside.io.download;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.openstreetmap.gui.jmapviewer.TileXY;
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.plugins.streetside.StreetsideData;
 import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
 import org.openstreetmap.josm.plugins.streetside.gui.StreetsideMainDialog;
 import org.openstreetmap.josm.plugins.streetside.utils.PluginState;
+import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL;
 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideUtils;
 
@@ -30,5 +36,25 @@
 
         // Download basic sequence data synchronously
-        new SequenceDownloadRunnable(StreetsideLayer.getInstance().getData(), bounds).run();
+        // Note that Microsoft limits the downloaded data to 500 images. So we need to split.
+        // Start with z16 tiles
+        final var zoom = 16;
+        final var cancelled = new AtomicBoolean(false);
+        final var counter = new AtomicInteger();
+        final var data = StreetsideLayer.getInstance().getData();
+        DownloadRunnable.download(zoom, bounds, cancelled, counter, data);
+
+        while (counter.get() > 0) {
+            try {
+                synchronized (counter) {
+                    counter.wait(100);
+                }
+            } catch (InterruptedException e) {
+                cancelled.set(true);
+                Thread.currentThread().interrupt();
+            }
+        }
+        // if (Boolean.TRUE.equals(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get())) {
+        //     this.data.downloadSurroundingCubemaps(image);
+        // }
 
         if (Thread.interrupted()) {
@@ -44,3 +70,72 @@
         StreetsideMainDialog.getInstance().updateImage();
     }
+
+    private record DownloadRunnable(int zoom, int x, int y, AtomicBoolean cancelled, AtomicInteger counter,
+                                    StreetsideData data) implements Runnable {
+
+    @Override
+    public void run() {
+        try {
+            if (cancelled.get()) {
+                return;
+            }
+            final var newData = new SequenceDownloadRunnable(getBounds(zoom, x, y)).call();
+            // Microsoft limits API responses to 500 at this time. Split up the bounds.
+            // Rather unfortunately, there are no hints in the response for this. So we have to use
+            // size checking.
+            if (newData.size() >= StreetsideURL.MAX_RETURN) {
+                download(zoom + 1, getBounds(zoom, x, y), cancelled, counter, data);
+                return;
+            }
+            if (cancelled.get()) {
+                return;
+            }
+            synchronized (data) {
+                data.addAll(newData);
+            }
+        } finally {
+            counter.decrementAndGet();
+            synchronized (counter) {
+                counter.notifyAll();
+            }
+        }
+    }
+
+    static void download(int zoom, Bounds bounds, AtomicBoolean cancelled, AtomicInteger counter, StreetsideData data) {
+        // Yes, we want max lat since tiles start at upper-left.
+        final var min = getTile(zoom, bounds.getMaxLat(), bounds.getMinLon());
+        final var max = getTile(zoom, bounds.getMinLat(), bounds.getMaxLon());
+        for (int x = min.getXIndex(); x <= max.getXIndex(); x++) {
+            for (int y = min.getYIndex(); y <= max.getYIndex(); y++) {
+                counter.incrementAndGet();
+                Thread.ofVirtual().name("streetside-" + zoom + "/" + x + "/" + y)
+                        .start(new DownloadRunnable(zoom, x, y, cancelled, counter, data));
+            }
+        }
+    }
+
+    }
+
+    private static TileXY getTile(int zoom, double lat, double lon) {
+        final var x = (lon + 180) / 360 * Math.pow(2, zoom);
+        final var y = (1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI)
+                * Math.pow(2, zoom - 1);
+        return new TileXY(x, y);
+    }
+
+    private static Bounds getBounds(int zoom, int x, int y) {
+        final var minLon = getLon(zoom, x);
+        final var maxLon = getLon(zoom, x + 1);
+        final var maxLat = getLat(zoom, y);
+        final var minLat = getLat(zoom, y + 1);
+        return new Bounds(minLat, minLon, maxLat, maxLon);
+    }
+
+    private static double getLon(int zoom, int x) {
+        return 360 * x / Math.pow(2, zoom) - 180;
+    }
+
+    private static double getLat(int zoom, int y) {
+        return Math.atan(Math.sinh(Math.PI - 2 * Math.PI * y / Math.pow(2, zoom))) * 180 / Math.PI;
+    }
 }
Index: /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideURL.java
===================================================================
--- /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideURL.java	(revision 36261)
+++ /applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideURL.java	(revision 36262)
@@ -22,4 +22,6 @@
 public final class StreetsideURL {
 
+    /** The maximum API responses */
+    public static final int MAX_RETURN = 500;
     private static final Logger LOGGER = Logger.getLogger(StreetsideURL.class.getCanonicalName());
 
@@ -50,5 +52,5 @@
         final var ret = new StringBuilder(100);
         if (parts != null) {
-            ret.append("?count=500").append("&key=").append(StreetsideProperties.BING_MAPS_KEY.get());
+            ret.append("?count=").append(MAX_RETURN).append("&key=").append(StreetsideProperties.BING_MAPS_KEY.get());
             if (parts.containsKey("bbox")) {
                 final String[] bbox = parts.get("bbox").split(",");
