[8378] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[3687] | 2 | package org.openstreetmap.josm.gui.bbox;
|
---|
| 3 |
|
---|
[4531] | 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
[3687] | 6 | import java.awt.Color;
|
---|
| 7 | import java.awt.Dimension;
|
---|
| 8 | import java.awt.Graphics;
|
---|
| 9 | import java.awt.Point;
|
---|
| 10 | import java.awt.Rectangle;
|
---|
| 11 | import java.util.ArrayList;
|
---|
| 12 | import java.util.Arrays;
|
---|
[3715] | 13 | import java.util.Collections;
|
---|
[8168] | 14 | import java.util.HashMap;
|
---|
[3715] | 15 | import java.util.HashSet;
|
---|
[3687] | 16 | import java.util.List;
|
---|
[8168] | 17 | import java.util.Map;
|
---|
[6316] | 18 | import java.util.Set;
|
---|
[3687] | 19 | import java.util.concurrent.CopyOnWriteArrayList;
|
---|
| 20 |
|
---|
[4531] | 21 | import javax.swing.JOptionPane;
|
---|
[6539] | 22 | import javax.swing.SpringLayout;
|
---|
[4531] | 23 |
|
---|
[3687] | 24 | import org.openstreetmap.gui.jmapviewer.Coordinate;
|
---|
| 25 | import org.openstreetmap.gui.jmapviewer.JMapViewer;
|
---|
| 26 | import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
|
---|
| 27 | import org.openstreetmap.gui.jmapviewer.MemoryTileCache;
|
---|
| 28 | import org.openstreetmap.gui.jmapviewer.OsmTileLoader;
|
---|
[8526] | 29 | import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
|
---|
[3687] | 30 | import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
|
---|
[8168] | 31 | import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
|
---|
[3687] | 32 | import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
|
---|
[3915] | 33 | import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;
|
---|
[3687] | 34 | import org.openstreetmap.josm.Main;
|
---|
| 35 | import org.openstreetmap.josm.data.Bounds;
|
---|
[5898] | 36 | import org.openstreetmap.josm.data.Version;
|
---|
[3687] | 37 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
[3715] | 38 | import org.openstreetmap.josm.data.imagery.ImageryInfo;
|
---|
| 39 | import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
|
---|
[8598] | 40 | import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader;
|
---|
[11188] | 41 | import org.openstreetmap.josm.data.imagery.TileLoaderFactory;
|
---|
[12339] | 42 | import org.openstreetmap.josm.data.osm.BBox;
|
---|
[3687] | 43 | import org.openstreetmap.josm.data.preferences.StringProperty;
|
---|
[8598] | 44 | import org.openstreetmap.josm.gui.layer.AbstractCachedTileSourceLayer;
|
---|
[3715] | 45 | import org.openstreetmap.josm.gui.layer.TMSLayer;
|
---|
[12846] | 46 | import org.openstreetmap.josm.spi.preferences.Config;
|
---|
[12620] | 47 | import org.openstreetmap.josm.tools.Logging;
|
---|
[3687] | 48 |
|
---|
[12339] | 49 | /**
|
---|
| 50 | * This panel displays a map and lets the user chose a {@link BBox}.
|
---|
| 51 | */
|
---|
[6364] | 52 | public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser {
|
---|
[3687] | 53 |
|
---|
[12339] | 54 | /**
|
---|
| 55 | * A list of tile sources that can be used for displaying the map.
|
---|
| 56 | */
|
---|
[10600] | 57 | @FunctionalInterface
|
---|
[3687] | 58 | public interface TileSourceProvider {
|
---|
[12339] | 59 | /**
|
---|
| 60 | * Gets the tile sources that can be displayed
|
---|
| 61 | * @return The tile sources
|
---|
| 62 | */
|
---|
[3687] | 63 | List<TileSource> getTileSources();
|
---|
| 64 | }
|
---|
| 65 |
|
---|
| 66 | /**
|
---|
[3715] | 67 | * TMS TileSource provider for the slippymap chooser
|
---|
| 68 | */
|
---|
| 69 | public static class TMSTileSourceProvider implements TileSourceProvider {
|
---|
[8285] | 70 | private static final Set<String> existingSlippyMapUrls = new HashSet<>();
|
---|
[3715] | 71 | static {
|
---|
| 72 | // Urls that already exist in the slippymap chooser and shouldn't be copied from TMS layer list
|
---|
[6917] | 73 | existingSlippyMapUrls.add("https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png"); // Mapnik
|
---|
[3715] | 74 | }
|
---|
| 75 |
|
---|
| 76 | @Override
|
---|
| 77 | public List<TileSource> getTileSources() {
|
---|
| 78 | if (!TMSLayer.PROP_ADD_TO_SLIPPYMAP_CHOOSER.get()) return Collections.<TileSource>emptyList();
|
---|
[7005] | 79 | List<TileSource> sources = new ArrayList<>();
|
---|
[3715] | 80 | for (ImageryInfo info : ImageryLayerInfo.instance.getLayers()) {
|
---|
[3826] | 81 | if (existingSlippyMapUrls.contains(info.getUrl())) {
|
---|
[3715] | 82 | continue;
|
---|
| 83 | }
|
---|
[4531] | 84 | try {
|
---|
[8526] | 85 | TileSource source = TMSLayer.getTileSourceStatic(info);
|
---|
[4531] | 86 | if (source != null) {
|
---|
| 87 | sources.add(source);
|
---|
| 88 | }
|
---|
| 89 | } catch (IllegalArgumentException ex) {
|
---|
[12620] | 90 | Logging.warn(ex);
|
---|
[4531] | 91 | if (ex.getMessage() != null && !ex.getMessage().isEmpty()) {
|
---|
| 92 | JOptionPane.showMessageDialog(Main.parent,
|
---|
| 93 | ex.getMessage(), tr("Warning"),
|
---|
| 94 | JOptionPane.WARNING_MESSAGE);
|
---|
| 95 | }
|
---|
[3715] | 96 | }
|
---|
| 97 | }
|
---|
| 98 | return sources;
|
---|
| 99 | }
|
---|
| 100 | }
|
---|
| 101 |
|
---|
| 102 | /**
|
---|
[3687] | 103 | * Plugins that wish to add custom tile sources to slippy map choose should call this method
|
---|
[8470] | 104 | * @param tileSourceProvider new tile source provider
|
---|
[3687] | 105 | */
|
---|
| 106 | public static void addTileSourceProvider(TileSourceProvider tileSourceProvider) {
|
---|
| 107 | providers.addIfAbsent(tileSourceProvider);
|
---|
| 108 | }
|
---|
| 109 |
|
---|
[7005] | 110 | private static CopyOnWriteArrayList<TileSourceProvider> providers = new CopyOnWriteArrayList<>();
|
---|
[3687] | 111 | static {
|
---|
[12203] | 112 | addTileSourceProvider(() -> Arrays.<TileSource>asList(new OsmTileSource.Mapnik()));
|
---|
[3715] | 113 | addTileSourceProvider(new TMSTileSourceProvider());
|
---|
[3687] | 114 | }
|
---|
| 115 |
|
---|
| 116 | private static final StringProperty PROP_MAPSTYLE = new StringProperty("slippy_map_chooser.mapstyle", "Mapnik");
|
---|
[12339] | 117 | /**
|
---|
| 118 | * The property name used for the resize button.
|
---|
| 119 | * @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
|
---|
| 120 | */
|
---|
[4336] | 121 | public static final String RESIZE_PROP = SlippyMapBBoxChooser.class.getName() + ".resize";
|
---|
[3687] | 122 |
|
---|
[9078] | 123 | private final transient TileLoader cachedLoader;
|
---|
| 124 | private final transient OsmTileLoader uncachedLoader;
|
---|
[3687] | 125 |
|
---|
[6539] | 126 | private final SizeButton iSizeButton;
|
---|
[3687] | 127 | private final SourceButton iSourceButton;
|
---|
[8308] | 128 | private transient Bounds bbox;
|
---|
[3687] | 129 |
|
---|
[6364] | 130 | // upper left and lower right corners of the selection rectangle (x/y on ZOOM_MAX)
|
---|
[9623] | 131 | private transient ICoordinate iSelectionRectStart;
|
---|
| 132 | private transient ICoordinate iSelectionRectEnd;
|
---|
[3687] | 133 |
|
---|
[6364] | 134 | /**
|
---|
| 135 | * Constructs a new {@code SlippyMapBBoxChooser}.
|
---|
| 136 | */
|
---|
[3687] | 137 | public SlippyMapBBoxChooser() {
|
---|
[12620] | 138 | debug = Logging.isDebugEnabled();
|
---|
[6539] | 139 | SpringLayout springLayout = new SpringLayout();
|
---|
| 140 | setLayout(springLayout);
|
---|
[3777] | 141 |
|
---|
[8168] | 142 | Map<String, String> headers = new HashMap<>();
|
---|
| 143 | headers.put("User-Agent", Version.getInstance().getFullAgentString());
|
---|
| 144 |
|
---|
[11188] | 145 | TileLoaderFactory cachedLoaderFactory = AbstractCachedTileSourceLayer.getTileLoaderFactory("TMS", TMSCachedTileLoader.class);
|
---|
| 146 | if (cachedLoaderFactory != null) {
|
---|
| 147 | cachedLoader = cachedLoaderFactory.makeTileLoader(this, headers);
|
---|
| 148 | } else {
|
---|
| 149 | cachedLoader = null;
|
---|
| 150 | }
|
---|
[8168] | 151 |
|
---|
[3687] | 152 | uncachedLoader = new OsmTileLoader(this);
|
---|
[8168] | 153 | uncachedLoader.headers.putAll(headers);
|
---|
[12846] | 154 | setZoomContolsVisible(Config.getPref().getBoolean("slippy_map_chooser.zoomcontrols", false));
|
---|
[3687] | 155 | setMapMarkerVisible(false);
|
---|
| 156 | setMinimumSize(new Dimension(350, 350 / 2));
|
---|
| 157 | // We need to set an initial size - this prevents a wrong zoom selection
|
---|
[6364] | 158 | // for the area before the component has been displayed the first time
|
---|
[3687] | 159 | setBounds(new Rectangle(getMinimumSize()));
|
---|
| 160 | if (cachedLoader == null) {
|
---|
| 161 | setFileCacheEnabled(false);
|
---|
| 162 | } else {
|
---|
[12846] | 163 | setFileCacheEnabled(Config.getPref().getBoolean("slippy_map_chooser.file_cache", true));
|
---|
[3687] | 164 | }
|
---|
[12846] | 165 | setMaxTilesInMemory(Config.getPref().getInt("slippy_map_chooser.max_tiles", 1000));
|
---|
[3687] | 166 |
|
---|
[6378] | 167 | List<TileSource> tileSources = getAllTileSources();
|
---|
[3687] | 168 |
|
---|
[6539] | 169 | iSourceButton = new SourceButton(this, tileSources);
|
---|
| 170 | add(iSourceButton);
|
---|
| 171 | springLayout.putConstraint(SpringLayout.EAST, iSourceButton, 0, SpringLayout.EAST, this);
|
---|
| 172 | springLayout.putConstraint(SpringLayout.NORTH, iSourceButton, 30, SpringLayout.NORTH, this);
|
---|
[3687] | 173 |
|
---|
[6539] | 174 | iSizeButton = new SizeButton(this);
|
---|
| 175 | add(iSizeButton);
|
---|
| 176 |
|
---|
[3687] | 177 | String mapStyle = PROP_MAPSTYLE.get();
|
---|
| 178 | boolean foundSource = false;
|
---|
| 179 | for (TileSource source: tileSources) {
|
---|
| 180 | if (source.getName().equals(mapStyle)) {
|
---|
| 181 | this.setTileSource(source);
|
---|
| 182 | iSourceButton.setCurrentMap(source);
|
---|
| 183 | foundSource = true;
|
---|
| 184 | break;
|
---|
| 185 | }
|
---|
| 186 | }
|
---|
| 187 | if (!foundSource) {
|
---|
| 188 | setTileSource(tileSources.get(0));
|
---|
| 189 | iSourceButton.setCurrentMap(tileSources.get(0));
|
---|
| 190 | }
|
---|
| 191 |
|
---|
[6539] | 192 | new SlippyMapControler(this, this);
|
---|
[3687] | 193 | }
|
---|
[6539] | 194 |
|
---|
[10755] | 195 | private static List<TileSource> getAllTileSources() {
|
---|
[7005] | 196 | List<TileSource> tileSources = new ArrayList<>();
|
---|
[6364] | 197 | for (TileSourceProvider provider: providers) {
|
---|
| 198 | tileSources.addAll(provider.getTileSources());
|
---|
| 199 | }
|
---|
| 200 | return tileSources;
|
---|
| 201 | }
|
---|
[3687] | 202 |
|
---|
[12339] | 203 | /**
|
---|
| 204 | * Handles a click/move on the attribution
|
---|
| 205 | * @param p The point in the view
|
---|
| 206 | * @param click true if it was a click, false for hover
|
---|
| 207 | * @return if the attribution handled the event
|
---|
| 208 | */
|
---|
[4195] | 209 | public boolean handleAttribution(Point p, boolean click) {
|
---|
[4489] | 210 | return attribution.handleAttribution(p, click);
|
---|
[4195] | 211 | }
|
---|
| 212 |
|
---|
[3687] | 213 | /**
|
---|
| 214 | * Draw the map.
|
---|
| 215 | */
|
---|
| 216 | @Override
|
---|
| 217 | public void paint(Graphics g) {
|
---|
[10212] | 218 | super.paint(g);
|
---|
[3687] | 219 |
|
---|
[10212] | 220 | // draw selection rectangle
|
---|
| 221 | if (iSelectionRectStart != null && iSelectionRectEnd != null) {
|
---|
| 222 | Rectangle box = new Rectangle(getMapPosition(iSelectionRectStart, false));
|
---|
| 223 | box.add(getMapPosition(iSelectionRectEnd, false));
|
---|
[3687] | 224 |
|
---|
[10212] | 225 | g.setColor(new Color(0.9f, 0.7f, 0.7f, 0.6f));
|
---|
| 226 | g.fillRect(box.x, box.y, box.width, box.height);
|
---|
[3687] | 227 |
|
---|
[10212] | 228 | g.setColor(Color.BLACK);
|
---|
| 229 | g.drawRect(box.x, box.y, box.width, box.height);
|
---|
[3687] | 230 | }
|
---|
| 231 | }
|
---|
| 232 |
|
---|
[12339] | 233 | /**
|
---|
| 234 | * Enables the disk tile cache.
|
---|
| 235 | * @param enabled true to enable, false to disable
|
---|
| 236 | */
|
---|
[6890] | 237 | public final void setFileCacheEnabled(boolean enabled) {
|
---|
[11188] | 238 | if (enabled && cachedLoader != null) {
|
---|
[3687] | 239 | setTileLoader(cachedLoader);
|
---|
| 240 | } else {
|
---|
| 241 | setTileLoader(uncachedLoader);
|
---|
| 242 | }
|
---|
| 243 | }
|
---|
| 244 |
|
---|
[12339] | 245 | /**
|
---|
| 246 | * Sets the maximum number of tiles that may be held in memory
|
---|
| 247 | * @param tiles The maximum number of tiles.
|
---|
| 248 | */
|
---|
[6890] | 249 | public final void setMaxTilesInMemory(int tiles) {
|
---|
[3687] | 250 | ((MemoryTileCache) getTileCache()).setCacheSize(tiles);
|
---|
| 251 | }
|
---|
| 252 |
|
---|
| 253 | /**
|
---|
[8470] | 254 | * Callback for the OsmMapControl. (Re-)Sets the start and end point of the selection rectangle.
|
---|
[3687] | 255 | *
|
---|
[8470] | 256 | * @param aStart selection start
|
---|
| 257 | * @param aEnd selection end
|
---|
[3687] | 258 | */
|
---|
| 259 | public void setSelection(Point aStart, Point aEnd) {
|
---|
| 260 | if (aStart == null || aEnd == null || aStart.x == aEnd.x || aStart.y == aEnd.y)
|
---|
| 261 | return;
|
---|
| 262 |
|
---|
[10001] | 263 | Point pMax = new Point(Math.max(aEnd.x, aStart.x), Math.max(aEnd.y, aStart.y));
|
---|
| 264 | Point pMin = new Point(Math.min(aEnd.x, aStart.x), Math.min(aEnd.y, aStart.y));
|
---|
[3687] | 265 |
|
---|
[10001] | 266 | iSelectionRectStart = getPosition(pMin);
|
---|
[10378] | 267 | iSelectionRectEnd = getPosition(pMax);
|
---|
[3687] | 268 |
|
---|
| 269 | Bounds b = new Bounds(
|
---|
| 270 | new LatLon(
|
---|
[8349] | 271 | Math.min(iSelectionRectStart.getLat(), iSelectionRectEnd.getLat()),
|
---|
| 272 | LatLon.toIntervalLon(Math.min(iSelectionRectStart.getLon(), iSelectionRectEnd.getLon()))
|
---|
[4868] | 273 | ),
|
---|
| 274 | new LatLon(
|
---|
[8349] | 275 | Math.max(iSelectionRectStart.getLat(), iSelectionRectEnd.getLat()),
|
---|
| 276 | LatLon.toIntervalLon(Math.max(iSelectionRectStart.getLon(), iSelectionRectEnd.getLon())))
|
---|
[4868] | 277 | );
|
---|
[3687] | 278 | Bounds oldValue = this.bbox;
|
---|
| 279 | this.bbox = b;
|
---|
[4336] | 280 | repaint();
|
---|
[3687] | 281 | firePropertyChange(BBOX_PROP, oldValue, this.bbox);
|
---|
| 282 | }
|
---|
| 283 |
|
---|
| 284 | /**
|
---|
| 285 | * Performs resizing of the DownloadDialog in order to enlarge or shrink the
|
---|
| 286 | * map.
|
---|
| 287 | */
|
---|
| 288 | public void resizeSlippyMap() {
|
---|
[4336] | 289 | boolean large = iSizeButton.isEnlarged();
|
---|
| 290 | firePropertyChange(RESIZE_PROP, !large, large);
|
---|
[3687] | 291 | }
|
---|
| 292 |
|
---|
[12339] | 293 | /**
|
---|
| 294 | * Sets the active tile source
|
---|
| 295 | * @param tileSource The active tile source
|
---|
| 296 | */
|
---|
[3687] | 297 | public void toggleMapSource(TileSource tileSource) {
|
---|
| 298 | this.tileController.setTileCache(new MemoryTileCache());
|
---|
| 299 | this.setTileSource(tileSource);
|
---|
| 300 | PROP_MAPSTYLE.put(tileSource.getName()); // TODO Is name really unique?
|
---|
| 301 | }
|
---|
| 302 |
|
---|
[6084] | 303 | @Override
|
---|
[3687] | 304 | public Bounds getBoundingBox() {
|
---|
| 305 | return bbox;
|
---|
| 306 | }
|
---|
| 307 |
|
---|
| 308 | /**
|
---|
| 309 | * Sets the current bounding box in this bbox chooser without
|
---|
| 310 | * emiting a property change event.
|
---|
| 311 | *
|
---|
| 312 | * @param bbox the bounding box. null to reset the bounding box
|
---|
| 313 | */
|
---|
[6084] | 314 | @Override
|
---|
[3687] | 315 | public void setBoundingBox(Bounds bbox) {
|
---|
[8393] | 316 | if (bbox == null || (bbox.getMinLat() == 0 && bbox.getMinLon() == 0
|
---|
| 317 | && bbox.getMaxLat() == 0 && bbox.getMaxLon() == 0)) {
|
---|
[3687] | 318 | this.bbox = null;
|
---|
[4336] | 319 | iSelectionRectStart = null;
|
---|
| 320 | iSelectionRectEnd = null;
|
---|
| 321 | repaint();
|
---|
[3687] | 322 | return;
|
---|
| 323 | }
|
---|
| 324 |
|
---|
[4336] | 325 | this.bbox = bbox;
|
---|
[8349] | 326 | iSelectionRectStart = new Coordinate(bbox.getMinLat(), bbox.getMinLon());
|
---|
| 327 | iSelectionRectEnd = new Coordinate(bbox.getMaxLat(), bbox.getMaxLon());
|
---|
[4868] | 328 |
|
---|
[3687] | 329 | // calc the screen coordinates for the new selection rectangle
|
---|
[8387] | 330 | MapMarkerDot min = new MapMarkerDot(bbox.getMinLat(), bbox.getMinLon());
|
---|
| 331 | MapMarkerDot max = new MapMarkerDot(bbox.getMaxLat(), bbox.getMaxLon());
|
---|
[3687] | 332 |
|
---|
[7005] | 333 | List<MapMarker> marker = new ArrayList<>(2);
|
---|
[8387] | 334 | marker.add(min);
|
---|
| 335 | marker.add(max);
|
---|
[3687] | 336 | setMapMarkerList(marker);
|
---|
| 337 | setDisplayToFitMapMarkers();
|
---|
| 338 | zoomOut();
|
---|
[4552] | 339 | repaint();
|
---|
[3687] | 340 | }
|
---|
[6539] | 341 |
|
---|
[6364] | 342 | /**
|
---|
[8732] | 343 | * Enables or disables painting of the shrink/enlarge button
|
---|
| 344 | *
|
---|
| 345 | * @param visible {@code true} to enable painting of the shrink/enlarge button
|
---|
| 346 | */
|
---|
| 347 | public void setSizeButtonVisible(boolean visible) {
|
---|
| 348 | iSizeButton.setVisible(visible);
|
---|
| 349 | }
|
---|
| 350 |
|
---|
| 351 | /**
|
---|
[6364] | 352 | * Refreshes the tile sources
|
---|
| 353 | * @since 6364
|
---|
| 354 | */
|
---|
| 355 | public final void refreshTileSources() {
|
---|
[6378] | 356 | iSourceButton.setSources(getAllTileSources());
|
---|
[6364] | 357 | }
|
---|
[3687] | 358 | }
|
---|