Changeset 14300 in josm


Ignore:
Timestamp:
2018-10-06T22:01:03+02:00 (6 weeks ago)
Author:
Don-vip
Message:

fix #16809 - SlippyMapBBoxChooser: include tile sources from current layers (patch by ris, modified)

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java

    r14273 r14300  
    1616import java.util.Collections;
    1717import java.util.HashMap;
    18 import java.util.HashSet;
     18import java.util.LinkedHashMap;
    1919import java.util.List;
    2020import java.util.Map;
    21 import java.util.Set;
    2221import java.util.concurrent.CopyOnWriteArrayList;
    2322import java.util.concurrent.TimeUnit;
     23import java.util.stream.Collectors;
    2424
    2525import javax.swing.ButtonModel;
     
    5353import org.openstreetmap.josm.gui.MainApplication;
    5454import org.openstreetmap.josm.gui.layer.AbstractCachedTileSourceLayer;
     55import org.openstreetmap.josm.gui.layer.ImageryLayer;
    5556import org.openstreetmap.josm.gui.layer.MainLayerManager;
    5657import org.openstreetmap.josm.gui.layer.TMSLayer;
     
    6162 * This panel displays a map and lets the user chose a {@link BBox}.
    6263 */
    63 public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, ChangeListener, MainLayerManager.ActiveLayerChangeListener {
    64 
     64public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, ChangeListener,
     65    MainLayerManager.ActiveLayerChangeListener, MainLayerManager.LayerChangeListener {
    6566    /**
    6667     * A list of tile sources that can be used for displaying the map.
     
    7677
    7778    /**
    78      * TMS TileSource provider for the slippymap chooser
    79      */
    80     public static class TMSTileSourceProvider implements TileSourceProvider {
    81         private static final Set<String> existingSlippyMapUrls = new HashSet<>();
    82         static {
    83             // Urls that already exist in the slippymap chooser and shouldn't be copied from TMS layer list
    84             existingSlippyMapUrls.add("https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png");      // Mapnik
    85         }
     79     * TileSource provider for the slippymap chooser.
     80     * @since 14300
     81     */
     82    public abstract static class AbstractImageryInfoBasedTileSourceProvider implements TileSourceProvider {
     83        /**
     84         * Returns the list of imagery infos backing tile sources.
     85         * @return the list of imagery infos backing tile sources
     86         */
     87        public abstract List<ImageryInfo> getImageryInfos();
    8688
    8789        @Override
     
    8991            if (!TMSLayer.PROP_ADD_TO_SLIPPYMAP_CHOOSER.get()) return Collections.<TileSource>emptyList();
    9092            List<TileSource> sources = new ArrayList<>();
    91             for (ImageryInfo info : ImageryLayerInfo.instance.getLayers()) {
    92                 if (existingSlippyMapUrls.contains(info.getUrl())) {
    93                     continue;
    94                 }
     93            for (ImageryInfo info : this.getImageryInfos()) {
    9594                try {
    9695                    TileSource source = TMSLayer.getTileSourceStatic(info);
     
    112111
    113112    /**
     113     * TileSource provider for the slippymap chooser - providing sources from imagery sources menu
     114     * @since 14300
     115     */
     116    public static class TMSTileSourceProvider extends AbstractImageryInfoBasedTileSourceProvider {
     117        @Override
     118        public List<ImageryInfo> getImageryInfos() {
     119            return ImageryLayerInfo.instance.getLayers();
     120        }
     121    }
     122
     123    /**
     124     * TileSource provider for the slippymap chooser - providing sources from current layers
     125     * @since 14300
     126     */
     127    public static class CurrentLayersTileSourceProvider extends AbstractImageryInfoBasedTileSourceProvider {
     128        @Override
     129        public List<ImageryInfo> getImageryInfos() {
     130            return MainApplication.getLayerManager().getLayers().stream().filter(
     131                layer -> layer instanceof ImageryLayer
     132            ).map(
     133                layer -> ((ImageryLayer) layer).getInfo()
     134            ).collect(Collectors.toList());
     135        }
     136    }
     137
     138    /**
    114139     * Plugins that wish to add custom tile sources to slippy map choose should call this method
    115140     * @param tileSourceProvider new tile source provider
     
    123148        addTileSourceProvider(() -> Arrays.<TileSource>asList(new OsmTileSource.Mapnik()));
    124149        addTileSourceProvider(new TMSTileSourceProvider());
     150        addTileSourceProvider(new CurrentLayersTileSourceProvider());
    125151    }
    126152
     
    178204        setMaxTilesInMemory(Config.getPref().getInt("slippy_map_chooser.max_tiles", 1000));
    179205
    180         List<TileSource> tileSources = getAllTileSources();
     206        List<TileSource> tileSources = new ArrayList<>(getAllTileSources().values());
    181207
    182208        this.showDownloadAreaButtonModel = new JToggleButton.ToggleButtonModel();
     
    211237    }
    212238
    213     private static List<TileSource> getAllTileSources() {
    214         List<TileSource> tileSources = new ArrayList<>();
    215         for (TileSourceProvider provider: providers) {
    216             tileSources.addAll(provider.getTileSources());
    217         }
    218         return tileSources;
     239    private static LinkedHashMap<String, TileSource> getAllTileSources() {
     240        // using a LinkedHashMap of <id, TileSource> to retain ordering but provide deduplication
     241        return providers.stream().flatMap(
     242            provider -> provider.getTileSources().stream()
     243        ).collect(Collectors.toMap(
     244            TileSource::getId,
     245            ts -> ts,
     246            (oldTs, newTs) -> oldTs,
     247            LinkedHashMap::new
     248        ));
    219249    }
    220250
     
    359389        this.setTileSource(tileSource);
    360390        PROP_MAPSTYLE.put(tileSource.getName()); // TODO Is name really unique?
    361         if (this.iSourceButton.getCurrentSource() != tileSource) { // prevent infinite recursion
    362             this.iSourceButton.setCurrentMap(tileSource);
    363         }
     391
     392        // we need to refresh the tile sources in case the deselected source should no longer be present
     393        // (and only remained there because its removal was deferred while the source was still the
     394        // selected one). this should also have the effect of propagating the new selection to the
     395        // iSourceButton & menu: it attempts to re-select the current source when rebuilding its menu.
     396        this.refreshTileSources();
    364397    }
    365398
     
    417450     */
    418451    public final void refreshTileSources() {
    419         iSourceButton.setSources(getAllTileSources());
    420     }
     452        final LinkedHashMap<String, TileSource> newTileSources = getAllTileSources();
     453        final TileSource currentTileSource = this.getTileController().getTileSource();
     454
     455        // re-add the currently active TileSource to prevent inconsistent display of menu
     456        newTileSources.putIfAbsent(currentTileSource.getId(), currentTileSource);
     457
     458        this.iSourceButton.setSources(new ArrayList<>(newTileSources.values()));
     459    }
     460
     461    @Override
     462    public void layerAdded(MainLayerManager.LayerAddEvent e) {
     463        if (e.getAddedLayer() instanceof ImageryLayer) {
     464            this.refreshTileSources();
     465        }
     466    }
     467
     468    @Override
     469    public void layerRemoving(MainLayerManager.LayerRemoveEvent e) {
     470        if (e.getRemovedLayer() instanceof ImageryLayer) {
     471            this.refreshTileSources();
     472        }
     473    }
     474
     475    @Override
     476    public void layerOrderChanged(MainLayerManager.LayerOrderChangeEvent e) {}
    421477}
  • trunk/src/org/openstreetmap/josm/gui/bbox/SourceButton.java

    r12962 r14300  
    8282
    8383            // attempt to initialize button group matching current state of slippyMapBBoxChooser
    84             buttonModel.setSelected(this.slippyMapBBoxChooser.getTileController().getTileSource() == ts);
     84            buttonModel.setSelected(this.slippyMapBBoxChooser.getTileController().getTileSource().getId().equals(ts.getId()));
    8585        }
    8686
  • trunk/src/org/openstreetmap/josm/gui/dialogs/MinimapDialog.java

    r12630 r14300  
    3838        slippyMap.setSizeButtonVisible(false);
    3939        slippyMap.addPropertyChangeListener(BBoxChooser.BBOX_PROP, this);
     40        MainApplication.getLayerManager().addLayerChangeListener(slippyMap);
    4041    }
    4142
  • trunk/test/unit/org/openstreetmap/josm/gui/dialogs/MinimapDialogTest.java

    r14201 r14300  
    33
    44import static java.util.concurrent.TimeUnit.MILLISECONDS;
     5import static org.junit.Assert.assertArrayEquals;
    56import static org.junit.Assert.assertEquals;
    67import static org.junit.Assert.assertFalse;
     
    1415import java.awt.event.ComponentEvent;
    1516import java.awt.image.BufferedImage;
     17import java.util.ArrayList;
    1618import java.util.Arrays;
    1719import java.util.Map;
     
    3133import org.openstreetmap.josm.data.DataSource;
    3234import org.openstreetmap.josm.data.osm.DataSet;
     35import org.openstreetmap.josm.data.imagery.ImageryInfo;
     36import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
    3337import org.openstreetmap.josm.data.projection.ProjectionRegistry;
    3438import org.openstreetmap.josm.data.projection.Projections;
     
    3741import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
    3842import org.openstreetmap.josm.gui.bbox.SourceButton;
     43import org.openstreetmap.josm.gui.layer.ImageryLayer;
    3944import org.openstreetmap.josm.gui.layer.LayerManagerTest.TestLayer;
    4045import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     
    128133    }
    129134
     135    protected void assertSourceLabelsVisible(final String... labels) {
     136        GuiHelper.runInEDTAndWaitWithException(() -> {
     137            final ArrayList<String> menuLabels = new ArrayList<>();
     138            final JPopupMenu menu = this.sourceButton.getPopupMenu();
     139            for (Component c: menu.getComponents()) {
     140                if (c instanceof JPopupMenu.Separator) {
     141                    break;
     142                }
     143                menuLabels.add(((JMenuItem) c).getText());
     144            }
     145
     146            assertArrayEquals(
     147                labels,
     148                menuLabels.toArray()
     149            );
     150        });
     151    }
     152
    130153    private MinimapDialog minimap;
    131154    private SlippyMapBBoxChooser slippyMap;
     
    218241
    219242        assertEquals("Green Tiles", Config.getPref().get("slippy_map_chooser.mapstyle", "Fail"));
     243    }
     244
     245    /**
     246     * Tests that the apparently-selected TileSource survives the tile sources being refreshed.
     247     * @throws Exception if any error occurs
     248     */
     249    @Test
     250    public void testRefreshSourcesRetainsSelection() throws Exception {
     251        // relevant prefs starting out empty, should choose the first source and have shown download area enabled
     252        // (not that there's a data layer for it to use)
     253
     254        this.setUpMiniMap();
     255
     256        this.clickSourceMenuItemByLabel("Magenta Tiles");
     257        this.assertSingleSelectedSourceLabel("Magenta Tiles");
     258
     259        // call paint to trigger new tile fetch
     260        this.paintSlippyMap();
     261
     262        Awaitility.await().atMost(1000, MILLISECONDS).until(this.slippyMapTasksFinished);
     263
     264        this.paintSlippyMap();
     265
     266        assertEquals(0xffff00ff, paintedSlippyMap.getRGB(0, 0));
     267
     268        this.slippyMap.refreshTileSources();
     269
     270        this.assertSingleSelectedSourceLabel("Magenta Tiles");
     271
     272        // call paint to trigger new tile fetch
     273        this.paintSlippyMap();
     274
     275        Awaitility.await().atMost(1000, MILLISECONDS).until(this.slippyMapTasksFinished);
     276
     277        this.paintSlippyMap();
     278
     279        assertEquals(0xffff00ff, paintedSlippyMap.getRGB(0, 0));
     280    }
     281
     282    /**
     283     * Tests that the currently selected source being removed from ImageryLayerInfo will remain present and
     284     * selected in the source menu even after the tile sources have been refreshed.
     285     * @throws Exception if any error occurs
     286     */
     287    @Test
     288    public void testRemovedSourceStillSelected() throws Exception {
     289        // relevant prefs starting out empty, should choose the first source and have shown download area enabled
     290        // (not that there's a data layer for it to use)
     291
     292        this.setUpMiniMap();
     293
     294        this.clickSourceMenuItemByLabel("Green Tiles");
     295
     296        ImageryLayerInfo.instance.remove(
     297            ImageryLayerInfo.instance.getLayers().stream().filter(i -> i.getName().equals("Green Tiles")).findAny().get()
     298        );
     299
     300        this.assertSingleSelectedSourceLabel("Green Tiles");
     301
     302        this.slippyMap.refreshTileSources();
     303
     304        this.assertSingleSelectedSourceLabel("Green Tiles");
     305
     306        // call paint to trigger new tile fetch
     307        this.paintSlippyMap();
     308
     309        Awaitility.await().atMost(1000, MILLISECONDS).until(this.slippyMapTasksFinished);
     310
     311        this.paintSlippyMap();
     312
     313        assertEquals(0xff00ff00, paintedSlippyMap.getRGB(0, 0));
     314    }
     315
     316    /**
     317     * Tests the tile source list includes sources only present in the LayerManager
     318     * @throws Exception if any error occurs
     319     */
     320    @Test
     321    public void testTileSourcesFromCurrentLayers() throws Exception {
     322        // relevant prefs starting out empty, should choose the first (ImageryLayerInfo) source and have shown download area enabled
     323        // (not that there's a data layer for it to use)
     324
     325        final ImageryInfo magentaTilesInfo = ImageryLayerInfo.instance.getLayers().stream().filter(
     326            i -> i.getName().equals("Magenta Tiles")
     327        ).findAny().get();
     328        final ImageryInfo blackTilesInfo = ImageryLayerInfo.instance.getLayers().stream().filter(
     329            i -> i.getName().equals("Black Tiles")
     330        ).findAny().get();
     331
     332        // first we will remove "Magenta Tiles" from ImageryLayerInfo
     333        ImageryLayerInfo.instance.remove(magentaTilesInfo);
     334
     335        this.setUpMiniMap();
     336
     337        assertSourceLabelsVisible(
     338            "White Tiles",
     339            "Black Tiles",
     340            "Green Tiles"
     341        );
     342
     343        final ImageryLayer magentaTilesLayer = ImageryLayer.create(magentaTilesInfo);
     344        GuiHelper.runInEDT(() -> MainApplication.getLayerManager().addLayer(magentaTilesLayer));
     345
     346        assertSourceLabelsVisible(
     347            "White Tiles",
     348            "Black Tiles",
     349            "Green Tiles",
     350            "Magenta Tiles"
     351        );
     352
     353        this.clickSourceMenuItemByLabel("Magenta Tiles");
     354        this.assertSingleSelectedSourceLabel("Magenta Tiles");
     355
     356        // call paint to trigger new tile fetch
     357        this.paintSlippyMap();
     358
     359        Awaitility.await().atMost(1000, MILLISECONDS).until(this.slippyMapTasksFinished);
     360
     361        this.paintSlippyMap();
     362
     363        assertEquals(0xffff00ff, paintedSlippyMap.getRGB(0, 0));
     364
     365        final ImageryLayer blackTilesLayer = ImageryLayer.create(blackTilesInfo);
     366        GuiHelper.runInEDT(() -> MainApplication.getLayerManager().addLayer(blackTilesLayer));
     367
     368        assertSourceLabelsVisible(
     369            "White Tiles",
     370            "Black Tiles",
     371            "Green Tiles",
     372            "Magenta Tiles"
     373        );
     374
     375        this.clickSourceMenuItemByLabel("Black Tiles");
     376        this.assertSingleSelectedSourceLabel("Black Tiles");
     377
     378        // call paint to trigger new tile fetch
     379        this.paintSlippyMap();
     380
     381        Awaitility.await().atMost(1000, MILLISECONDS).until(this.slippyMapTasksFinished);
     382
     383        this.paintSlippyMap();
     384
     385        assertEquals(0xff000000, paintedSlippyMap.getRGB(0, 0));
     386
     387        // removing magentaTilesLayer while it is *not* the selected TileSource should make it disappear
     388        // immediately
     389        GuiHelper.runInEDT(() -> MainApplication.getLayerManager().removeLayer(magentaTilesLayer));
     390
     391        assertSourceLabelsVisible(
     392            "White Tiles",
     393            "Black Tiles",
     394            "Green Tiles"
     395        );
     396        this.assertSingleSelectedSourceLabel("Black Tiles");
     397
     398        final ImageryLayer magentaTilesLayer2 = ImageryLayer.create(magentaTilesInfo);
     399        GuiHelper.runInEDT(() -> MainApplication.getLayerManager().addLayer(magentaTilesLayer2));
     400
     401        assertSourceLabelsVisible(
     402            "White Tiles",
     403            "Black Tiles",
     404            "Green Tiles",
     405            "Magenta Tiles"
     406        );
     407
     408        this.clickSourceMenuItemByLabel("Magenta Tiles");
     409        this.assertSingleSelectedSourceLabel("Magenta Tiles");
     410
     411        // call paint to trigger new tile fetch
     412        this.paintSlippyMap();
     413
     414        Awaitility.await().atMost(1000, MILLISECONDS).until(this.slippyMapTasksFinished);
     415
     416        this.paintSlippyMap();
     417
     418        assertEquals(0xffff00ff, paintedSlippyMap.getRGB(0, 0));
     419
     420        // removing magentaTilesLayer while it *is* the selected TileSource...
     421        GuiHelper.runInEDT(() -> MainApplication.getLayerManager().removeLayer(magentaTilesLayer2));
     422
     423        assertSourceLabelsVisible(
     424            "White Tiles",
     425            "Black Tiles",
     426            "Green Tiles",
     427            "Magenta Tiles"
     428        );
     429        this.assertSingleSelectedSourceLabel("Magenta Tiles");
     430
     431        this.clickSourceMenuItemByLabel("Green Tiles");
     432        this.assertSingleSelectedSourceLabel("Green Tiles");
     433        assertSourceLabelsVisible(
     434            "White Tiles",
     435            "Black Tiles",
     436            "Green Tiles"
     437        );
     438
     439        // removing blackTilesLayer shouldn't remove it from the menu as it is already in ImageryLayerInfo
     440        GuiHelper.runInEDT(() -> MainApplication.getLayerManager().removeLayer(blackTilesLayer));
     441
     442        this.assertSingleSelectedSourceLabel("Green Tiles");
     443        assertSourceLabelsVisible(
     444            "White Tiles",
     445            "Black Tiles",
     446            "Green Tiles"
     447        );
    220448    }
    221449
Note: See TracChangeset for help on using the changeset viewer.