Ignore:
Timestamp:
2024-05-06T19:52:43+02:00 (8 months ago)
Author:
taylor.smock
Message:

Fetch more than 500 images in some areas, improve performance at lower zooms

Location:
applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/StreetsideLayer.java

    r36228 r36262  
    1212import java.awt.geom.Line2D;
    1313import java.awt.image.BufferedImage;
     14import java.util.ArrayList;
    1415import java.util.Comparator;
     16import java.util.List;
    1517import java.util.Objects;
    1618import java.util.logging.Logger;
     
    2830import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    2931import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
     32import org.openstreetmap.josm.gui.draw.MapViewPath;
    3033import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
    3134import org.openstreetmap.josm.gui.layer.Layer;
     
    279282        }
    280283
    281         for (var imageAbs : data.getImages()) {
    282             if (imageAbs.visible() && mv != null && mv.contains(mv.getPoint(imageAbs))) {
    283                 drawImageMarker(g, imageAbs);
    284             }
    285         }
     284        if (mv != null && mv.getDist100Pixel() < 100) {
     285            for (var imageAbs : data.search(box.toBBox())) {
     286                if (imageAbs.visible() && mv.contains(mv.getPoint(imageAbs))) {
     287                    drawImageMarker(g, imageAbs);
     288                }
     289            }
     290        } else if (mv != null) {
     291            // Generate sequence lines
     292            final var sortedImages = new ArrayList<>(data.search(box.toBBox()));
     293            sortedImages.sort(Comparator.naturalOrder());
     294            final var imagesToPaint = new ArrayList<StreetsideImage>(sortedImages.size());
     295            boolean containsSelected = false;
     296            for (var image : sortedImages) {
     297                if (!imagesToPaint.isEmpty() && imagesToPaint.getLast().greatCircleDistance(image) > 20) {
     298                    paintSequence(g, mv, imagesToPaint, containsSelected);
     299                    containsSelected = false;
     300                    imagesToPaint.clear();
     301                }
     302                imagesToPaint.add(image);
     303                if (image.equals(getData().getHighlightedImage())) {
     304                    containsSelected = true;
     305                }
     306            }
     307            if (!imagesToPaint.isEmpty()) {
     308                paintSequence(g, mv, imagesToPaint, containsSelected);
     309            }
     310        }
     311    }
     312
     313    /**
     314     * Paint an artificial sequence
     315     * @param g The graphics to paint on
     316     * @param mv The current mapview
     317     * @param images The images to use for the sequence
     318     * @param containsSelected {@code true} if the sequence has a selected or highlighted image
     319     */
     320    private void paintSequence(Graphics2D g, MapView mv, List<StreetsideImage> images, boolean containsSelected) {
     321        final var color = containsSelected ? StreetsideColorScheme.SEQ_HIGHLIGHTED
     322                : StreetsideColorScheme.SEQ_UNSELECTED;
     323        final var path = new MapViewPath(mv);
     324        path.moveTo(images.get(0));
     325        for (int i = 1; i < images.size(); i++) {
     326            path.lineTo(images.get(i));
     327        }
     328        g.setColor(color);
     329        g.draw(path);
    286330    }
    287331
     
    322366        // Paint image marker
    323367        g.setColor(markerC);
    324         g.fillOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS);
     368        g.fillOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS,
     369                2 * IMG_MARKER_RADIUS);
    325370
    326371        // Paint highlight for selected or highlighted images
     
    328373            g.setColor(Color.WHITE);
    329374            g.setStroke(new BasicStroke(2));
    330             g.drawOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS);
     375            g.drawOval(point.x - IMG_MARKER_RADIUS, point.y - IMG_MARKER_RADIUS, 2 * IMG_MARKER_RADIUS,
     376                    2 * IMG_MARKER_RADIUS);
    331377        }
    332378    }
     
    441487
    442488    private record NearestImgToTargetComparator(StreetsideAbstractImage target) implements Comparator<StreetsideAbstractImage> {
    443         @Override
    444         public int compare(StreetsideAbstractImage img1, StreetsideAbstractImage img2) {
    445             return (int) Math.signum(img1.greatCircleDistance(target) - img2.greatCircleDistance(target));
    446         }
    447     }
    448 }
     489
     490    @Override
     491    public int compare(StreetsideAbstractImage img1, StreetsideAbstractImage img2) {
     492        return (int) Math.signum(img1.greatCircleDistance(target) - img2.greatCircleDistance(target));
     493    }
     494}}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideWalkAction.java

    r36228 r36262  
    4141     */
    4242    public StreetsideWalkAction() {
    43         super(tr("Walk mode"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true), tr("Walk mode"),
    44                 null, false, "streetsideWalk", true);
     43        super(tr("Walk mode"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true),
     44                tr("Walk mode"), null, false, "streetsideWalk", true);
    4545    }
    4646
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/actions/StreetsideZoomAction.java

    r36228 r36262  
    3131     */
    3232    public StreetsideZoomAction() {
    33         super(tr("Zoom to selected image"), new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true),
     33        super(tr("Zoom to selected image"),
     34                new ImageProvider(StreetsidePlugin.LOGO).setSize(ImageSizes.DEFAULT).setOptional(true),
    3435                tr("Zoom to the currently selected Streetside image"), null, false, "mapillaryZoom", true);
    3536    }
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/StreetsidePreferenceSetting.java

    r36228 r36262  
    134134    @Override
    135135    public boolean ok() {
    136         StreetsideProperties.DOWNLOAD_MODE
    137                 .put(DOWNLOAD_MODE.fromLabel(Objects.requireNonNull(downloadModeComboBox.getSelectedItem()).toString()).getPrefId());
     136        StreetsideProperties.DOWNLOAD_MODE.put(DOWNLOAD_MODE
     137                .fromLabel(Objects.requireNonNull(downloadModeComboBox.getSelectedItem()).toString()).getPrefId());
    138138        StreetsideProperties.DISPLAY_HOUR.put(displayHour.isSelected());
    139139        StreetsideProperties.TIME_FORMAT_24.put(format24.isSelected());
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ImageInfoPanel.java

    r36228 r36262  
    124124        final Collection<? extends OsmPrimitive> sel = event.getSelection();
    125125        if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    126             LOGGER.log(Logging.LEVEL_DEBUG,
    127                     "Selection changed. {0} primitives are selected.", sel == null ? 0 : sel.size());
     126            LOGGER.log(Logging.LEVEL_DEBUG, "Selection changed. {0} primitives are selected.",
     127                    sel == null ? 0 : sel.size());
    128128        }
    129129    }
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/StreetsideViewerPanel.java

    r36228 r36262  
    150150            if (Boolean.TRUE.equals(StreetsideProperties.DEBUGING_ENABLED.get())) {
    151151                LOGGER.log(Logging.LEVEL_DEBUG, "Privacy link set for Streetside image {0} quadKey {1}",
    152                         new Object[] {bubbleId, newImageId});
     152                        new Object[] { bubbleId, newImageId });
    153153            }
    154154
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/gui/imageinfo/ThreeSixtyDegreeViewerPanel.java

    r36228 r36262  
    126126        if (me.isSecondaryButtonDown()) { // JOSM viewer uses right-click for moving.
    127127            cameraTransform.setRy(
    128                     ((cameraTransform.ry.getAngle() - mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540)
    129                             % 360 - 180); // +
     128                    ((cameraTransform.ry.getAngle() - mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360
     129                            - 180); // +
    130130            cameraTransform.setRx(
    131                     ((cameraTransform.rx.getAngle() + mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540)
    132                             % 360 - 180); // -
     131                    ((cameraTransform.rx.getAngle() + mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540) % 360
     132                            - 180); // -
    133133        } else if (me.isPrimaryButtonDown()) {
    134134            final double z = camera.getTranslateZ();
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/SequenceDownloadRunnable.java

    r36260 r36262  
    1414import java.util.List;
    1515import java.util.Objects;
     16import java.util.concurrent.Callable;
    1617import java.util.function.Function;
    1718import java.util.logging.Level;
     
    1920
    2021import org.openstreetmap.josm.data.Bounds;
    21 import org.openstreetmap.josm.plugins.streetside.StreetsideData;
    2222import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
    23 import org.openstreetmap.josm.plugins.streetside.utils.StreetsideProperties;
     23import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL;
    2424import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL.APIv3;
    2525import org.openstreetmap.josm.tools.JosmRuntimeException;
     
    3434 * Download an area
    3535 */
    36 public final class SequenceDownloadRunnable extends BoundsDownloadRunnable {
     36public final class SequenceDownloadRunnable extends BoundsDownloadRunnable implements Callable<List<StreetsideImage>> {
    3737    private static final Logger LOG = Logger.getLogger(BoundsDownloadRunnable.class.getCanonicalName());
    3838    private static final Function<Bounds, URL> URL_GEN = APIv3::searchStreetsideImages;
    39     private final StreetsideData data;
     39    private final List<StreetsideImage> images = new ArrayList<>(StreetsideURL.MAX_RETURN);
    4040    private String logo;
    4141    private String copyright;
     
    4343    /**
    4444     * Create a new downloader
    45      * @param data The data to add to
    4645     * @param bounds The bounds to download
    4746     */
    48     public SequenceDownloadRunnable(final StreetsideData data, final Bounds bounds) {
     47    public SequenceDownloadRunnable(final Bounds bounds) {
    4948        super(bounds);
    50         this.data = data;
     49    }
     50
     51    @Override
     52    public List<StreetsideImage> call() {
     53        this.run();
     54        return this.images;
    5155    }
    5256
     
    6973            final long endTime = System.currentTimeMillis();
    7074            LOG.log(Level.INFO, "Successfully loaded {0} Microsoft Streetside images in {1} seconds.",
    71                     new Object[] {this.data.getImages().size(), (endTime - startTime) / 1000});
     75                    new Object[] { this.images.size(), (endTime - startTime) / 1000 });
    7276        } catch (DateTimeParseException dateTimeParseException) {
    7377            // Added to debug #23658 -- a valid date string caused an exception
     
    8488                case "brandLogoUri" -> parseBrandLogoUri(parser);
    8589                case "copyright" -> parseCopyright(parser);
    86                 default -> { /* Do nothing for now */ }
     90                default -> {
     91                    /* Do nothing for now */ }
    8792                }
    8893            }
     
    106111            while (parser.hasNext() && parser.next() == JsonParser.Event.START_OBJECT) {
    107112                while (parser.hasNext() && parser.currentEvent() != JsonParser.Event.END_OBJECT) {
    108                     if (parser.next() == JsonParser.Event.KEY_NAME
    109                             && "resources".equals(parser.getString())) {
     113                    if (parser.next() == JsonParser.Event.KEY_NAME && "resources".equals(parser.getString())) {
    110114                        parser.next();
    111115                        List<StreetsideImage> bubbleImages = new ArrayList<>();
    112116                        parseResource(parser, bubbleImages);
    113                         this.data.addAll(bubbleImages, true);
     117                        this.images.addAll(bubbleImages);
    114118                    }
    115119                }
     
    157161                final var imageHeight = node.getInt("imageHeight");
    158162                final var imageWidth = node.getInt("imageWidth");
    159                 final var image = new StreetsideImage(id, lat, lon, heading, pitch, roll, vintageStart,
    160                         vintageEnd, this.logo, this.copyright, zoomMin, zoomMax, imageHeight, imageWidth,
    161                         imageUrlSubdomains);
     163                final var image = new StreetsideImage(id, lat, lon, heading, pitch, roll, vintageStart, vintageEnd,
     164                        this.logo, this.copyright, zoomMin, zoomMax, imageHeight, imageWidth, imageUrlSubdomains);
    162165                bubbleImages.add(image);
    163166                LOG.info(() -> "Added image with id <" + image.id() + ">");
    164                 if (Boolean.TRUE.equals(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get())) {
    165                     this.data.downloadSurroundingCubemaps(image);
    166                 }
    167167            } else {
    168168                LOG.info(() -> MessageFormat.format("Unparsable JSON node object: {0}", node));
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/io/download/StreetsideSquareDownloadRunnable.java

    r36228 r36262  
    22package org.openstreetmap.josm.plugins.streetside.io.download;
    33
     4import java.util.concurrent.atomic.AtomicBoolean;
     5import java.util.concurrent.atomic.AtomicInteger;
     6
     7import org.openstreetmap.gui.jmapviewer.TileXY;
    48import org.openstreetmap.josm.data.Bounds;
     9import org.openstreetmap.josm.plugins.streetside.StreetsideData;
    510import org.openstreetmap.josm.plugins.streetside.StreetsideLayer;
    611import org.openstreetmap.josm.plugins.streetside.gui.StreetsideMainDialog;
    712import org.openstreetmap.josm.plugins.streetside.utils.PluginState;
     13import org.openstreetmap.josm.plugins.streetside.utils.StreetsideURL;
    814import org.openstreetmap.josm.plugins.streetside.utils.StreetsideUtils;
    915
     
    3036
    3137        // Download basic sequence data synchronously
    32         new SequenceDownloadRunnable(StreetsideLayer.getInstance().getData(), bounds).run();
     38        // Note that Microsoft limits the downloaded data to 500 images. So we need to split.
     39        // Start with z16 tiles
     40        final var zoom = 16;
     41        final var cancelled = new AtomicBoolean(false);
     42        final var counter = new AtomicInteger();
     43        final var data = StreetsideLayer.getInstance().getData();
     44        DownloadRunnable.download(zoom, bounds, cancelled, counter, data);
     45
     46        while (counter.get() > 0) {
     47            try {
     48                synchronized (counter) {
     49                    counter.wait(100);
     50                }
     51            } catch (InterruptedException e) {
     52                cancelled.set(true);
     53                Thread.currentThread().interrupt();
     54            }
     55        }
     56        // if (Boolean.TRUE.equals(StreetsideProperties.PREDOWNLOAD_CUBEMAPS.get())) {
     57        //     this.data.downloadSurroundingCubemaps(image);
     58        // }
    3359
    3460        if (Thread.interrupted()) {
     
    4470        StreetsideMainDialog.getInstance().updateImage();
    4571    }
     72
     73    private record DownloadRunnable(int zoom, int x, int y, AtomicBoolean cancelled, AtomicInteger counter,
     74                                    StreetsideData data) implements Runnable {
     75
     76    @Override
     77    public void run() {
     78        try {
     79            if (cancelled.get()) {
     80                return;
     81            }
     82            final var newData = new SequenceDownloadRunnable(getBounds(zoom, x, y)).call();
     83            // Microsoft limits API responses to 500 at this time. Split up the bounds.
     84            // Rather unfortunately, there are no hints in the response for this. So we have to use
     85            // size checking.
     86            if (newData.size() >= StreetsideURL.MAX_RETURN) {
     87                download(zoom + 1, getBounds(zoom, x, y), cancelled, counter, data);
     88                return;
     89            }
     90            if (cancelled.get()) {
     91                return;
     92            }
     93            synchronized (data) {
     94                data.addAll(newData);
     95            }
     96        } finally {
     97            counter.decrementAndGet();
     98            synchronized (counter) {
     99                counter.notifyAll();
     100            }
     101        }
     102    }
     103
     104    static void download(int zoom, Bounds bounds, AtomicBoolean cancelled, AtomicInteger counter, StreetsideData data) {
     105        // Yes, we want max lat since tiles start at upper-left.
     106        final var min = getTile(zoom, bounds.getMaxLat(), bounds.getMinLon());
     107        final var max = getTile(zoom, bounds.getMinLat(), bounds.getMaxLon());
     108        for (int x = min.getXIndex(); x <= max.getXIndex(); x++) {
     109            for (int y = min.getYIndex(); y <= max.getYIndex(); y++) {
     110                counter.incrementAndGet();
     111                Thread.ofVirtual().name("streetside-" + zoom + "/" + x + "/" + y)
     112                        .start(new DownloadRunnable(zoom, x, y, cancelled, counter, data));
     113            }
     114        }
     115    }
     116
     117    }
     118
     119    private static TileXY getTile(int zoom, double lat, double lon) {
     120        final var x = (lon + 180) / 360 * Math.pow(2, zoom);
     121        final var y = (1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI)
     122                * Math.pow(2, zoom - 1);
     123        return new TileXY(x, y);
     124    }
     125
     126    private static Bounds getBounds(int zoom, int x, int y) {
     127        final var minLon = getLon(zoom, x);
     128        final var maxLon = getLon(zoom, x + 1);
     129        final var maxLat = getLat(zoom, y);
     130        final var minLat = getLat(zoom, y + 1);
     131        return new Bounds(minLat, minLon, maxLat, maxLon);
     132    }
     133
     134    private static double getLon(int zoom, int x) {
     135        return 360 * x / Math.pow(2, zoom) - 180;
     136    }
     137
     138    private static double getLat(int zoom, int y) {
     139        return Math.atan(Math.sinh(Math.PI - 2 * Math.PI * y / Math.pow(2, zoom))) * 180 / Math.PI;
     140    }
    46141}
  • applications/editors/josm/plugins/MicrosoftStreetside/src/org/openstreetmap/josm/plugins/streetside/utils/StreetsideURL.java

    r36228 r36262  
    2222public final class StreetsideURL {
    2323
     24    /** The maximum API responses */
     25    public static final int MAX_RETURN = 500;
    2426    private static final Logger LOGGER = Logger.getLogger(StreetsideURL.class.getCanonicalName());
    2527
     
    5052        final var ret = new StringBuilder(100);
    5153        if (parts != null) {
    52             ret.append("?count=500").append("&key=").append(StreetsideProperties.BING_MAPS_KEY.get());
     54            ret.append("?count=").append(MAX_RETURN).append("&key=").append(StreetsideProperties.BING_MAPS_KEY.get());
    5355            if (parts.containsKey("bbox")) {
    5456                final String[] bbox = parts.get("bbox").split(",");
Note: See TracChangeset for help on using the changeset viewer.