Changeset 8526 in josm for trunk/src/org
- Timestamp:
- 2015-06-24T20:57:43+02:00 (10 years ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 6 added
- 6 deleted
- 14 edited
- 1 copied
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
r8513 r8526 193 193 } 194 194 // object not in cache, so submit work to separate thread 195 getDownloadExecutor().execute(this);195 downloadJobExecutor.execute(this); 196 196 } 197 197 } … … 230 230 protected String getServerKey() { 231 231 return getUrl().getHost(); 232 }233 234 /**235 * this needs to be non-static, so it can be overridden by subclasses236 */237 protected ThreadPoolExecutor getDownloadExecutor() {238 return downloadJobExecutor;239 232 } 240 233 … … 506 499 */ 507 500 public void cancelOutstandingTasks() { 508 ThreadPoolExecutor downloadExecutor = getDownloadExecutor(); 509 for (Runnable r: downloadExecutor.getQueue()) { 510 if (downloadExecutor.remove(r)) { 511 if (r instanceof JCSCachedTileLoaderJob) { 512 ((JCSCachedTileLoaderJob<?, ?>) r).handleJobCancellation(); 513 } 501 for(Runnable r: downloadJobExecutor.getQueue()) { 502 if (downloadJobExecutor.remove(r) && r instanceof JCSCachedTileLoaderJob) { 503 ((JCSCachedTileLoaderJob<?, ?>) r).handleJobCancellation(); 514 504 } 515 505 } -
trunk/src/org/openstreetmap/josm/data/coor/LatLon.java
r8510 r8526 17 17 import java.util.Locale; 18 18 19 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 19 20 import org.openstreetmap.josm.Main; 20 21 import org.openstreetmap.josm.data.Bounds; … … 188 189 } 189 190 191 public LatLon(ICoordinate coor) { 192 this(coor.getLat(), coor.getLon()); 193 } 194 195 190 196 /** 191 197 * Returns the latitude, i.e., the north-south position in degrees. … … 434 440 return true; 435 441 } 442 443 public ICoordinate toCoordinate() { 444 return new org.openstreetmap.gui.jmapviewer.Coordinate(lat(), lon()); 445 } 436 446 } -
trunk/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
r8513 r8526 19 19 import javax.swing.ImageIcon; 20 20 21 import org.openstreetmap.gui.jmapviewer.Coordinate;22 21 import org.openstreetmap.gui.jmapviewer.OsmMercator; 23 22 import org.openstreetmap.gui.jmapviewer.interfaces.Attributed; 23 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 24 24 import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource; 25 25 import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik; … … 575 575 576 576 @Override 577 public String getAttributionText(int zoom, Coordinate topLeft, Coordinate botRight) { 577 public String getAttributionText(int zoom, ICoordinate topLeft, ICoordinate botRight) { 578 578 return attributionText; 579 579 } -
trunk/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java
r8510 r8526 29 29 public class TMSCachedTileLoader implements TileLoader, CachedTileLoader, TileCache { 30 30 31 pr ivatefinal ICacheAccess<String, BufferedImageCacheEntry> cache;32 pr ivatefinal int connectTimeout;33 pr ivatefinal int readTimeout;34 pr ivatefinal Map<String, String> headers;35 pr ivatefinal TileLoaderListener listener;31 protected final ICacheAccess<String, BufferedImageCacheEntry> cache; 32 protected final int connectTimeout; 33 protected final int readTimeout; 34 protected final Map<String, String> headers; 35 protected final TileLoaderListener listener; 36 36 private static final String PREFERENCE_PREFIX = "imagery.tms.cache."; 37 37 … … 55 55 * and for TMS imagery 56 56 */ 57 private static ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = getThreadPoolExecutor(); 57 private static ThreadPoolExecutor DEFAULT_DOWNLOAD_JOB_DISPATCHER = getNewThreadPoolExecutor("TMS downloader"); 58 58 59 private final ThreadPoolExecutor downloadExecutor = DEFAULT_DOWNLOAD_JOB_DISPATCHER; 60 61 private static ThreadPoolExecutor getThreadPoolExecutor() { 62 return new ThreadPoolExecutor( 63 THREAD_LIMIT.get().intValue(), // keep the thread number constant 64 THREAD_LIMIT.get().intValue(), // do not this number of threads 65 30, // keepalive for thread 66 TimeUnit.SECONDS, 67 new HostLimitQueue(HOST_LIMIT.get().intValue()), 68 JCSCachedTileLoaderJob.getNamedThreadFactory("TMS downloader") 69 ); 70 } 59 private ThreadPoolExecutor downloadExecutor = DEFAULT_DOWNLOAD_JOB_DISPATCHER; 71 60 72 61 /** … … 92 81 } 93 82 83 /** 84 * @param name name of the threads 85 * @param workers number of worker thread to keep 86 * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue 87 */ 88 public static ThreadPoolExecutor getNewThreadPoolExecutor(String name, int workers) { 89 return new ThreadPoolExecutor( 90 workers, // keep the thread number constant 91 workers, // do not this number of threads 92 30, // keepalive for thread 93 TimeUnit.SECONDS, 94 new HostLimitQueue(HOST_LIMIT.get().intValue()), 95 JCSCachedTileLoaderJob.getNamedThreadFactory(name) 96 ); 97 } 98 99 /** 100 * @param name name of threads 101 * @return new ThreadPoolExecutor that will use a @see HostLimitQueue based queue, with default number of threads 102 */ 103 public static ThreadPoolExecutor getNewThreadPoolExecutor(String name) { 104 return getNewThreadPoolExecutor(name, THREAD_LIMIT.get().intValue()); 105 } 106 94 107 @Override 95 108 public TileJob createTileLoaderJob(Tile tile) { 96 109 return new TMSCachedTileLoaderJob(listener, tile, cache, 97 connectTimeout, readTimeout, headers, downloadExecutor);110 connectTimeout, readTimeout, headers, getDownloadExecutor()); 98 111 } 99 112 … … 141 154 } 142 155 } 156 157 /** 158 * Sets the download executor that will be used to download tiles instead of default one. 159 * You can use {@link #getNewThreadPoolExecutor} to create a new download executor with separate 160 * queue from default. 161 * 162 * @param downloadExecutor 163 */ 164 public void setDownloadExecutor(ThreadPoolExecutor downloadExecutor) { 165 this.downloadExecutor = downloadExecutor; 166 } 167 168 /** 169 * @return download executor that is used by this factory 170 */ 171 public ThreadPoolExecutor getDownloadExecutor() { 172 return downloadExecutor; 173 } 174 143 175 } -
trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java
r8510 r8526 27 27 import org.openstreetmap.gui.jmapviewer.MemoryTileCache; 28 28 import org.openstreetmap.gui.jmapviewer.OsmTileLoader; 29 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 29 30 import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker; 30 31 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; … … 70 71 } 71 72 try { 72 TileSource source = TMSLayer.getTileSource(info); 73 TileSource source = TMSLayer.getTileSourceStatic(info); 73 74 if (source != null) { 74 75 sources.add(source); … … 124 125 125 126 // upper left and lower right corners of the selection rectangle (x/y on ZOOM_MAX) 126 private Coordinate iSelectionRectStart; 127 private Coordinate iSelectionRectEnd; 127 private ICoordinate iSelectionRectStart; 128 private ICoordinate iSelectionRectEnd; 128 129 129 130 /** -
trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
r8525 r8526 5 5 6 6 import java.awt.Color; 7 import java.awt.Component; 7 8 import java.awt.Font; 8 9 import java.awt.Graphics; … … 19 20 import java.io.File; 20 21 import java.io.IOException; 21 import java.io.StringReader;22 import java.net.URL;23 22 import java.text.SimpleDateFormat; 24 23 import java.util.ArrayList; … … 26 25 import java.util.Comparator; 27 26 import java.util.Date; 28 import java.util.HashMap;29 27 import java.util.LinkedList; 30 28 import java.util.List; 31 29 import java.util.Map; 32 30 import java.util.Map.Entry; 33 import java.util.Scanner; 34 import java.util.concurrent.Callable; 35 import java.util.regex.Matcher; 36 import java.util.regex.Pattern; 31 import java.util.Set; 32 import java.util.concurrent.ConcurrentSkipListSet; 37 33 38 34 import javax.swing.AbstractAction; … … 52 48 import org.openstreetmap.gui.jmapviewer.OsmTileLoader; 53 49 import org.openstreetmap.gui.jmapviewer.Tile; 50 import org.openstreetmap.gui.jmapviewer.TileXY; 54 51 import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader; 52 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 55 53 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache; 56 54 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 57 55 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 58 56 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 59 import org.openstreetmap.gui.jmapviewer.tilesources.BingAerialTileSource;60 import org.openstreetmap.gui.jmapviewer.tilesources.ScanexTileSource;61 import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;62 import org.openstreetmap.gui.jmapviewer.tilesources.TemplatedTMSTileSource;63 57 import org.openstreetmap.josm.Main; 64 58 import org.openstreetmap.josm.actions.RenameLayerAction; 59 import org.openstreetmap.josm.actions.SaveActionBase; 65 60 import org.openstreetmap.josm.data.Bounds; 66 import org.openstreetmap.josm.data.Version;67 61 import org.openstreetmap.josm.data.coor.EastNorth; 68 62 import org.openstreetmap.josm.data.coor.LatLon; 69 63 import org.openstreetmap.josm.data.imagery.ImageryInfo; 70 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;71 64 import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader; 65 import org.openstreetmap.josm.data.imagery.TileLoaderFactory; 72 66 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 73 67 import org.openstreetmap.josm.data.preferences.BooleanProperty; 74 68 import org.openstreetmap.josm.data.preferences.IntegerProperty; 75 import org.openstreetmap.josm.data.preferences.StringProperty;76 import org.openstreetmap.josm.data.projection.Projection;77 69 import org.openstreetmap.josm.gui.ExtendedDialog; 78 70 import org.openstreetmap.josm.gui.MapFrame; … … 84 76 import org.openstreetmap.josm.gui.dialogs.LayerListPopup; 85 77 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 86 import org.openstreetmap.josm.io.CacheCustomContent; 87 import org.openstreetmap.josm.io.OsmTransferException; 88 import org.openstreetmap.josm.io.UTFInputStreamReader; 89 import org.openstreetmap.josm.tools.CheckParameterUtil; 78 import org.openstreetmap.josm.io.WMSLayerImporter; 90 79 import org.openstreetmap.josm.tools.GBC; 91 import org.openstreetmap.josm.tools.Utils;92 import org.xml.sax.InputSource;93 import org.xml.sax.SAXException;94 80 95 81 /** 96 * Class that displays a slippy map layer.97 82 * 98 * @author Frederik Ramm 99 * @author LuVar <lubomir.varga@freemap.sk> 100 * @author Dave Hansen <dave@sr71.net> 101 * @author Upliner <upliner@gmail.com> 83 * Base abstract class that supports displaying images provided by TileSource. It might be TMS source, WMS or WMTS 84 * 85 * It implements all standard functions of tilesource based layers: autozoom, tile reloads, layer saving, loading,etc. 86 * 87 * @author Wiktor Niesiobędzki 88 * @since TODO 102 89 * 103 90 */ 104 public class TMSLayer extends ImageryLayer implements ImageObserver, TileLoaderListener, ZoomChangeListener { 105 public static final String PREFERENCE_PREFIX = "imagery.tms"; 106 91 public abstract class AbstractTileSourceLayer extends ImageryLayer implements ImageObserver, TileLoaderListener, ZoomChangeListener { 92 private static final String PREFERENCE_PREFIX = "imagery.generic"; 93 94 /** maximum zoom level supported */ 107 95 public static final int MAX_ZOOM = 30; 96 /** minium zoom level supported */ 108 97 public static final int MIN_ZOOM = 2; 109 p ublicstatic finalint DEFAULT_MAX_ZOOM = 20;110 public static final int DEFAULT_MIN_ZOOM = 2; 111 98 private static final Font InfoFont = new Font("sansserif", Font.BOLD, 13); 99 100 /** do set autozoom when creating a new layer */ 112 101 public static final BooleanProperty PROP_DEFAULT_AUTOZOOM = new BooleanProperty(PREFERENCE_PREFIX + ".default_autozoom", true); 102 /** do set autoload when creating a new layer */ 113 103 public static final BooleanProperty PROP_DEFAULT_AUTOLOAD = new BooleanProperty(PREFERENCE_PREFIX + ".default_autoload", true); 104 /** do set showerrors when creating a new layer */ 114 105 public static final BooleanProperty PROP_DEFAULT_SHOWERRORS = new BooleanProperty(PREFERENCE_PREFIX + ".default_showerrors", true); 115 public static final IntegerProperty PROP_MIN_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".min_zoom_lvl", DEFAULT_MIN_ZOOM); 116 public static final IntegerProperty PROP_MAX_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".max_zoom_lvl", DEFAULT_MAX_ZOOM); 117 public static final BooleanProperty PROP_ADD_TO_SLIPPYMAP_CHOOSER = new BooleanProperty(PREFERENCE_PREFIX + 118 ".add_to_slippymap_chooser", true); 119 public static final StringProperty PROP_TILECACHE_DIR; 120 static { 121 String defPath = null; 122 try { 123 defPath = new File(Main.pref.getCacheDirectory(), "tms").getAbsolutePath(); 124 } catch (SecurityException e) { 125 Main.warn(e); 126 } 127 PROP_TILECACHE_DIR = new StringProperty(PREFERENCE_PREFIX + ".tilecache", defPath); 128 } 129 130 private final class ShowTileInfoAction extends AbstractAction { 131 private ShowTileInfoAction() { 132 super(tr("Show Tile Info")); 133 } 134 135 private String getSizeString(int size) { 136 return new StringBuilder().append(size).append("x").append(size).toString(); 137 } 138 139 private JTextField createTextField(String text) { 140 JTextField ret = new JTextField(text); 141 ret.setEditable(false); 142 ret.setBorder(BorderFactory.createEmptyBorder()); 143 return ret; 144 } 145 146 @Override 147 public void actionPerformed(ActionEvent ae) { 148 if (clickedTile != null) { 149 ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Tile Info"), new String[]{tr("OK")}); 150 JPanel panel = new JPanel(new GridBagLayout()); 151 Rectangle displaySize = tileToRect(clickedTile); 152 String url = ""; 153 try { 154 url = clickedTile.getUrl(); 155 } catch (IOException e) { 156 // silence exceptions 157 if (Main.isTraceEnabled()) { 158 Main.trace(e.getMessage()); 159 } 160 } 161 162 String[][] content = { 163 {"Tile name", clickedTile.getKey()}, 164 {"Tile url", url}, 165 {"Tile size", getSizeString(clickedTile.getTileSource().getTileSize()) }, 166 {"Tile display size", new StringBuilder().append(displaySize.width).append("x").append(displaySize.height).toString()}, 167 }; 168 169 for (String[] entry: content) { 170 panel.add(new JLabel(tr(entry[0]) + ":"), GBC.std()); 171 panel.add(GBC.glue(5, 0), GBC.std()); 172 panel.add(createTextField(entry[1]), GBC.eol().fill(GBC.HORIZONTAL)); 173 } 174 175 for (Entry<String, String> e: clickedTile.getMetadata().entrySet()) { 176 panel.add(new JLabel(tr("Metadata ") + tr(e.getKey()) + ":"), GBC.std()); 177 panel.add(GBC.glue(5, 0), GBC.std()); 178 String value = e.getValue(); 179 if ("lastModification".equals(e.getKey()) || "expirationTime".equals(e.getKey())) { 180 value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(Long.parseLong(value))); 181 } 182 panel.add(createTextField(value), GBC.eol().fill(GBC.HORIZONTAL)); 183 } 184 ed.setIcon(JOptionPane.INFORMATION_MESSAGE); 185 ed.setContent(panel); 186 ed.showDialog(); 187 } 188 } 189 } 190 191 /** 192 * Interface for creating TileLoaders, ie. classes responsible for loading tiles on map 193 * 194 */ 195 public interface TileLoaderFactory { 196 /** 197 * @param listener object that will be notified, when tile has finished loading 198 * @return TileLoader that will notify the listener 199 */ 200 TileLoader makeTileLoader(TileLoaderListener listener); 201 202 /** 203 * @param listener object that will be notified, when tile has finished loading 204 * @param headers HTTP headers that should be sent by TileLoader to tile server 205 * @return TileLoader that will notify the listener 206 */ 207 TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers); 208 } 106 /** minimum zoom level to show to user */ 107 public static final IntegerProperty PROP_MIN_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".min_zoom_lvl", 2); 108 /** maximum zoom level to show to user */ 109 public static final IntegerProperty PROP_MAX_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".max_zoom_lvl", 20); 110 111 //public static final BooleanProperty PROP_DRAW_DEBUG = new BooleanProperty(PREFERENCE_PREFIX + ".draw_debug", false); 112 /** 113 * Zoomlevel at which tiles is currently downloaded. 114 * Initial zoom lvl is set to bestZoom 115 */ 116 public int currentZoomLevel; 117 private boolean needRedraw; 118 119 private AttributionSupport attribution = new AttributionSupport(); 120 Tile showMetadataTile; 121 122 // needed public access for session exporter 123 /** if layers changes automatically, when user zooms in */ 124 public boolean autoZoom; 125 /** if layer automatically loads new tiles */ 126 public boolean autoLoad; 127 /** if layer should show errors on tiles */ 128 public boolean showErrors; 209 129 210 130 protected TileCache tileCache; 211 131 protected TileSource tileSource; 132 //protected tileMatrix; 212 133 protected TileLoader tileLoader; 213 134 214 215 public static TileLoaderFactory loaderFactory = new TileLoaderFactory() { 216 @Override 217 public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> inputHeaders) { 218 Map<String, String> headers = new HashMap<>(); 219 headers.put("User-Agent", Version.getInstance().getFullAgentString()); 220 headers.put("Accept", "text/html, image/png, image/jpeg, image/gif, */*"); 221 if (inputHeaders != null) 222 headers.putAll(inputHeaders); 223 224 try { 225 return new TMSCachedTileLoader(listener, "TMS", 226 Main.pref.getInteger("socket.timeout.connect", 15) * 1000, 227 Main.pref.getInteger("socket.timeout.read", 30) * 1000, 228 headers, 229 PROP_TILECACHE_DIR.get()); 230 } catch (IOException e) { 231 Main.warn(e); 232 } 233 return null; 234 } 235 236 @Override 237 public TileLoader makeTileLoader(TileLoaderListener listener) { 238 return makeTileLoader(listener, null); 239 } 240 }; 241 242 /** 243 * Plugins that wish to set custom tile loader should call this method 244 */ 245 246 public static void setCustomTileLoaderFactory(TileLoaderFactory loaderFactory) { 247 TMSLayer.loaderFactory = loaderFactory; 248 } 135 /** 136 * Creates Tile Source based Imagery Layer based on Imagery Info 137 * @param info 138 * @param tileSource 139 */ 140 public AbstractTileSourceLayer(ImageryInfo info) { 141 super(info); 142 143 if(!isProjectionSupported(Main.getProjection())) { 144 JOptionPane.showMessageDialog(Main.parent, 145 tr("This layer do not support the projection {0}.\n{1}\n" 146 + "Change the projection or remove the layer.", 147 Main.getProjection().toCode(), nameSupportedProjections()), 148 tr("Warning"), 149 JOptionPane.WARNING_MESSAGE); 150 } 151 setBackgroundLayer(true); 152 this.setVisible(true); 153 154 initTileSource(getTileSource(info)); 155 MapView.addZoomChangeListener(this); 156 } 157 158 protected abstract TileLoaderFactory getTileLoaderFactory(); 159 160 /** 161 * 162 * @param info 163 * @return TileSource for specified ImageryInfo 164 * @throws IllegalArgumentException when Imagery is not supported by layer 165 */ 166 protected abstract TileSource getTileSource(ImageryInfo info) throws IllegalArgumentException; 167 168 protected abstract Map<String, String> getHeaders(TileSource tileSource); 169 170 protected void initTileSource(TileSource tileMatrix) { 171 this.tileSource = tileMatrix; 172 attribution.initialize(tileMatrix); 173 174 currentZoomLevel = getBestZoom(); 175 176 Map<String, String> headers = getHeaders(tileMatrix); 177 178 tileLoader = getTileLoaderFactory().makeTileLoader(this, headers); 179 if (tileLoader instanceof TMSCachedTileLoader) { 180 tileCache = (TileCache) tileLoader; 181 } else { 182 tileCache = new MemoryTileCache(); 183 } 184 if (tileLoader == null) 185 tileLoader = new OsmTileLoader(this); 186 } 187 188 249 189 250 190 @Override … … 279 219 tileCache.clear(); 280 220 if (tileLoader instanceof CachedTileLoader) { 281 ((CachedTileLoader) tileLoader).clearCache(tileSource); 282 } 283 redraw(); 284 } 285 286 /** 287 * Zoomlevel at which tiles is currently downloaded. 288 * Initial zoom lvl is set to bestZoom 289 */ 290 public int currentZoomLevel; 291 292 private Tile clickedTile; 293 private boolean needRedraw; 294 private JPopupMenu tileOptionMenu; 295 private Tile showMetadataTile; 296 private AttributionSupport attribution = new AttributionSupport(); 297 private static final Font InfoFont = new Font("sansserif", Font.BOLD, 13); 298 299 protected boolean autoZoom; 300 protected boolean autoLoad; 301 protected boolean showErrors; 221 ((CachedTileLoader)tileLoader).clearCache(tileSource); 222 } 223 } 224 302 225 303 226 /** … … 312 235 } 313 236 314 protected static int checkMaxZoomLvl(int maxZoomLvl, TileSource ts) {315 if (maxZoomLvl > MAX_ZOOM) {316 maxZoomLvl = MAX_ZOOM;317 }318 if (maxZoomLvl < PROP_MIN_ZOOM_LVL.get()) {319 maxZoomLvl = PROP_MIN_ZOOM_LVL.get();320 }321 if (ts != null && ts.getMaxZoom() != 0 && ts.getMaxZoom() < maxZoomLvl) {322 maxZoomLvl = ts.getMaxZoom();323 }324 return maxZoomLvl;325 }326 327 public static int getMaxZoomLvl(TileSource ts) {328 return checkMaxZoomLvl(PROP_MAX_ZOOM_LVL.get(), ts);329 }330 331 public static void setMaxZoomLvl(int maxZoomLvl) {332 Integer newMaxZoom = Integer.valueOf(checkMaxZoomLvl(maxZoomLvl, null));333 PROP_MAX_ZOOM_LVL.put(newMaxZoom);334 }335 336 static int checkMinZoomLvl(int minZoomLvl, TileSource ts) {337 if (minZoomLvl < MIN_ZOOM) {338 /*Main.debug("Min. zoom level should not be less than "+MIN_ZOOM+"! Setting to that.");*/339 minZoomLvl = MIN_ZOOM;340 }341 if (minZoomLvl > PROP_MAX_ZOOM_LVL.get()) {342 /*Main.debug("Min. zoom level should not be more than Max. zoom level! Setting to Max.");*/343 minZoomLvl = getMaxZoomLvl(ts);344 }345 if (ts != null && ts.getMinZoom() > minZoomLvl) {346 /*Main.debug("Increasing min. zoom level to match tile source");*/347 minZoomLvl = ts.getMinZoom();348 }349 return minZoomLvl;350 }351 352 public static int getMinZoomLvl(TileSource ts) {353 return checkMinZoomLvl(PROP_MIN_ZOOM_LVL.get(), ts);354 }355 356 public static void setMinZoomLvl(int minZoomLvl) {357 minZoomLvl = checkMinZoomLvl(minZoomLvl, null);358 PROP_MIN_ZOOM_LVL.put(minZoomLvl);359 }360 361 private static class CachedAttributionBingAerialTileSource extends BingAerialTileSource {362 363 public CachedAttributionBingAerialTileSource(ImageryInfo info) {364 super(info);365 }366 367 class BingAttributionData extends CacheCustomContent<IOException> {368 369 public BingAttributionData() {370 super("bing.attribution.xml", CacheCustomContent.INTERVAL_HOURLY);371 }372 373 @Override374 protected byte[] updateData() throws IOException {375 URL u = getAttributionUrl();376 try (Scanner scanner = new Scanner(UTFInputStreamReader.create(Utils.openURL(u)))) {377 String r = scanner.useDelimiter("\\A").next();378 Main.info("Successfully loaded Bing attribution data.");379 return r.getBytes("UTF-8");380 }381 }382 }383 384 @Override385 protected Callable<List<Attribution>> getAttributionLoaderCallable() {386 return new Callable<List<Attribution>>() {387 388 @Override389 public List<Attribution> call() throws Exception {390 BingAttributionData attributionLoader = new BingAttributionData();391 int waitTimeSec = 1;392 while (true) {393 try {394 String xml = attributionLoader.updateIfRequiredString();395 return parseAttributionText(new InputSource(new StringReader(xml)));396 } catch (IOException ex) {397 Main.warn("Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds.");398 Thread.sleep(waitTimeSec * 1000L);399 waitTimeSec *= 2;400 }401 }402 }403 };404 }405 }406 407 /**408 * Creates and returns a new TileSource instance depending on the {@link ImageryType}409 * of the passed ImageryInfo object.410 *411 * If no appropriate TileSource is found, null is returned.412 * Currently supported ImageryType are {@link ImageryType#TMS},413 * {@link ImageryType#BING}, {@link ImageryType#SCANEX}.414 *415 * @param info imagery info416 * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found.417 * @throws IllegalArgumentException if url from imagery info is null or invalid418 */419 public static TileSource getTileSource(ImageryInfo info) {420 if (info.getImageryType() == ImageryType.TMS) {421 checkUrl(info.getUrl());422 TMSTileSource t = new TemplatedTMSTileSource(info);423 info.setAttribution(t);424 return t;425 } else if (info.getImageryType() == ImageryType.BING) {426 return new CachedAttributionBingAerialTileSource(info);427 } else if (info.getImageryType() == ImageryType.SCANEX) {428 return new ScanexTileSource(info);429 }430 return null;431 }432 433 /**434 * Checks validity of given URL.435 * @param url URL to check436 * @throws IllegalArgumentException if url is null or invalid437 */438 public static void checkUrl(String url) {439 CheckParameterUtil.ensureParameterNotNull(url, "url");440 Matcher m = Pattern.compile("\\{[^}]*\\}").matcher(url);441 while (m.find()) {442 boolean isSupportedPattern = false;443 for (String pattern : TemplatedTMSTileSource.ALL_PATTERNS) {444 if (m.group().matches(pattern)) {445 isSupportedPattern = true;446 break;447 }448 }449 if (!isSupportedPattern) {450 throw new IllegalArgumentException(451 tr("{0} is not a valid TMS argument. Please check this server URL:\n{1}", m.group(), url));452 }453 }454 }455 456 private void initTileSource(TileSource tileSource) {457 this.tileSource = tileSource;458 attribution.initialize(tileSource);459 460 currentZoomLevel = getBestZoom();461 462 Map<String, String> headers = null;463 if (tileSource instanceof TemplatedTMSTileSource) {464 headers = (((TemplatedTMSTileSource) tileSource).getHeaders());465 }466 467 tileLoader = loaderFactory.makeTileLoader(this, headers);468 if (tileLoader instanceof TMSCachedTileLoader) {469 tileCache = (TileCache) tileLoader;470 } else {471 tileCache = new MemoryTileCache();472 }473 if (tileLoader == null)474 tileLoader = new OsmTileLoader(this);475 }476 477 237 /** 478 238 * Marks layer as needing redraw on offset change … … 483 243 needRedraw = true; 484 244 } 245 246 485 247 /** 486 248 * Returns average number of screen pixels per tile pixel for current mapview … … 491 253 LatLon topLeft = mv.getLatLon(0, 0); 492 254 LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight()); 493 double x1 = tileSource.lonToTileX(topLeft.lon(), zoom); 494 double y1 = tileSource.latToTileY(topLeft.lat(), zoom); 495 double x2 = tileSource.lonToTileX(botRight.lon(), zoom); 496 double y2 = tileSource.latToTileY(botRight.lat(), zoom); 255 TileXY t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom); 256 TileXY t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom); 497 257 498 258 int screenPixels = mv.getWidth()*mv.getHeight(); 499 double tilePixels = Math.abs(( y2-y1)*(x2-x1)*tileSource.getTileSize()*tileSource.getTileSize());259 double tilePixels = Math.abs((t2.getY()-t1.getY())*(t2.getX()-t1.getX())*tileSource.getTileSize()*tileSource.getTileSize()); 500 260 if (screenPixels == 0 || tilePixels == 0) return 1; 501 261 return screenPixels/tilePixels; 502 262 } 503 263 504 private int getBestZoom() { 264 private final int getBestZoom() { 505 265 double factor = getScaleFactor(1); // check the ratio between area of tilesize at zoom 1 to current view 506 266 double result = Math.log(factor)/Math.log(2)/2+1; … … 515 275 * getScaleFactor(...) is supposed to be between 0.75 and 3 516 276 */ 517 int intResult = (int) 277 int intResult = (int)Math.floor(result); 518 278 if (intResult > getMaxZoomLvl()) 519 279 return getMaxZoomLvl(); … … 523 283 } 524 284 525 @SuppressWarnings("serial") 526 public TMSLayer(ImageryInfo info) { 527 super(info); 528 529 if (!isProjectionSupported(Main.getProjection())) { 530 JOptionPane.showMessageDialog(Main.parent, 531 tr("TMS layers do not support the projection {0}.\n{1}\n" 532 + "Change the projection or remove the layer.", 533 Main.getProjection().toCode(), nameSupportedProjections()), 534 tr("Warning"), 535 JOptionPane.WARNING_MESSAGE); 536 } 537 538 setBackgroundLayer(true); 539 this.setVisible(true); 540 541 TileSource source = getTileSource(info); 542 if (source == null) 543 throw new IllegalStateException("Cannot create TMSLayer with non-TMS ImageryInfo"); 544 initTileSource(source); 545 546 MapView.addZoomChangeListener(this); 547 } 548 549 /** 550 * Adds a context menu to the mapView. 285 286 private final static boolean actionSupportLayers(List<Layer> layers) { 287 return layers.size() == 1 && layers.get(0) instanceof TMSLayer; 288 } 289 290 private class AutoZoomAction extends AbstractAction implements LayerAction { 291 public AutoZoomAction() { 292 super(tr("Auto Zoom")); 293 } 294 @Override 295 public void actionPerformed(ActionEvent ae) { 296 autoZoom = !autoZoom; 297 } 298 299 public Component createMenuComponent() { 300 JCheckBoxMenuItem item = new JCheckBoxMenuItem(this); 301 item.setSelected(autoZoom); 302 return item; 303 } 304 @Override 305 public boolean supportLayers(List<Layer> layers) { 306 return actionSupportLayers(layers); 307 } 308 309 } 310 311 private class AutoLoadTilesAction extends AbstractAction implements LayerAction { 312 public AutoLoadTilesAction() { 313 super(tr("Auto load tiles")); 314 } 315 @Override 316 public void actionPerformed(ActionEvent ae) { 317 autoLoad= !autoLoad; 318 } 319 320 public Component createMenuComponent() { 321 JCheckBoxMenuItem item = new JCheckBoxMenuItem(this); 322 item.setSelected(autoLoad); 323 return item; 324 } 325 @Override 326 public boolean supportLayers(List<Layer> layers) { 327 return actionSupportLayers(layers); 328 } 329 } 330 331 private class LoadAllTilesAction extends AbstractAction { 332 public LoadAllTilesAction() { 333 super(tr("Load All Tiles")); 334 } 335 @Override 336 public void actionPerformed(ActionEvent ae) { 337 loadAllTiles(true); 338 redraw(); 339 } 340 } 341 342 private class LoadErroneusTilesAction extends AbstractAction { 343 public LoadErroneusTilesAction() { 344 super(tr("Load All Error Tiles")); 345 } 346 347 @Override 348 public void actionPerformed(ActionEvent ae) { 349 loadAllErrorTiles(true); 350 redraw(); 351 } 352 } 353 354 private class ZoomToNativeLevelAction extends AbstractAction { 355 public ZoomToNativeLevelAction() { 356 super(tr("Zoom to native resolution")); 357 } 358 @Override 359 public void actionPerformed(ActionEvent ae) { 360 double new_factor = Math.sqrt(getScaleFactor(currentZoomLevel)); 361 Main.map.mapView.zoomToFactor(new_factor); 362 redraw(); 363 } 364 } 365 366 private class ZoomToBestAction extends AbstractAction { 367 public ZoomToBestAction() { 368 super(tr("Change resolution")); 369 } 370 @Override 371 public void actionPerformed(ActionEvent ae) { 372 setZoomLevel(getBestZoom()); 373 } 374 } 375 376 /* 377 * Simple class to keep clickedTile within hookUpMapView 378 */ 379 private class TileHolder { 380 private Tile t = null; 381 382 public Tile getTile() { 383 return t; 384 } 385 386 public void setTile(Tile t) { 387 this.t = t; 388 } 389 } 390 391 /** 392 * Creates popup menu items and binds to mouse actions 551 393 */ 552 394 @Override 553 395 public void hookUpMapView() { 554 tileOptionMenu = new JPopupMenu(); 396 // keep them final here, so we avoid namespace clutter in the class 397 final JPopupMenu tileOptionMenu = new JPopupMenu(); 398 final TileHolder clickedTileHolder = new TileHolder(); 555 399 556 400 autoZoom = PROP_DEFAULT_AUTOZOOM.get(); 557 401 JCheckBoxMenuItem autoZoomPopup = new JCheckBoxMenuItem(); 558 autoZoomPopup.setAction(new AbstractAction(tr("Auto Zoom")) { 559 @Override 560 public void actionPerformed(ActionEvent ae) { 561 autoZoom = !autoZoom; 562 } 563 }); 402 autoZoomPopup.setAction(new AutoZoomAction()); 564 403 autoZoomPopup.setSelected(autoZoom); 565 404 tileOptionMenu.add(autoZoomPopup); … … 567 406 autoLoad = PROP_DEFAULT_AUTOLOAD.get(); 568 407 JCheckBoxMenuItem autoLoadPopup = new JCheckBoxMenuItem(); 569 autoLoadPopup.setAction(new AbstractAction(tr("Auto load tiles")) { 570 @Override 571 public void actionPerformed(ActionEvent ae) { 572 autoLoad = !autoLoad; 573 } 574 }); 408 autoLoadPopup.setAction(new AutoLoadTilesAction()); 575 409 autoLoadPopup.setSelected(autoLoad); 576 410 tileOptionMenu.add(autoLoadPopup); … … 590 424 @Override 591 425 public void actionPerformed(ActionEvent ae) { 426 Tile clickedTile = clickedTileHolder.getTile(); 592 427 if (clickedTile != null) { 593 428 loadTile(clickedTile, true); … … 597 432 })); 598 433 599 tileOptionMenu.add(new JMenuItem(new ShowTileInfoAction()));600 601 434 tileOptionMenu.add(new JMenuItem(new AbstractAction( 602 tr("Request Update")) { 435 tr("Show Tile Info")) { 436 private String getSizeString(int size) { 437 StringBuilder ret = new StringBuilder(); 438 return ret.append(size).append("x").append(size).toString(); 439 } 440 441 private JTextField createTextField(String text) { 442 JTextField ret = new JTextField(text); 443 ret.setEditable(false); 444 ret.setBorder(BorderFactory.createEmptyBorder()); 445 return ret; 446 } 447 603 448 @Override 604 449 public void actionPerformed(ActionEvent ae) { 450 Tile clickedTile = clickedTileHolder.getTile(); 605 451 if (clickedTile != null) { 606 clickedTile.setLoaded(false); 607 tileLoader.createTileLoaderJob(clickedTile).submit(true); 452 ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Tile Info"), new String[]{tr("OK")}); 453 JPanel panel = new JPanel(new GridBagLayout()); 454 Rectangle displaySize = tileToRect(clickedTile); 455 String url = ""; 456 try { 457 url = clickedTile.getUrl(); 458 } catch (IOException e) { 459 // silence exceptions 460 } 461 462 String[][] content = { 463 {"Tile name", clickedTile.getKey()}, 464 {"Tile url", url}, 465 {"Tile size", getSizeString(clickedTile.getTileSource().getTileSize()) }, 466 {"Tile display size", new StringBuilder().append(displaySize.width).append("x").append(displaySize.height).toString()}, 467 }; 468 469 for (String[] entry: content) { 470 panel.add(new JLabel(tr(entry[0]) + ":"), GBC.std()); 471 panel.add(GBC.glue(5,0), GBC.std()); 472 panel.add(createTextField(entry[1]), GBC.eol().fill(GBC.HORIZONTAL)); 473 } 474 475 for (Entry<String, String> e: clickedTile.getMetadata().entrySet()) { 476 panel.add(new JLabel(tr("Metadata ") + tr(e.getKey()) + ":"), GBC.std()); 477 panel.add(GBC.glue(5,0), GBC.std()); 478 String value = e.getValue(); 479 if ("lastModification".equals(e.getKey()) || "expirationTime".equals(e.getKey())) { 480 value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(Long.parseLong(value))); 481 } 482 panel.add(createTextField(value), GBC.eol().fill(GBC.HORIZONTAL)); 483 484 } 485 ed.setIcon(JOptionPane.INFORMATION_MESSAGE); 486 ed.setContent(panel); 487 ed.showDialog(); 608 488 } 609 489 } 610 490 })); 611 491 612 tileOptionMenu.add(new JMenuItem(new AbstractAction( 613 tr("Load All Tiles")) { 614 @Override 615 public void actionPerformed(ActionEvent ae) { 616 loadAllTiles(true); 617 redraw(); 618 } 619 })); 620 621 tileOptionMenu.add(new JMenuItem(new AbstractAction( 622 tr("Load All Error Tiles")) { 623 @Override 624 public void actionPerformed(ActionEvent ae) { 625 loadAllErrorTiles(true); 626 redraw(); 627 } 628 })); 492 tileOptionMenu.add(new JMenuItem(new LoadAllTilesAction())); 493 tileOptionMenu.add(new JMenuItem(new LoadErroneusTilesAction())); 629 494 630 495 // increase and decrease commands … … 663 528 new PleaseWaitRunnable(tr("Flush Tile Cache")) { 664 529 @Override 665 protected void realRun() throws SAXException, IOException, 666 OsmTransferException { 530 protected void realRun() { 667 531 clearTileCache(getProgressMonitor()); 668 532 } … … 684 548 if (!isVisible()) return; 685 549 if (e.getButton() == MouseEvent.BUTTON3) { 686 clickedTile =getTileForPixelpos(e.getX(), e.getY());550 clickedTileHolder.setTile(getTileForPixelpos(e.getX(), e.getY())); 687 551 tileOptionMenu.show(e.getComponent(), e.getX(), e.getY()); 688 552 } else if (e.getButton() == MouseEvent.BUTTON1) { … … 706 570 @Override 707 571 public void layerRemoved(Layer oldLayer) { 708 if (oldLayer == TMSLayer.this) {572 if (oldLayer == AbstractTileSourceLayer.this) { 709 573 Main.map.mapView.removeMouseListener(adapter); 710 MapView.removeZoomChangeListener(TMSLayer.this);711 574 MapView.removeLayerChangeListener(this); 575 MapView.removeZoomChangeListener(AbstractTileSourceLayer.this); 712 576 } 713 577 } 714 578 }); 715 579 } 580 581 /** 582 * Checks zoom level against settings 583 * @param maxZoomLvl zoom level to check 584 * @param ts tile source to crosscheck with 585 * @return maximum zoom level, not higher than supported by tilesource nor set by the user 586 */ 587 public static int checkMaxZoomLvl(int maxZoomLvl, TileSource ts) { 588 if(maxZoomLvl > MAX_ZOOM) { 589 maxZoomLvl = MAX_ZOOM; 590 } 591 if(maxZoomLvl < PROP_MIN_ZOOM_LVL.get()) { 592 maxZoomLvl = PROP_MIN_ZOOM_LVL.get(); 593 } 594 if (ts != null && ts.getMaxZoom() != 0 && ts.getMaxZoom() < maxZoomLvl) { 595 maxZoomLvl = ts.getMaxZoom(); 596 } 597 return maxZoomLvl; 598 } 599 600 /** 601 * Checks zoom level against settings 602 * @param minZoomLvl zoom level to check 603 * @param ts tile source to crosscheck with 604 * @return minimum zoom level, not higher than supported by tilesource nor set by the user 605 */ 606 public static int checkMinZoomLvl(int minZoomLvl, TileSource ts) { 607 if(minZoomLvl < MIN_ZOOM) { 608 minZoomLvl = MIN_ZOOM; 609 } 610 if(minZoomLvl > PROP_MAX_ZOOM_LVL.get()) { 611 minZoomLvl = getMaxZoomLvl(ts); 612 } 613 if (ts != null && ts.getMinZoom() > minZoomLvl) { 614 minZoomLvl = ts.getMinZoom(); 615 } 616 return minZoomLvl; 617 } 618 619 620 /** 621 * @param ts TileSource for which we want to know maximum zoom level 622 * @return maximum max zoom level, that will be shown on layer 623 */ 624 public static int getMaxZoomLvl(TileSource ts) { 625 return checkMaxZoomLvl(PROP_MAX_ZOOM_LVL.get(), ts); 626 } 627 628 /** 629 * @param ts TileSource for which we want to know minimum zoom level 630 * @return minimum zoom level, that will be shown on layer 631 */ 632 public static int getMinZoomLvl(TileSource ts) { 633 return checkMinZoomLvl(PROP_MIN_ZOOM_LVL.get(), ts); 634 } 635 636 637 /** 638 * Sets maximum zoom level, that layer will attempt show 639 * @param maxZoomLvl 640 */ 641 public static void setMaxZoomLvl(int maxZoomLvl) { 642 maxZoomLvl = checkMaxZoomLvl(maxZoomLvl, null); 643 PROP_MAX_ZOOM_LVL.put(maxZoomLvl); 644 } 645 646 /** 647 * Sets minimum zoom level, that layer will attempt show 648 * @param minZoomLvl 649 */ 650 public static void setMinZoomLvl(int minZoomLvl) { 651 minZoomLvl = checkMinZoomLvl(minZoomLvl, null); 652 PROP_MIN_ZOOM_LVL.put(minZoomLvl); 653 } 654 716 655 717 656 /** … … 724 663 Main.debug("zoomChanged(): " + currentZoomLevel); 725 664 } 665 if (tileLoader instanceof TMSCachedTileLoader) { 666 ((TMSCachedTileLoader)tileLoader).cancelOutstandingTasks(); 667 } 726 668 needRedraw = true; 727 if (tileLoader instanceof TMSCachedTileLoader) {728 ((TMSCachedTileLoader) tileLoader).cancelOutstandingTasks();729 }730 669 } 731 670 … … 742 681 743 682 /** 683 * 684 * @return if its allowed to zoom in 685 */ 686 public boolean zoomIncreaseAllowed() { 687 boolean zia = currentZoomLevel < this.getMaxZoomLvl(); 688 if (Main.isDebugEnabled()) { 689 Main.debug("zoomIncreaseAllowed(): " + zia + " " + currentZoomLevel + " vs. " + this.getMaxZoomLvl() ); 690 } 691 return zia; 692 } 693 694 /** 744 695 * Zoom in, go closer to map. 745 696 * 746 697 * @return true, if zoom increasing was successful, false otherwise 747 698 */ 748 public boolean zoomIncreaseAllowed() {749 boolean zia = currentZoomLevel < this.getMaxZoomLvl();750 if (Main.isDebugEnabled()) {751 Main.debug("zoomIncreaseAllowed(): " + zia + " " + currentZoomLevel + " vs. " + this.getMaxZoomLvl());752 }753 return zia;754 }755 756 699 public boolean increaseZoomLevel() { 757 700 if (zoomIncreaseAllowed()) { … … 769 712 } 770 713 714 /** 715 * Sets the zoom level of the layer 716 * @param zoom zoom level 717 * @return true, when zoom has changed to desired value, false if it was outside supported zoom levels 718 */ 771 719 public boolean setZoomLevel(int zoom) { 772 720 if (zoom == currentZoomLevel) return true; … … 837 785 */ 838 786 private Tile getTile(int x, int y, int zoom) { 839 int max = 1 << zoom; 787 int max = (1 << zoom); 840 788 if (x < 0 || x >= max || y < 0 || y >= max) 841 789 return null; … … 850 798 if (tile.isLoading()) 851 799 return false; 852 tileLoader.createTileLoaderJob(tile).submit( force);800 tileLoader.createTileLoaderJob(tile).submit(); 853 801 return true; 854 802 } 855 803 856 private void loadAllTiles(boolean force) {804 private TileSet getVisibleTileSet() { 857 805 MapView mv = Main.map.mapView; 858 806 EastNorth topLeft = mv.getEastNorth(0, 0); 859 807 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight()); 860 861 TileSet ts = new TileSet(topLeft, botRight, currentZoomLevel); 808 return new TileSet(topLeft, botRight, currentZoomLevel); 809 } 810 811 private void loadAllTiles(boolean force) { 812 TileSet ts = getVisibleTileSet(); 862 813 863 814 // if there is more than 18 tiles on screen in any direction, do not … … 871 822 872 823 private void loadAllErrorTiles(boolean force) { 873 MapView mv = Main.map.mapView; 874 EastNorth topLeft = mv.getEastNorth(0, 0); 875 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight()); 876 877 TileSet ts = new TileSet(topLeft, botRight, currentZoomLevel); 878 824 TileSet ts = getVisibleTileSet(); 879 825 ts.loadAllErrorTiles(force); 880 826 } … … 914 860 return null; 915 861 return img; 916 }917 918 private LatLon tileLatLon(Tile t) {919 int zoom = t.getZoom();920 return new LatLon(tileSource.tileYToLat(t.getYtile(), zoom),921 tileSource.tileXToLon(t.getXtile(), zoom));922 862 } 923 863 … … 968 908 // And how many pixels into the image itself does that 969 909 // correlate to? 970 int img_x_offset = (int) 971 int img_y_offset = (int) 910 int img_x_offset = (int)(screen_x_offset * imageXScaling + 0.5); 911 int img_y_offset = (int)(screen_y_offset * imageYScaling + 0.5); 972 912 // Now calculate the other corner of the image that we need 973 913 // by scaling the 'target' rectangle's dimensions. 974 int img_x_end = img_x_offset + (int) 975 int img_y_end = img_y_offset + (int) 914 int img_x_end = img_x_offset + (int)(target.getWidth() * imageXScaling + 0.5); 915 int img_y_end = img_y_offset + (int)(target.getHeight() * imageYScaling + 0.5); 976 916 977 917 if (Main.isDebugEnabled()) { … … 1035 975 Color oldColor = g.getColor(); 1036 976 g.setColor(Color.black); 1037 g.drawString(text, x+1,y+1);977 g.drawString(text,x+1,y+1); 1038 978 g.setColor(oldColor); 1039 g.drawString(text, x,y);979 g.drawString(text,x,y); 1040 980 } 1041 981 … … 1055 995 } 1056 996 }*/ 1057 1058 if (tile == showMetadataTile) {1059 String md = tile.toString();1060 if (md != null) {1061 myDrawString(g, md, p.x + 2, texty);1062 texty += 1 + fontHeight;1063 }1064 Map<String, String> meta = tile.getMetadata();1065 if (meta != null) {1066 for (Map.Entry<String, String> entry : meta.entrySet()) {1067 myDrawString(g, entry.getKey() + ": " + entry.getValue(), p.x + 2, texty);1068 texty += 1 + fontHeight;1069 }1070 }1071 }1072 997 1073 998 /*String tileStatus = tile.getStatus(); … … 1113 1038 1114 1039 private Point pixelPos(Tile t) { 1115 double lon = tileSource.tileXToLon(t.getXtile(), t.getZoom()); 1116 LatLon tmpLL = new LatLon(tileSource.tileYToLat(t.getYtile(), t.getZoom()), lon); 1117 return pixelPos(tmpLL); 1040 ICoordinate coord = tileSource.tileXYToLatLon(t); 1041 return pixelPos(new LatLon(coord)); 1118 1042 } 1119 1043 … … 1124 1048 private Coordinate getShiftedCoord(EastNorth en) { 1125 1049 LatLon ll = getShiftedLatLon(en); 1126 return new Coordinate(ll.lat(), ll.lon()); 1127 } 1128 1129 private final TileSet nullTileSet = new TileSet((LatLon) null, (LatLon) null, 0); 1130 1050 return new Coordinate(ll.lat(),ll.lon()); 1051 } 1052 1053 private final TileSet nullTileSet = new TileSet((LatLon)null, (LatLon)null, 0); 1131 1054 private final class TileSet { 1132 private int x0, x1, y0, y1; 1133 private int zoom; 1134 private int tileMax = -1; 1055 int x0, x1, y0, y1; 1056 int zoom; 1135 1057 1136 1058 /** … … 1138 1060 */ 1139 1061 private TileSet(EastNorth topLeft, EastNorth botRight, int zoom) { 1140 this(getShiftedLatLon(topLeft), getShiftedLatLon(botRight), 1062 this(getShiftedLatLon(topLeft), getShiftedLatLon(botRight),zoom); 1141 1063 } 1142 1064 … … 1149 1071 return; 1150 1072 1151 x0 = (int) tileSource.lonToTileX(topLeft.lon(), zoom); 1152 y0 = (int) tileSource.latToTileY(topLeft.lat(), zoom); 1153 x1 = (int) tileSource.lonToTileX(botRight.lon(), zoom); 1154 y1 = (int) tileSource.latToTileY(botRight.lat(), zoom); 1073 TileXY t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom); 1074 TileXY t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom); 1075 1076 x0 = t1.getXIndex(); 1077 y0 = t1.getYIndex(); 1078 x1 = t2.getXIndex(); 1079 y1 = t2.getYIndex(); 1080 1155 1081 if (x0 > x1) { 1156 1082 int tmp = x0; … … 1163 1089 y1 = tmp; 1164 1090 } 1165 tileMax = (int) Math.pow(2.0, zoom); 1166 if (x0 < 0) {1167 x0 = 0;1168 } 1169 if (y0 < 0) {1170 y0 = 0;1171 } 1172 if (x1 > tile Max) {1173 x1 = tile Max;1174 } 1175 if (y1 > tile Max) {1176 y1 = tile Max;1091 1092 if (x0 < tileSource.getTileXMin(zoom)) { 1093 x0 = tileSource.getTileXMin(zoom); 1094 } 1095 if (y0 < tileSource.getTileYMin(zoom)) { 1096 y0 = tileSource.getTileYMin(zoom); 1097 } 1098 if (x1 > tileSource.getTileXMax(zoom)) { 1099 x1 = tileSource.getTileXMax(zoom); 1100 } 1101 if (y1 > tileSource.getTileYMax(zoom)) { 1102 y1 = tileSource.getTileYMax(zoom); 1177 1103 } 1178 1104 } … … 1221 1147 Tile t; 1222 1148 if (create) { 1223 t = getOrCreateTile(x % tileMax, y % tileMax, zoom);1149 t = getOrCreateTile(x, y , zoom); 1224 1150 } else { 1225 t = getTile(x % tileMax, y % tileMax, zoom);1151 t = getTile(x, y, zoom); 1226 1152 } 1227 1153 if (t != null) { … … 1242 1168 } 1243 1169 1170 /** 1171 * @return comparator, that sorts the tiles from the center to the edge of the current screen 1172 */ 1244 1173 private Comparator<Tile> getTileDistanceComparator() { 1245 final int centerX = (int) Math.ceil((x0 + x1) / 2); 1246 final int centerY = (int) Math.ceil((y0 + y1) / 2); 1174 final int centerX = (int) Math.ceil((x0 + x1) / 2d); 1175 final int centerY = (int) Math.ceil((y0 + y1) / 2d); 1247 1176 return new Comparator<Tile>() { 1248 1177 private int getDistance(Tile t) { 1249 1178 return Math.abs(t.getXtile() - centerX) + Math.abs(t.getYtile() - centerY); 1250 1179 } 1251 1252 1180 @Override 1253 1181 public int compare(Tile o1, Tile o2) { … … 1258 1186 }; 1259 1187 } 1188 1260 1189 1261 1190 private void loadAllTiles(boolean force) { … … 1279 1208 } 1280 1209 } 1210 1281 1211 1282 1212 private static class TileSetInfo { … … 1310 1240 private final TileSet[] tileSets; 1311 1241 private final TileSetInfo[] tileSetInfos; 1312 1313 1242 public DeepTileSet(EastNorth topLeft, EastNorth botRight, int minZoom, int maxZoom) { 1314 1243 this.topLeft = topLeft; … … 1319 1248 this.tileSetInfos = new TileSetInfo[maxZoom - minZoom + 1]; 1320 1249 } 1321 1322 1250 public TileSet getTileSet(int zoom) { 1323 1251 if (zoom < minZoom) … … 1339 1267 TileSetInfo tsi = tileSetInfos[zoom-minZoom]; 1340 1268 if (tsi == null) { 1341 tsi = TMSLayer.getTileSetInfo(getTileSet(zoom));1269 tsi = AbstractTileSourceLayer.getTileSetInfo(getTileSet(zoom)); 1342 1270 tileSetInfos[zoom-minZoom] = tsi; 1343 1271 } … … 1388 1316 } 1389 1317 // Do binary search between currentZoomLevel and displayZoomLevel 1390 while (zoom > displayZoomLevel && !tsi.hasVisibleTiles && tsi.hasOverzoomedTiles) 1318 while (zoom > displayZoomLevel && !tsi.hasVisibleTiles && tsi.hasOverzoomedTiles){ 1391 1319 zoom = (zoom + displayZoomLevel)/2; 1392 1320 tsi = dts.getTileSetInfo(zoom); … … 1445 1373 } 1446 1374 Tile t2 = tempCornerTile(missed); 1447 LatLon topLeft2 = tileLatLon(missed);1448 LatLon botRight2 = tileLatLon(t2);1375 LatLon topLeft2 = new LatLon(tileSource.tileXYToLatLon(missed)); 1376 LatLon botRight2 = new LatLon(tileSource.tileXYToLatLon(t2)); 1449 1377 TileSet ts2 = new TileSet(topLeft2, botRight2, newzoom); 1450 1378 // Instantiating large TileSets is expensive. If there … … 1494 1422 myDrawString(g, tr("Pixel scale: {0}", getScaleFactor(currentZoomLevel)), 50, 170); 1495 1423 myDrawString(g, tr("Best zoom: {0}", getBestZoom()), 50, 185); 1496 if 1497 TMSCachedTileLoader cachedTileLoader = (TMSCachedTileLoader) 1424 if(tileLoader instanceof TMSCachedTileLoader) { 1425 TMSCachedTileLoader cachedTileLoader = (TMSCachedTileLoader)tileLoader; 1498 1426 int offset = 185; 1499 for (String part: cachedTileLoader.getStats().split("\n")) { 1500 myDrawString(g, tr("Cache stats: {0}", part), 50, offset += 15); 1501 } 1427 for(String part: cachedTileLoader.getStats().split("\n")) { 1428 myDrawString(g, tr("Cache stats: {0}", part), 50, offset+=15); 1429 } 1430 1502 1431 } 1503 1432 } … … 1546 1475 public Action[] getMenuEntries() { 1547 1476 return new Action[] { 1477 LayerListDialog.getInstance().createActivateLayerAction(this), 1548 1478 LayerListDialog.getInstance().createShowHideLayerAction(), 1549 1479 LayerListDialog.getInstance().createDeleteLayerAction(), … … 1553 1483 new RenameLayerAction(this.getAssociatedFile(), this), 1554 1484 SeparatorLayerAction.INSTANCE, 1555 new LayerListPopup.InfoAction(this) }; 1485 new AutoLoadTilesAction(), 1486 new AutoZoomAction(), 1487 new ZoomToBestAction(), 1488 new ZoomToNativeLevelAction(), 1489 new LoadErroneusTilesAction(), 1490 new LoadAllTilesAction(), 1491 new LayerListPopup.InfoAction(this) 1492 }; 1556 1493 } 1557 1494 1558 1495 @Override 1559 1496 public String getToolTipText() { 1560 return tr("TMS layer ({0}), downloading in zoom {1}", getName(), currentZoomLevel); 1497 if(autoLoad) { 1498 return tr("{0} ({1}), automatically downloading in zoom {2}", this.getClass().getSimpleName(), getName(), currentZoomLevel); 1499 } else { 1500 return tr("{0} ({1}), downloading in zoom {2}", this.getClass().getSimpleName(), getName(), currentZoomLevel); 1501 } 1561 1502 } 1562 1503 … … 1570 1511 } 1571 1512 1513 /** 1514 * Task responsible for precaching imagery along the gpx track 1515 * 1516 */ 1517 public class PrecacheTask implements TileLoaderListener { 1518 private final ProgressMonitor progressMonitor; 1519 private volatile int totalCount; 1520 private volatile int processedCount = 0; 1521 private TileLoader tileLoader; 1522 1523 /** 1524 * @param progressMonitor that will be notified about progess of the task 1525 */ 1526 public PrecacheTask(ProgressMonitor progressMonitor) { 1527 this.progressMonitor = progressMonitor; 1528 this.tileLoader = getTileLoaderFactory().makeTileLoader(this, getHeaders(tileSource)); 1529 if (this.tileLoader instanceof TMSCachedTileLoader) { 1530 ((TMSCachedTileLoader) this.tileLoader).setDownloadExecutor( 1531 TMSCachedTileLoader.getNewThreadPoolExecutor("Precache downloader")); 1532 } 1533 1534 } 1535 1536 /** 1537 * @return true, if all is done 1538 */ 1539 public boolean isFinished() { 1540 return processedCount >= totalCount; 1541 } 1542 1543 /** 1544 * @return total number of tiles to download 1545 */ 1546 public int getTotalCount() { 1547 return totalCount; 1548 } 1549 1550 /** 1551 * cancel the task 1552 */ 1553 public void cancel() { 1554 if (tileLoader instanceof TMSCachedTileLoader) { 1555 ((TMSCachedTileLoader)tileLoader).cancelOutstandingTasks(); 1556 } 1557 } 1558 1559 1560 @Override 1561 public void tileLoadingFinished(Tile tile, boolean success) { 1562 if (success) { 1563 this.processedCount++; 1564 this.progressMonitor.worked(1); 1565 this.progressMonitor.setCustomText(tr("Downloaded {0}/{1} tiles", processedCount, totalCount)); 1566 } 1567 } 1568 1569 /** 1570 * @return tile loader that is used to load the tiles 1571 */ 1572 public TileLoader getTileLoader() { 1573 return tileLoader; 1574 } 1575 } 1576 1577 /** 1578 * Calculates tiles, that needs to be downloaded to cache, gets a current tile loader and creates a task to download 1579 * all of the tiles. Buffer contains at least one tile. 1580 * 1581 * To prevent accidental clear of the queue, new download executor is created with separate queue 1582 * 1583 * @param precacheTask 1584 * @param points 1585 * @param bufferX how many units in current Coordinate Reference System to cover in X axis in both sides 1586 * @param bufferY how many units in current Coordinate Reference System to cover in Y axis in both sides 1587 */ 1588 public void downloadAreaToCache(final PrecacheTask precacheTask, List<LatLon> points, double bufferX, double bufferY) { 1589 final Set<Tile> requestedTiles = new ConcurrentSkipListSet <>(new Comparator<Tile>() { 1590 public int compare(Tile o1, Tile o2) { 1591 return String.CASE_INSENSITIVE_ORDER.compare(o1.getKey(), o2.getKey()); 1592 } 1593 }); 1594 for (LatLon point: points) { 1595 1596 TileXY minTile = tileSource.latLonToTileXY(point.lat() - bufferY, point.lon() - bufferX, currentZoomLevel); 1597 TileXY curTile = tileSource.latLonToTileXY(point.toCoordinate(), currentZoomLevel); 1598 TileXY maxTile = tileSource.latLonToTileXY(point.lat() + bufferY, point.lon() + bufferX, currentZoomLevel); 1599 1600 // take at least one tile of buffer 1601 int minY = Math.min(curTile.getYIndex() - 1, minTile.getYIndex()); 1602 int maxY = Math.max(curTile.getYIndex() + 1, maxTile.getYIndex()); 1603 int minX = Math.min(curTile.getXIndex() - 1, minTile.getXIndex()); 1604 int maxX = Math.min(curTile.getXIndex() + 1, minTile.getXIndex()); 1605 1606 for (int x= minX; x<=maxX; x++) { 1607 for (int y= minY; y<=maxY; y++) { 1608 requestedTiles.add(new Tile(tileSource, x, y, currentZoomLevel)); 1609 } 1610 } 1611 } 1612 1613 precacheTask.totalCount = requestedTiles.size(); 1614 precacheTask.progressMonitor.setTicksCount(requestedTiles.size()); 1615 1616 TileLoader loader = precacheTask.getTileLoader(); 1617 for (Tile t: requestedTiles) { 1618 loader.createTileLoaderJob(t).submit(); 1619 } 1620 } 1621 1572 1622 @Override 1573 public finalboolean isProjectionSupported(Projection proj) {1574 return "EPSG:3857".equals(proj.toCode()) || "EPSG:4326".equals(proj.toCode());1623 public boolean isSavable() { 1624 return true; // With WMSLayerExporter 1575 1625 } 1576 1626 1577 1627 @Override 1578 public final String nameSupportedProjections() {1579 return tr("EPSG:4326 and Mercator projection are supported");1628 public File createAndOpenSaveFileChooser() { 1629 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save WMS file"), WMSLayerImporter.FILE_FILTER); 1580 1630 } 1581 1631 } -
trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java
r8513 r8526 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.Color;7 import java.awt.Font;8 import java.awt.Graphics;9 import java.awt.Graphics2D;10 import java.awt.GridBagLayout;11 import java.awt.Image;12 import java.awt.Point;13 import java.awt.Rectangle;14 import java.awt.Toolkit;15 import java.awt.event.ActionEvent;16 import java.awt.event.MouseAdapter;17 import java.awt.event.MouseEvent;18 import java.awt.image.ImageObserver;19 import java.io.File;20 6 import java.io.IOException; 21 import java.io.StringReader;22 import java.net.URL;23 import java.text.SimpleDateFormat;24 import java.util.ArrayList;25 import java.util.Collections;26 import java.util.Comparator;27 import java.util.Date;28 import java.util.HashMap;29 import java.util.LinkedList;30 import java.util.List;31 7 import java.util.Map; 32 import java.util.Map.Entry;33 import java.util.Scanner;34 import java.util.concurrent.Callable;35 import java.util.regex.Matcher;36 import java.util.regex.Pattern;37 8 38 import javax.swing.AbstractAction;39 import javax.swing.Action;40 import javax.swing.BorderFactory;41 import javax.swing.JCheckBoxMenuItem;42 import javax.swing.JLabel;43 import javax.swing.JMenuItem;44 import javax.swing.JOptionPane;45 import javax.swing.JPanel;46 import javax.swing.JPopupMenu;47 import javax.swing.JTextField;48 49 import org.openstreetmap.gui.jmapviewer.AttributionSupport;50 import org.openstreetmap.gui.jmapviewer.Coordinate;51 import org.openstreetmap.gui.jmapviewer.MemoryTileCache;52 import org.openstreetmap.gui.jmapviewer.OsmTileLoader;53 import org.openstreetmap.gui.jmapviewer.Tile;54 import org.openstreetmap.gui.jmapviewer.interfaces.CachedTileLoader;55 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;56 9 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 57 10 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 58 11 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 59 import org.openstreetmap.gui.jmapviewer.tilesources.BingAerialTileSource;60 12 import org.openstreetmap.gui.jmapviewer.tilesources.ScanexTileSource; 61 13 import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource; 62 14 import org.openstreetmap.gui.jmapviewer.tilesources.TemplatedTMSTileSource; 63 import org.openstreetmap.josm.Main; 64 import org.openstreetmap.josm.actions.RenameLayerAction; 65 import org.openstreetmap.josm.data.Bounds; 66 import org.openstreetmap.josm.data.Version; 67 import org.openstreetmap.josm.data.coor.EastNorth; 68 import org.openstreetmap.josm.data.coor.LatLon; 15 import org.openstreetmap.josm.data.imagery.CachedAttributionBingAerialTileSource; 16 import org.openstreetmap.josm.data.imagery.CachedTileLoaderFactory; 69 17 import org.openstreetmap.josm.data.imagery.ImageryInfo; 70 18 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; 71 19 import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader; 72 import org.openstreetmap.josm.data. osm.visitor.BoundingXYVisitor;20 import org.openstreetmap.josm.data.imagery.TileLoaderFactory; 73 21 import org.openstreetmap.josm.data.preferences.BooleanProperty; 74 22 import org.openstreetmap.josm.data.preferences.IntegerProperty; 75 import org.openstreetmap.josm.data.preferences.StringProperty;76 23 import org.openstreetmap.josm.data.projection.Projection; 77 import org.openstreetmap.josm.gui.ExtendedDialog; 78 import org.openstreetmap.josm.gui.MapFrame; 79 import org.openstreetmap.josm.gui.MapView; 80 import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 81 import org.openstreetmap.josm.gui.NavigatableComponent.ZoomChangeListener; 82 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 83 import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 84 import org.openstreetmap.josm.gui.dialogs.LayerListPopup; 85 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 86 import org.openstreetmap.josm.io.CacheCustomContent; 87 import org.openstreetmap.josm.io.OsmTransferException; 88 import org.openstreetmap.josm.io.UTFInputStreamReader; 89 import org.openstreetmap.josm.tools.CheckParameterUtil; 90 import org.openstreetmap.josm.tools.GBC; 91 import org.openstreetmap.josm.tools.Utils; 92 import org.xml.sax.InputSource; 93 import org.xml.sax.SAXException; 24 94 25 95 26 /** … … 102 33 * 103 34 */ 104 public class TMSLayer extends ImageryLayer implements ImageObserver, TileLoaderListener, ZoomChangeListener {105 p ublicstatic final String PREFERENCE_PREFIX = "imagery.tms";35 public class TMSLayer extends AbstractTileSourceLayer { 36 private static final String PREFERENCE_PREFIX = "imagery.tms"; 106 37 107 public static final int MAX_ZOOM = 30; 108 public static final int MIN_ZOOM = 2; 109 public static final int DEFAULT_MAX_ZOOM = 20; 110 public static final int DEFAULT_MIN_ZOOM = 2; 38 /** minimum zoom level for TMS layer */ 39 public static final IntegerProperty PROP_MIN_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".min_zoom_lvl", AbstractTileSourceLayer.PROP_MIN_ZOOM_LVL.get()); 40 /** maximum zoom level for TMS layer */ 41 public static final IntegerProperty PROP_MAX_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".max_zoom_lvl", AbstractTileSourceLayer.PROP_MAX_ZOOM_LVL.get()); 42 /** shall TMS layers be added to download dialog */ 43 public static final BooleanProperty PROP_ADD_TO_SLIPPYMAP_CHOOSER = new BooleanProperty(PREFERENCE_PREFIX + ".add_to_slippymap_chooser", true); 111 44 112 public static final BooleanProperty PROP_DEFAULT_AUTOZOOM = new BooleanProperty(PREFERENCE_PREFIX + ".default_autozoom", true); 113 public static final BooleanProperty PROP_DEFAULT_AUTOLOAD = new BooleanProperty(PREFERENCE_PREFIX + ".default_autoload", true); 114 public static final BooleanProperty PROP_DEFAULT_SHOWERRORS = new BooleanProperty(PREFERENCE_PREFIX + ".default_showerrors", true); 115 public static final IntegerProperty PROP_MIN_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".min_zoom_lvl", DEFAULT_MIN_ZOOM); 116 public static final IntegerProperty PROP_MAX_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".max_zoom_lvl", DEFAULT_MAX_ZOOM); 117 public static final BooleanProperty PROP_ADD_TO_SLIPPYMAP_CHOOSER = new BooleanProperty(PREFERENCE_PREFIX + 118 ".add_to_slippymap_chooser", true); 119 public static final StringProperty PROP_TILECACHE_DIR; 120 static { 121 String defPath = null; 122 try { 123 defPath = new File(Main.pref.getCacheDirectory(), "tms").getAbsolutePath(); 124 } catch (SecurityException e) { 125 Main.warn(e); 126 } 127 PROP_TILECACHE_DIR = new StringProperty(PREFERENCE_PREFIX + ".tilecache", defPath); 128 } 45 /** loader factory responsible for loading tiles for this layer */ 46 public static TileLoaderFactory loaderFactory = new CachedTileLoaderFactory("TMS"){ 129 47 130 private final class ShowTileInfoAction extends AbstractAction { 131 private ShowTileInfoAction() { 132 super(tr("Show Tile Info")); 48 @Override 49 protected TileLoader getLoader(TileLoaderListener listener, String cacheName, int connectTimeout, 50 int readTimeout, Map<String, String> headers, String cacheDir) throws IOException { 51 return new TMSCachedTileLoader(listener, cacheName, connectTimeout, readTimeout, headers, cacheDir); 133 52 } 134 53 135 private String getSizeString(int size) { 136 return new StringBuilder().append(size).append("x").append(size).toString(); 137 } 54 }; 138 55 139 private JTextField createTextField(String text) { 140 JTextField ret = new JTextField(text); 141 ret.setEditable(false); 142 ret.setBorder(BorderFactory.createEmptyBorder()); 143 return ret; 144 } 145 146 @Override 147 public void actionPerformed(ActionEvent ae) { 148 if (clickedTile != null) { 149 ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Tile Info"), new String[]{tr("OK")}); 150 JPanel panel = new JPanel(new GridBagLayout()); 151 Rectangle displaySize = tileToRect(clickedTile); 152 String url = ""; 153 try { 154 url = clickedTile.getUrl(); 155 } catch (IOException e) { 156 // silence exceptions 157 if (Main.isTraceEnabled()) { 158 Main.trace(e.getMessage()); 159 } 160 } 161 162 String[][] content = { 163 {"Tile name", clickedTile.getKey()}, 164 {"Tile url", url}, 165 {"Tile size", getSizeString(clickedTile.getTileSource().getTileSize()) }, 166 {"Tile display size", new StringBuilder().append(displaySize.width).append("x").append(displaySize.height).toString()}, 167 }; 168 169 for (String[] entry: content) { 170 panel.add(new JLabel(tr(entry[0]) + ":"), GBC.std()); 171 panel.add(GBC.glue(5, 0), GBC.std()); 172 panel.add(createTextField(entry[1]), GBC.eol().fill(GBC.HORIZONTAL)); 173 } 174 175 for (Entry<String, String> e: clickedTile.getMetadata().entrySet()) { 176 panel.add(new JLabel(tr("Metadata ") + tr(e.getKey()) + ":"), GBC.std()); 177 panel.add(GBC.glue(5, 0), GBC.std()); 178 String value = e.getValue(); 179 if ("lastModification".equals(e.getKey()) || "expirationTime".equals(e.getKey())) { 180 value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(Long.parseLong(value))); 181 } 182 panel.add(createTextField(value), GBC.eol().fill(GBC.HORIZONTAL)); 183 } 184 ed.setIcon(JOptionPane.INFORMATION_MESSAGE); 185 ed.setContent(panel); 186 ed.showDialog(); 187 } 188 } 56 /** 57 * Create a layer based on ImageryInfo 58 * @param info description of the layer 59 */ 60 public TMSLayer(ImageryInfo info) { 61 super(info); 189 62 } 190 63 191 64 /** 192 * Interface for creating TileLoaders, ie. classes responsible for loading tiles on map193 *194 */195 public interface TileLoaderFactory {196 /**197 * @param listener object that will be notified, when tile has finished loading198 * @return TileLoader that will notify the listener199 */200 TileLoader makeTileLoader(TileLoaderListener listener);201 202 /**203 * @param listener object that will be notified, when tile has finished loading204 * @param headers HTTP headers that should be sent by TileLoader to tile server205 * @return TileLoader that will notify the listener206 */207 TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> headers);208 }209 210 protected TileCache tileCache;211 protected TileSource tileSource;212 protected TileLoader tileLoader;213 214 215 public static TileLoaderFactory loaderFactory = new TileLoaderFactory() {216 @Override217 public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> inputHeaders) {218 Map<String, String> headers = new HashMap<>();219 headers.put("User-Agent", Version.getInstance().getFullAgentString());220 headers.put("Accept", "text/html, image/png, image/jpeg, image/gif, */*");221 if (inputHeaders != null)222 headers.putAll(inputHeaders);223 224 try {225 return new TMSCachedTileLoader(listener, "TMS",226 Main.pref.getInteger("socket.timeout.connect", 15) * 1000,227 Main.pref.getInteger("socket.timeout.read", 30) * 1000,228 headers,229 PROP_TILECACHE_DIR.get());230 } catch (IOException e) {231 Main.warn(e);232 }233 return null;234 }235 236 @Override237 public TileLoader makeTileLoader(TileLoaderListener listener) {238 return makeTileLoader(listener, null);239 }240 };241 242 /**243 65 * Plugins that wish to set custom tile loader should call this method 66 * @param newLoaderFactory that will be used to load tiles 244 67 */ 245 68 246 public static void set CustomTileLoaderFactory(TileLoaderFactoryloaderFactory) {247 TMSLayer.loaderFactory =loaderFactory;69 public static void setTileLoaderFactory(TileLoaderFactory newLoaderFactory) { 70 loaderFactory = newLoaderFactory; 248 71 } 249 72 250 73 @Override 251 public synchronized void tileLoadingFinished(Tile tile, boolean success) { 252 if (tile.hasError()) { 253 success = false; 254 tile.setImage(null); 74 protected TileLoaderFactory getTileLoaderFactory() { 75 return loaderFactory; 76 } 77 78 @Override 79 protected Map<String, String> getHeaders(TileSource tileSource) { 80 if (tileSource instanceof TemplatedTMSTileSource) { 81 return ((TemplatedTMSTileSource)tileSource).getHeaders(); 255 82 } 256 if (sharpenLevel != 0 && success) { 257 tile.setImage(sharpenImage(tile.getImage())); 258 } 259 tile.setLoaded(success); 260 needRedraw = true; 261 if (Main.map != null) { 262 Main.map.repaint(100); 263 } 264 if (Main.isDebugEnabled()) { 265 Main.debug("tileLoadingFinished() tile: " + tile + " success: " + success); 266 } 83 return null; 267 84 } 268 85 269 86 /** 270 * Clears the tile cache. 87 * Creates and returns a new TileSource instance depending on the {@link ImageryType} 88 * of the passed ImageryInfo object. 271 89 * 272 * If the current tileLoader is an instance of OsmTileLoader, a new273 * TmsTileClearController is created and passed to the according clearCache274 * method.90 * If no appropriate TileSource is found, null is returned. 91 * Currently supported ImageryType are {@link ImageryType#TMS}, 92 * {@link ImageryType#BING}, {@link ImageryType#SCANEX}. 275 93 * 276 * @param monitor not used in this implementation - as cache clear is instaneus 94 * 95 * @param info imagery info 96 * @return a new TileSource instance or null if no TileSource for the ImageryInfo/ImageryType could be found. 97 * @throws IllegalArgumentException if url from imagery info is null or invalid 277 98 */ 278 public void clearTileCache(ProgressMonitor monitor) { 279 tileCache.clear(); 280 if (tileLoader instanceof CachedTileLoader) { 281 ((CachedTileLoader) tileLoader).clearCache(tileSource); 282 } 283 redraw(); 99 @Override 100 protected TileSource getTileSource(ImageryInfo info) throws IllegalArgumentException { 101 return getTileSourceStatic(info); 284 102 } 285 103 286 104 /** 287 * Zoomlevel at which tiles is currently downloaded. 288 * Initial zoom lvl is set to bestZoom 105 * Adds a context menu to the mapView. 289 106 */ 290 public int currentZoomLevel;291 107 292 private Tile clickedTile; 293 private boolean needRedraw; 294 private JPopupMenu tileOptionMenu; 295 private Tile showMetadataTile; 296 private AttributionSupport attribution = new AttributionSupport(); 297 private static final Font InfoFont = new Font("sansserif", Font.BOLD, 13); 298 299 protected boolean autoZoom; 300 protected boolean autoLoad; 301 protected boolean showErrors; 302 303 /** 304 * Initiates a repaint of Main.map 305 * 306 * @see Main#map 307 * @see MapFrame#repaint() 308 */ 309 protected void redraw() { 310 needRedraw = true; 311 Main.map.repaint(); 108 @Override 109 public final boolean isProjectionSupported(Projection proj) { 110 return "EPSG:3857".equals(proj.toCode()) || "EPSG:4326".equals(proj.toCode()); 312 111 } 313 112 314 protected static int checkMaxZoomLvl(int maxZoomLvl, TileSource ts) { 315 if (maxZoomLvl > MAX_ZOOM) { 316 maxZoomLvl = MAX_ZOOM; 317 } 318 if (maxZoomLvl < PROP_MIN_ZOOM_LVL.get()) { 319 maxZoomLvl = PROP_MIN_ZOOM_LVL.get(); 320 } 321 if (ts != null && ts.getMaxZoom() != 0 && ts.getMaxZoom() < maxZoomLvl) { 322 maxZoomLvl = ts.getMaxZoom(); 323 } 324 return maxZoomLvl; 325 } 326 327 public static int getMaxZoomLvl(TileSource ts) { 328 return checkMaxZoomLvl(PROP_MAX_ZOOM_LVL.get(), ts); 329 } 330 331 public static void setMaxZoomLvl(int maxZoomLvl) { 332 Integer newMaxZoom = Integer.valueOf(checkMaxZoomLvl(maxZoomLvl, null)); 333 PROP_MAX_ZOOM_LVL.put(newMaxZoom); 334 } 335 336 static int checkMinZoomLvl(int minZoomLvl, TileSource ts) { 337 if (minZoomLvl < MIN_ZOOM) { 338 /*Main.debug("Min. zoom level should not be less than "+MIN_ZOOM+"! Setting to that.");*/ 339 minZoomLvl = MIN_ZOOM; 340 } 341 if (minZoomLvl > PROP_MAX_ZOOM_LVL.get()) { 342 /*Main.debug("Min. zoom level should not be more than Max. zoom level! Setting to Max.");*/ 343 minZoomLvl = getMaxZoomLvl(ts); 344 } 345 if (ts != null && ts.getMinZoom() > minZoomLvl) { 346 /*Main.debug("Increasing min. zoom level to match tile source");*/ 347 minZoomLvl = ts.getMinZoom(); 348 } 349 return minZoomLvl; 350 } 351 352 public static int getMinZoomLvl(TileSource ts) { 353 return checkMinZoomLvl(PROP_MIN_ZOOM_LVL.get(), ts); 354 } 355 356 public static void setMinZoomLvl(int minZoomLvl) { 357 minZoomLvl = checkMinZoomLvl(minZoomLvl, null); 358 PROP_MIN_ZOOM_LVL.put(minZoomLvl); 359 } 360 361 private static class CachedAttributionBingAerialTileSource extends BingAerialTileSource { 362 363 public CachedAttributionBingAerialTileSource(ImageryInfo info) { 364 super(info); 365 } 366 367 class BingAttributionData extends CacheCustomContent<IOException> { 368 369 public BingAttributionData() { 370 super("bing.attribution.xml", CacheCustomContent.INTERVAL_HOURLY); 371 } 372 373 @Override 374 protected byte[] updateData() throws IOException { 375 URL u = getAttributionUrl(); 376 try (Scanner scanner = new Scanner(UTFInputStreamReader.create(Utils.openURL(u)))) { 377 String r = scanner.useDelimiter("\\A").next(); 378 Main.info("Successfully loaded Bing attribution data."); 379 return r.getBytes("UTF-8"); 380 } 381 } 382 } 383 384 @Override 385 protected Callable<List<Attribution>> getAttributionLoaderCallable() { 386 return new Callable<List<Attribution>>() { 387 388 @Override 389 public List<Attribution> call() throws Exception { 390 BingAttributionData attributionLoader = new BingAttributionData(); 391 int waitTimeSec = 1; 392 while (true) { 393 try { 394 String xml = attributionLoader.updateIfRequiredString(); 395 return parseAttributionText(new InputSource(new StringReader(xml))); 396 } catch (IOException ex) { 397 Main.warn("Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds."); 398 Thread.sleep(waitTimeSec * 1000L); 399 waitTimeSec *= 2; 400 } 401 } 402 } 403 }; 404 } 113 @Override 114 public final String nameSupportedProjections() { 115 return tr("EPSG:4326 and Mercator projection are supported"); 405 116 } 406 117 … … 417 128 * @throws IllegalArgumentException if url from imagery info is null or invalid 418 129 */ 419 public static TileSource getTileSource(ImageryInfo info) { 130 public static TileSource getTileSourceStatic(ImageryInfo info) throws IllegalArgumentException { 420 131 if (info.getImageryType() == ImageryType.TMS) { 421 checkUrl(info.getUrl()); 132 TemplatedTMSTileSource.checkUrl(info.getUrl()); 422 133 TMSTileSource t = new TemplatedTMSTileSource(info); 423 134 info.setAttribution(t); 424 135 return t; 425 } else if (info.getImageryType() == ImageryType.BING) {136 } else if (info.getImageryType() == ImageryType.BING) 426 137 return new CachedAttributionBingAerialTileSource(info); 427 }else if (info.getImageryType() == ImageryType.SCANEX) {138 else if (info.getImageryType() == ImageryType.SCANEX) { 428 139 return new ScanexTileSource(info); 429 140 } … … 431 142 } 432 143 433 /**434 * Checks validity of given URL.435 * @param url URL to check436 * @throws IllegalArgumentException if url is null or invalid437 */438 public static void checkUrl(String url) {439 CheckParameterUtil.ensureParameterNotNull(url, "url");440 Matcher m = Pattern.compile("\\{[^}]*\\}").matcher(url);441 while (m.find()) {442 boolean isSupportedPattern = false;443 for (String pattern : TemplatedTMSTileSource.ALL_PATTERNS) {444 if (m.group().matches(pattern)) {445 isSupportedPattern = true;446 break;447 }448 }449 if (!isSupportedPattern) {450 throw new IllegalArgumentException(451 tr("{0} is not a valid TMS argument. Please check this server URL:\n{1}", m.group(), url));452 }453 }454 }455 144 456 private void initTileSource(TileSource tileSource) {457 this.tileSource = tileSource;458 attribution.initialize(tileSource);459 145 460 currentZoomLevel = getBestZoom();461 462 Map<String, String> headers = null;463 if (tileSource instanceof TemplatedTMSTileSource) {464 headers = (((TemplatedTMSTileSource) tileSource).getHeaders());465 }466 467 tileLoader = loaderFactory.makeTileLoader(this, headers);468 if (tileLoader instanceof TMSCachedTileLoader) {469 tileCache = (TileCache) tileLoader;470 } else {471 tileCache = new MemoryTileCache();472 }473 if (tileLoader == null)474 tileLoader = new OsmTileLoader(this);475 }476 477 /**478 * Marks layer as needing redraw on offset change479 */480 @Override481 public void setOffset(double dx, double dy) {482 super.setOffset(dx, dy);483 needRedraw = true;484 }485 /**486 * Returns average number of screen pixels per tile pixel for current mapview487 */488 private double getScaleFactor(int zoom) {489 if (!Main.isDisplayingMapView()) return 1;490 MapView mv = Main.map.mapView;491 LatLon topLeft = mv.getLatLon(0, 0);492 LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());493 double x1 = tileSource.lonToTileX(topLeft.lon(), zoom);494 double y1 = tileSource.latToTileY(topLeft.lat(), zoom);495 double x2 = tileSource.lonToTileX(botRight.lon(), zoom);496 double y2 = tileSource.latToTileY(botRight.lat(), zoom);497 498 int screenPixels = mv.getWidth()*mv.getHeight();499 double tilePixels = Math.abs((y2-y1)*(x2-x1)*tileSource.getTileSize()*tileSource.getTileSize());500 if (screenPixels == 0 || tilePixels == 0) return 1;501 return screenPixels/tilePixels;502 }503 504 private int getBestZoom() {505 double factor = getScaleFactor(1); // check the ratio between area of tilesize at zoom 1 to current view506 double result = Math.log(factor)/Math.log(2)/2+1;507 /*508 * Math.log(factor)/Math.log(2) - gives log base 2 of factor509 * We divide result by 2, as factor contains ratio between areas. We could do Math.sqrt before log, or just divide log by 2510 * In general, smaller zoom levels are more readable. We prefer big,511 * block, pixelated (but readable) map text to small, smeared,512 * unreadable underzoomed text. So, use .floor() instead of rounding513 * to skew things a bit toward the lower zooms.514 * Remember, that result here, should correspond to TMSLayer.paint(...)515 * getScaleFactor(...) is supposed to be between 0.75 and 3516 */517 int intResult = (int) Math.floor(result);518 if (intResult > getMaxZoomLvl())519 return getMaxZoomLvl();520 if (intResult < getMinZoomLvl())521 return getMinZoomLvl();522 return intResult;523 }524 525 @SuppressWarnings("serial")526 public TMSLayer(ImageryInfo info) {527 super(info);528 529 if (!isProjectionSupported(Main.getProjection())) {530 JOptionPane.showMessageDialog(Main.parent,531 tr("TMS layers do not support the projection {0}.\n{1}\n"532 + "Change the projection or remove the layer.",533 Main.getProjection().toCode(), nameSupportedProjections()),534 tr("Warning"),535 JOptionPane.WARNING_MESSAGE);536 }537 538 setBackgroundLayer(true);539 this.setVisible(true);540 541 TileSource source = getTileSource(info);542 if (source == null)543 throw new IllegalStateException("Cannot create TMSLayer with non-TMS ImageryInfo");544 initTileSource(source);545 546 MapView.addZoomChangeListener(this);547 }548 549 /**550 * Adds a context menu to the mapView.551 */552 @Override553 public void hookUpMapView() {554 tileOptionMenu = new JPopupMenu();555 556 autoZoom = PROP_DEFAULT_AUTOZOOM.get();557 JCheckBoxMenuItem autoZoomPopup = new JCheckBoxMenuItem();558 autoZoomPopup.setAction(new AbstractAction(tr("Auto Zoom")) {559 @Override560 public void actionPerformed(ActionEvent ae) {561 autoZoom = !autoZoom;562 }563 });564 autoZoomPopup.setSelected(autoZoom);565 tileOptionMenu.add(autoZoomPopup);566 567 autoLoad = PROP_DEFAULT_AUTOLOAD.get();568 JCheckBoxMenuItem autoLoadPopup = new JCheckBoxMenuItem();569 autoLoadPopup.setAction(new AbstractAction(tr("Auto load tiles")) {570 @Override571 public void actionPerformed(ActionEvent ae) {572 autoLoad = !autoLoad;573 }574 });575 autoLoadPopup.setSelected(autoLoad);576 tileOptionMenu.add(autoLoadPopup);577 578 showErrors = PROP_DEFAULT_SHOWERRORS.get();579 JCheckBoxMenuItem showErrorsPopup = new JCheckBoxMenuItem();580 showErrorsPopup.setAction(new AbstractAction(tr("Show Errors")) {581 @Override582 public void actionPerformed(ActionEvent ae) {583 showErrors = !showErrors;584 }585 });586 showErrorsPopup.setSelected(showErrors);587 tileOptionMenu.add(showErrorsPopup);588 589 tileOptionMenu.add(new JMenuItem(new AbstractAction(tr("Load Tile")) {590 @Override591 public void actionPerformed(ActionEvent ae) {592 if (clickedTile != null) {593 loadTile(clickedTile, true);594 redraw();595 }596 }597 }));598 599 tileOptionMenu.add(new JMenuItem(new ShowTileInfoAction()));600 601 tileOptionMenu.add(new JMenuItem(new AbstractAction(602 tr("Request Update")) {603 @Override604 public void actionPerformed(ActionEvent ae) {605 if (clickedTile != null) {606 clickedTile.setLoaded(false);607 tileLoader.createTileLoaderJob(clickedTile).submit(true);608 }609 }610 }));611 612 tileOptionMenu.add(new JMenuItem(new AbstractAction(613 tr("Load All Tiles")) {614 @Override615 public void actionPerformed(ActionEvent ae) {616 loadAllTiles(true);617 redraw();618 }619 }));620 621 tileOptionMenu.add(new JMenuItem(new AbstractAction(622 tr("Load All Error Tiles")) {623 @Override624 public void actionPerformed(ActionEvent ae) {625 loadAllErrorTiles(true);626 redraw();627 }628 }));629 630 // increase and decrease commands631 tileOptionMenu.add(new JMenuItem(new AbstractAction(632 tr("Increase zoom")) {633 @Override634 public void actionPerformed(ActionEvent ae) {635 increaseZoomLevel();636 redraw();637 }638 }));639 640 tileOptionMenu.add(new JMenuItem(new AbstractAction(641 tr("Decrease zoom")) {642 @Override643 public void actionPerformed(ActionEvent ae) {644 decreaseZoomLevel();645 redraw();646 }647 }));648 649 tileOptionMenu.add(new JMenuItem(new AbstractAction(650 tr("Snap to tile size")) {651 @Override652 public void actionPerformed(ActionEvent ae) {653 double newFactor = Math.sqrt(getScaleFactor(currentZoomLevel));654 Main.map.mapView.zoomToFactor(newFactor);655 redraw();656 }657 }));658 659 tileOptionMenu.add(new JMenuItem(new AbstractAction(660 tr("Flush Tile Cache")) {661 @Override662 public void actionPerformed(ActionEvent ae) {663 new PleaseWaitRunnable(tr("Flush Tile Cache")) {664 @Override665 protected void realRun() throws SAXException, IOException,666 OsmTransferException {667 clearTileCache(getProgressMonitor());668 }669 670 @Override671 protected void finish() {672 }673 674 @Override675 protected void cancel() {676 }677 }.run();678 }679 }));680 681 final MouseAdapter adapter = new MouseAdapter() {682 @Override683 public void mouseClicked(MouseEvent e) {684 if (!isVisible()) return;685 if (e.getButton() == MouseEvent.BUTTON3) {686 clickedTile = getTileForPixelpos(e.getX(), e.getY());687 tileOptionMenu.show(e.getComponent(), e.getX(), e.getY());688 } else if (e.getButton() == MouseEvent.BUTTON1) {689 attribution.handleAttribution(e.getPoint(), true);690 }691 }692 };693 Main.map.mapView.addMouseListener(adapter);694 695 MapView.addLayerChangeListener(new LayerChangeListener() {696 @Override697 public void activeLayerChange(Layer oldLayer, Layer newLayer) {698 //699 }700 701 @Override702 public void layerAdded(Layer newLayer) {703 //704 }705 706 @Override707 public void layerRemoved(Layer oldLayer) {708 if (oldLayer == TMSLayer.this) {709 Main.map.mapView.removeMouseListener(adapter);710 MapView.removeZoomChangeListener(TMSLayer.this);711 MapView.removeLayerChangeListener(this);712 }713 }714 });715 }716 717 /**718 * This fires every time the user changes the zoom, but also (due to ZoomChangeListener) - on all719 * changes to visible map (panning/zooming)720 */721 @Override722 public void zoomChanged() {723 if (Main.isDebugEnabled()) {724 Main.debug("zoomChanged(): " + currentZoomLevel);725 }726 needRedraw = true;727 if (tileLoader instanceof TMSCachedTileLoader) {728 ((TMSCachedTileLoader) tileLoader).cancelOutstandingTasks();729 }730 }731 732 protected int getMaxZoomLvl() {733 if (info.getMaxZoom() != 0)734 return checkMaxZoomLvl(info.getMaxZoom(), tileSource);735 else736 return getMaxZoomLvl(tileSource);737 }738 739 protected int getMinZoomLvl() {740 return getMinZoomLvl(tileSource);741 }742 743 /**744 * Zoom in, go closer to map.745 *746 * @return true, if zoom increasing was successful, false otherwise747 */748 public boolean zoomIncreaseAllowed() {749 boolean zia = currentZoomLevel < this.getMaxZoomLvl();750 if (Main.isDebugEnabled()) {751 Main.debug("zoomIncreaseAllowed(): " + zia + " " + currentZoomLevel + " vs. " + this.getMaxZoomLvl());752 }753 return zia;754 }755 756 public boolean increaseZoomLevel() {757 if (zoomIncreaseAllowed()) {758 currentZoomLevel++;759 if (Main.isDebugEnabled()) {760 Main.debug("increasing zoom level to: " + currentZoomLevel);761 }762 zoomChanged();763 } else {764 Main.warn("Current zoom level ("+currentZoomLevel+") could not be increased. "+765 "Max.zZoom Level "+this.getMaxZoomLvl()+" reached.");766 return false;767 }768 return true;769 }770 771 public boolean setZoomLevel(int zoom) {772 if (zoom == currentZoomLevel) return true;773 if (zoom > this.getMaxZoomLvl()) return false;774 if (zoom < this.getMinZoomLvl()) return false;775 currentZoomLevel = zoom;776 zoomChanged();777 return true;778 }779 780 /**781 * Check if zooming out is allowed782 *783 * @return true, if zooming out is allowed (currentZoomLevel > minZoomLevel)784 */785 public boolean zoomDecreaseAllowed() {786 return currentZoomLevel > this.getMinZoomLvl();787 }788 789 /**790 * Zoom out from map.791 *792 * @return true, if zoom increasing was successfull, false othervise793 */794 public boolean decreaseZoomLevel() {795 //int minZoom = this.getMinZoomLvl();796 if (zoomDecreaseAllowed()) {797 if (Main.isDebugEnabled()) {798 Main.debug("decreasing zoom level to: " + currentZoomLevel);799 }800 currentZoomLevel--;801 zoomChanged();802 } else {803 /*Main.debug("Current zoom level could not be decreased. Min. zoom level "+minZoom+" reached.");*/804 return false;805 }806 return true;807 }808 809 /*810 * We use these for quick, hackish calculations. They811 * are temporary only and intentionally not inserted812 * into the tileCache.813 */814 private Tile tempCornerTile(Tile t) {815 int x = t.getXtile() + 1;816 int y = t.getYtile() + 1;817 int zoom = t.getZoom();818 Tile tile = getTile(x, y, zoom);819 if (tile != null)820 return tile;821 return new Tile(tileSource, x, y, zoom);822 }823 824 private Tile getOrCreateTile(int x, int y, int zoom) {825 Tile tile = getTile(x, y, zoom);826 if (tile == null) {827 tile = new Tile(tileSource, x, y, zoom);828 tileCache.addTile(tile);829 tile.loadPlaceholderFromCache(tileCache);830 }831 return tile;832 }833 834 /*835 * This can and will return null for tiles that are not836 * already in the cache.837 */838 private Tile getTile(int x, int y, int zoom) {839 int max = 1 << zoom;840 if (x < 0 || x >= max || y < 0 || y >= max)841 return null;842 return tileCache.getTile(tileSource, x, y, zoom);843 }844 845 private boolean loadTile(Tile tile, boolean force) {846 if (tile == null)847 return false;848 if (!force && (tile.isLoaded() || tile.hasError()))849 return false;850 if (tile.isLoading())851 return false;852 tileLoader.createTileLoaderJob(tile).submit(force);853 return true;854 }855 856 private void loadAllTiles(boolean force) {857 MapView mv = Main.map.mapView;858 EastNorth topLeft = mv.getEastNorth(0, 0);859 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());860 861 TileSet ts = new TileSet(topLeft, botRight, currentZoomLevel);862 863 // if there is more than 18 tiles on screen in any direction, do not864 // load all tiles!865 if (ts.tooLarge()) {866 Main.warn("Not downloading all tiles because there is more than 18 tiles on an axis!");867 return;868 }869 ts.loadAllTiles(force);870 }871 872 private void loadAllErrorTiles(boolean force) {873 MapView mv = Main.map.mapView;874 EastNorth topLeft = mv.getEastNorth(0, 0);875 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());876 877 TileSet ts = new TileSet(topLeft, botRight, currentZoomLevel);878 879 ts.loadAllErrorTiles(force);880 }881 882 @Override883 public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {884 boolean done = (infoflags & (ERROR | FRAMEBITS | ALLBITS)) != 0;885 needRedraw = true;886 if (Main.isDebugEnabled()) {887 Main.debug("imageUpdate() done: " + done + " calling repaint");888 }889 Main.map.repaint(done ? 0 : 100);890 return !done;891 }892 893 private boolean imageLoaded(Image i) {894 if (i == null)895 return false;896 int status = Toolkit.getDefaultToolkit().checkImage(i, -1, -1, this);897 if ((status & ALLBITS) != 0)898 return true;899 return false;900 }901 902 /**903 * Returns the image for the given tile if both tile and image are loaded.904 * Otherwise returns null.905 *906 * @param tile the Tile for which the image should be returned907 * @return the image of the tile or null.908 */909 private Image getLoadedTileImage(Tile tile) {910 if (!tile.isLoaded())911 return null;912 Image img = tile.getImage();913 if (!imageLoaded(img))914 return null;915 return img;916 }917 918 private LatLon tileLatLon(Tile t) {919 int zoom = t.getZoom();920 return new LatLon(tileSource.tileYToLat(t.getYtile(), zoom),921 tileSource.tileXToLon(t.getXtile(), zoom));922 }923 924 private Rectangle tileToRect(Tile t1) {925 /*926 * We need to get a box in which to draw, so advance by one tile in927 * each direction to find the other corner of the box.928 * Note: this somewhat pollutes the tile cache929 */930 Tile t2 = tempCornerTile(t1);931 Rectangle rect = new Rectangle(pixelPos(t1));932 rect.add(pixelPos(t2));933 return rect;934 }935 936 // 'source' is the pixel coordinates for the area that937 // the img is capable of filling in. However, we probably938 // only want a portion of it.939 //940 // 'border' is the screen cordinates that need to be drawn.941 // We must not draw outside of it.942 private void drawImageInside(Graphics g, Image sourceImg, Rectangle source, Rectangle border) {943 Rectangle target = source;944 945 // If a border is specified, only draw the intersection946 // if what we have combined with what we are supposed947 // to draw.948 if (border != null) {949 target = source.intersection(border);950 if (Main.isDebugEnabled()) {951 Main.debug("source: " + source + "\nborder: " + border + "\nintersection: " + target);952 }953 }954 955 // All of the rectangles are in screen coordinates. We need956 // to how these correlate to the sourceImg pixels. We could957 // avoid doing this by scaling the image up to the 'source' size,958 // but this should be cheaper.959 //960 // In some projections, x any y are scaled differently enough to961 // cause a pixel or two of fudge. Calculate them separately.962 double imageYScaling = sourceImg.getHeight(this) / source.getHeight();963 double imageXScaling = sourceImg.getWidth(this) / source.getWidth();964 965 // How many pixels into the 'source' rectangle are we drawing?966 int screen_x_offset = target.x - source.x;967 int screen_y_offset = target.y - source.y;968 // And how many pixels into the image itself does that969 // correlate to?970 int img_x_offset = (int) (screen_x_offset * imageXScaling + 0.5);971 int img_y_offset = (int) (screen_y_offset * imageYScaling + 0.5);972 // Now calculate the other corner of the image that we need973 // by scaling the 'target' rectangle's dimensions.974 int img_x_end = img_x_offset + (int) (target.getWidth() * imageXScaling + 0.5);975 int img_y_end = img_y_offset + (int) (target.getHeight() * imageYScaling + 0.5);976 977 if (Main.isDebugEnabled()) {978 Main.debug("drawing image into target rect: " + target);979 }980 g.drawImage(sourceImg,981 target.x, target.y,982 target.x + target.width, target.y + target.height,983 img_x_offset, img_y_offset,984 img_x_end, img_y_end,985 this);986 if (PROP_FADE_AMOUNT.get() != 0) {987 // dimm by painting opaque rect...988 g.setColor(getFadeColorWithAlpha());989 g.fillRect(target.x, target.y,990 target.width, target.height);991 }992 }993 994 // This function is called for several zoom levels, not just995 // the current one. It should not trigger any tiles to be996 // downloaded. It should also avoid polluting the tile cache997 // with any tiles since these tiles are not mandatory.998 //999 // The "border" tile tells us the boundaries of where we may1000 // draw. It will not be from the zoom level that is being1001 // drawn currently. If drawing the displayZoomLevel,1002 // border is null and we draw the entire tile set.1003 private List<Tile> paintTileImages(Graphics g, TileSet ts, int zoom, Tile border) {1004 if (zoom <= 0) return Collections.emptyList();1005 Rectangle borderRect = null;1006 if (border != null) {1007 borderRect = tileToRect(border);1008 }1009 List<Tile> missedTiles = new LinkedList<>();1010 // The callers of this code *require* that we return any tiles1011 // that we do not draw in missedTiles. ts.allExistingTiles() by1012 // default will only return already-existing tiles. However, we1013 // need to return *all* tiles to the callers, so force creation1014 // here.1015 //boolean forceTileCreation = true;1016 for (Tile tile : ts.allTilesCreate()) {1017 Image img = getLoadedTileImage(tile);1018 if (img == null || tile.hasError()) {1019 if (Main.isDebugEnabled()) {1020 Main.debug("missed tile: " + tile);1021 }1022 missedTiles.add(tile);1023 continue;1024 }1025 Rectangle sourceRect = tileToRect(tile);1026 if (borderRect != null && !sourceRect.intersects(borderRect)) {1027 continue;1028 }1029 drawImageInside(g, img, sourceRect, borderRect);1030 }1031 return missedTiles;1032 }1033 1034 private void myDrawString(Graphics g, String text, int x, int y) {1035 Color oldColor = g.getColor();1036 g.setColor(Color.black);1037 g.drawString(text, x+1, y+1);1038 g.setColor(oldColor);1039 g.drawString(text, x, y);1040 }1041 1042 private void paintTileText(TileSet ts, Tile tile, Graphics g, MapView mv, int zoom, Tile t) {1043 int fontHeight = g.getFontMetrics().getHeight();1044 if (tile == null)1045 return;1046 Point p = pixelPos(t);1047 int texty = p.y + 2 + fontHeight;1048 1049 /*if (PROP_DRAW_DEBUG.get()) {1050 myDrawString(g, "x=" + t.getXtile() + " y=" + t.getYtile() + " z=" + zoom + "", p.x + 2, texty);1051 texty += 1 + fontHeight;1052 if ((t.getXtile() % 32 == 0) && (t.getYtile() % 32 == 0)) {1053 myDrawString(g, "x=" + t.getXtile() / 32 + " y=" + t.getYtile() / 32 + " z=7", p.x + 2, texty);1054 texty += 1 + fontHeight;1055 }1056 }*/1057 1058 if (tile == showMetadataTile) {1059 String md = tile.toString();1060 if (md != null) {1061 myDrawString(g, md, p.x + 2, texty);1062 texty += 1 + fontHeight;1063 }1064 Map<String, String> meta = tile.getMetadata();1065 if (meta != null) {1066 for (Map.Entry<String, String> entry : meta.entrySet()) {1067 myDrawString(g, entry.getKey() + ": " + entry.getValue(), p.x + 2, texty);1068 texty += 1 + fontHeight;1069 }1070 }1071 }1072 1073 /*String tileStatus = tile.getStatus();1074 if (!tile.isLoaded() && PROP_DRAW_DEBUG.get()) {1075 myDrawString(g, tr("image " + tileStatus), p.x + 2, texty);1076 texty += 1 + fontHeight;1077 }*/1078 1079 if (tile.hasError() && showErrors) {1080 myDrawString(g, tr("Error") + ": " + tr(tile.getErrorMessage()), p.x + 2, texty);1081 texty += 1 + fontHeight;1082 }1083 1084 /*int xCursor = -1;1085 int yCursor = -1;1086 if (PROP_DRAW_DEBUG.get()) {1087 if (yCursor < t.getYtile()) {1088 if (t.getYtile() % 32 == 31) {1089 g.fillRect(0, p.y - 1, mv.getWidth(), 3);1090 } else {1091 g.drawLine(0, p.y, mv.getWidth(), p.y);1092 }1093 yCursor = t.getYtile();1094 }1095 // This draws the vertical lines for the entire1096 // column. Only draw them for the top tile in1097 // the column.1098 if (xCursor < t.getXtile()) {1099 if (t.getXtile() % 32 == 0) {1100 // level 7 tile boundary1101 g.fillRect(p.x - 1, 0, 3, mv.getHeight());1102 } else {1103 g.drawLine(p.x, 0, p.x, mv.getHeight());1104 }1105 xCursor = t.getXtile();1106 }1107 }*/1108 }1109 1110 private Point pixelPos(LatLon ll) {1111 return Main.map.mapView.getPoint(Main.getProjection().latlon2eastNorth(ll).add(getDx(), getDy()));1112 }1113 1114 private Point pixelPos(Tile t) {1115 double lon = tileSource.tileXToLon(t.getXtile(), t.getZoom());1116 LatLon tmpLL = new LatLon(tileSource.tileYToLat(t.getYtile(), t.getZoom()), lon);1117 return pixelPos(tmpLL);1118 }1119 1120 private LatLon getShiftedLatLon(EastNorth en) {1121 return Main.getProjection().eastNorth2latlon(en.add(-getDx(), -getDy()));1122 }1123 1124 private Coordinate getShiftedCoord(EastNorth en) {1125 LatLon ll = getShiftedLatLon(en);1126 return new Coordinate(ll.lat(), ll.lon());1127 }1128 1129 private final TileSet nullTileSet = new TileSet((LatLon) null, (LatLon) null, 0);1130 1131 private final class TileSet {1132 private int x0, x1, y0, y1;1133 private int zoom;1134 private int tileMax = -1;1135 1136 /**1137 * Create a TileSet by EastNorth bbox taking a layer shift in account1138 */1139 private TileSet(EastNorth topLeft, EastNorth botRight, int zoom) {1140 this(getShiftedLatLon(topLeft), getShiftedLatLon(botRight), zoom);1141 }1142 1143 /**1144 * Create a TileSet by known LatLon bbox without layer shift correction1145 */1146 private TileSet(LatLon topLeft, LatLon botRight, int zoom) {1147 this.zoom = zoom;1148 if (zoom == 0)1149 return;1150 1151 x0 = (int) tileSource.lonToTileX(topLeft.lon(), zoom);1152 y0 = (int) tileSource.latToTileY(topLeft.lat(), zoom);1153 x1 = (int) tileSource.lonToTileX(botRight.lon(), zoom);1154 y1 = (int) tileSource.latToTileY(botRight.lat(), zoom);1155 if (x0 > x1) {1156 int tmp = x0;1157 x0 = x1;1158 x1 = tmp;1159 }1160 if (y0 > y1) {1161 int tmp = y0;1162 y0 = y1;1163 y1 = tmp;1164 }1165 tileMax = (int) Math.pow(2.0, zoom);1166 if (x0 < 0) {1167 x0 = 0;1168 }1169 if (y0 < 0) {1170 y0 = 0;1171 }1172 if (x1 > tileMax) {1173 x1 = tileMax;1174 }1175 if (y1 > tileMax) {1176 y1 = tileMax;1177 }1178 }1179 1180 private boolean tooSmall() {1181 return this.tilesSpanned() < 2.1;1182 }1183 1184 private boolean tooLarge() {1185 return this.tilesSpanned() > 10;1186 }1187 1188 private boolean insane() {1189 return this.tilesSpanned() > 100;1190 }1191 1192 private double tilesSpanned() {1193 return Math.sqrt(1.0 * this.size());1194 }1195 1196 private int size() {1197 int x_span = x1 - x0 + 1;1198 int y_span = y1 - y0 + 1;1199 return x_span * y_span;1200 }1201 1202 /*1203 * Get all tiles represented by this TileSet that are1204 * already in the tileCache.1205 */1206 private List<Tile> allExistingTiles() {1207 return this.__allTiles(false);1208 }1209 1210 private List<Tile> allTilesCreate() {1211 return this.__allTiles(true);1212 }1213 1214 private List<Tile> __allTiles(boolean create) {1215 // Tileset is either empty or too large1216 if (zoom == 0 || this.insane())1217 return Collections.emptyList();1218 List<Tile> ret = new ArrayList<>();1219 for (int x = x0; x <= x1; x++) {1220 for (int y = y0; y <= y1; y++) {1221 Tile t;1222 if (create) {1223 t = getOrCreateTile(x % tileMax, y % tileMax, zoom);1224 } else {1225 t = getTile(x % tileMax, y % tileMax, zoom);1226 }1227 if (t != null) {1228 ret.add(t);1229 }1230 }1231 }1232 return ret;1233 }1234 1235 private List<Tile> allLoadedTiles() {1236 List<Tile> ret = new ArrayList<>();1237 for (Tile t : this.allExistingTiles()) {1238 if (t.isLoaded())1239 ret.add(t);1240 }1241 return ret;1242 }1243 1244 private Comparator<Tile> getTileDistanceComparator() {1245 final int centerX = (int) Math.ceil((x0 + x1) / 2);1246 final int centerY = (int) Math.ceil((y0 + y1) / 2);1247 return new Comparator<Tile>() {1248 private int getDistance(Tile t) {1249 return Math.abs(t.getXtile() - centerX) + Math.abs(t.getYtile() - centerY);1250 }1251 1252 @Override1253 public int compare(Tile o1, Tile o2) {1254 int distance1 = getDistance(o1);1255 int distance2 = getDistance(o2);1256 return Integer.compare(distance1, distance2);1257 }1258 };1259 }1260 1261 private void loadAllTiles(boolean force) {1262 if (!autoLoad && !force)1263 return;1264 List<Tile> allTiles = allTilesCreate();1265 Collections.sort(allTiles, getTileDistanceComparator());1266 for (Tile t : allTiles) {1267 loadTile(t, force);1268 }1269 }1270 1271 private void loadAllErrorTiles(boolean force) {1272 if (!autoLoad && !force)1273 return;1274 for (Tile t : this.allTilesCreate()) {1275 if (t.hasError()) {1276 loadTile(t, true);1277 }1278 }1279 }1280 }1281 1282 private static class TileSetInfo {1283 public boolean hasVisibleTiles = false;1284 public boolean hasOverzoomedTiles = false;1285 public boolean hasLoadingTiles = false;1286 }1287 1288 private static TileSetInfo getTileSetInfo(TileSet ts) {1289 List<Tile> allTiles = ts.allExistingTiles();1290 TileSetInfo result = new TileSetInfo();1291 result.hasLoadingTiles = allTiles.size() < ts.size();1292 for (Tile t : allTiles) {1293 if (t.isLoaded()) {1294 if (!t.hasError()) {1295 result.hasVisibleTiles = true;1296 }1297 if ("no-tile".equals(t.getValue("tile-info"))) {1298 result.hasOverzoomedTiles = true;1299 }1300 } else {1301 result.hasLoadingTiles = true;1302 }1303 }1304 return result;1305 }1306 1307 private class DeepTileSet {1308 private final EastNorth topLeft, botRight;1309 private final int minZoom, maxZoom;1310 private final TileSet[] tileSets;1311 private final TileSetInfo[] tileSetInfos;1312 1313 public DeepTileSet(EastNorth topLeft, EastNorth botRight, int minZoom, int maxZoom) {1314 this.topLeft = topLeft;1315 this.botRight = botRight;1316 this.minZoom = minZoom;1317 this.maxZoom = maxZoom;1318 this.tileSets = new TileSet[maxZoom - minZoom + 1];1319 this.tileSetInfos = new TileSetInfo[maxZoom - minZoom + 1];1320 }1321 1322 public TileSet getTileSet(int zoom) {1323 if (zoom < minZoom)1324 return nullTileSet;1325 synchronized (tileSets) {1326 TileSet ts = tileSets[zoom-minZoom];1327 if (ts == null) {1328 ts = new TileSet(topLeft, botRight, zoom);1329 tileSets[zoom-minZoom] = ts;1330 }1331 return ts;1332 }1333 }1334 1335 public TileSetInfo getTileSetInfo(int zoom) {1336 if (zoom < minZoom)1337 return new TileSetInfo();1338 synchronized (tileSetInfos) {1339 TileSetInfo tsi = tileSetInfos[zoom-minZoom];1340 if (tsi == null) {1341 tsi = TMSLayer.getTileSetInfo(getTileSet(zoom));1342 tileSetInfos[zoom-minZoom] = tsi;1343 }1344 return tsi;1345 }1346 }1347 }1348 1349 @Override1350 public void paint(Graphics2D g, MapView mv, Bounds bounds) {1351 EastNorth topLeft = mv.getEastNorth(0, 0);1352 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());1353 1354 if (botRight.east() == 0 || botRight.north() == 0) {1355 /*Main.debug("still initializing??");*/1356 // probably still initializing1357 return;1358 }1359 1360 needRedraw = false;1361 1362 int zoom = currentZoomLevel;1363 if (autoZoom) {1364 double pixelScaling = getScaleFactor(zoom);1365 if (pixelScaling > 3 || pixelScaling < 0.7) {1366 zoom = getBestZoom();1367 }1368 }1369 1370 DeepTileSet dts = new DeepTileSet(topLeft, botRight, getMinZoomLvl(), zoom);1371 TileSet ts = dts.getTileSet(zoom);1372 1373 int displayZoomLevel = zoom;1374 1375 boolean noTilesAtZoom = false;1376 if (autoZoom && autoLoad) {1377 // Auto-detection of tilesource maxzoom (currently fully works only for Bing)1378 TileSetInfo tsi = dts.getTileSetInfo(zoom);1379 if (!tsi.hasVisibleTiles && (!tsi.hasLoadingTiles || tsi.hasOverzoomedTiles)) {1380 noTilesAtZoom = true;1381 }1382 // Find highest zoom level with at least one visible tile1383 for (int tmpZoom = zoom; tmpZoom > dts.minZoom; tmpZoom--) {1384 if (dts.getTileSetInfo(tmpZoom).hasVisibleTiles) {1385 displayZoomLevel = tmpZoom;1386 break;1387 }1388 }1389 // Do binary search between currentZoomLevel and displayZoomLevel1390 while (zoom > displayZoomLevel && !tsi.hasVisibleTiles && tsi.hasOverzoomedTiles) {1391 zoom = (zoom + displayZoomLevel)/2;1392 tsi = dts.getTileSetInfo(zoom);1393 }1394 1395 setZoomLevel(zoom);1396 1397 // If all tiles at displayZoomLevel is loaded, load all tiles at next zoom level1398 // to make sure there're really no more zoom levels1399 if (zoom == displayZoomLevel && !tsi.hasLoadingTiles && zoom < dts.maxZoom) {1400 zoom++;1401 tsi = dts.getTileSetInfo(zoom);1402 }1403 // When we have overzoomed tiles and all tiles at current zoomlevel is loaded,1404 // load tiles at previovus zoomlevels until we have all tiles on screen is loaded.1405 while (zoom > dts.minZoom && tsi.hasOverzoomedTiles && !tsi.hasLoadingTiles) {1406 zoom--;1407 tsi = dts.getTileSetInfo(zoom);1408 }1409 ts = dts.getTileSet(zoom);1410 } else if (autoZoom) {1411 setZoomLevel(zoom);1412 }1413 1414 // Too many tiles... refuse to download1415 if (!ts.tooLarge()) {1416 //Main.debug("size: " + ts.size() + " spanned: " + ts.tilesSpanned());1417 ts.loadAllTiles(false);1418 }1419 1420 if (displayZoomLevel != zoom) {1421 ts = dts.getTileSet(displayZoomLevel);1422 }1423 1424 g.setColor(Color.DARK_GRAY);1425 1426 List<Tile> missedTiles = this.paintTileImages(g, ts, displayZoomLevel, null);1427 int[] otherZooms = {-1, 1, -2, 2, -3, -4, -5};1428 for (int zoomOffset : otherZooms) {1429 if (!autoZoom) {1430 break;1431 }1432 int newzoom = displayZoomLevel + zoomOffset;1433 if (newzoom < MIN_ZOOM) {1434 continue;1435 }1436 if (missedTiles.isEmpty()) {1437 break;1438 }1439 List<Tile> newlyMissedTiles = new LinkedList<>();1440 for (Tile missed : missedTiles) {1441 if ("no-tile".equals(missed.getValue("tile-info")) && zoomOffset > 0) {1442 // Don't try to paint from higher zoom levels when tile is overzoomed1443 newlyMissedTiles.add(missed);1444 continue;1445 }1446 Tile t2 = tempCornerTile(missed);1447 LatLon topLeft2 = tileLatLon(missed);1448 LatLon botRight2 = tileLatLon(t2);1449 TileSet ts2 = new TileSet(topLeft2, botRight2, newzoom);1450 // Instantiating large TileSets is expensive. If there1451 // are no loaded tiles, don't bother even trying.1452 if (ts2.allLoadedTiles().isEmpty()) {1453 newlyMissedTiles.add(missed);1454 continue;1455 }1456 if (ts2.tooLarge()) {1457 continue;1458 }1459 newlyMissedTiles.addAll(this.paintTileImages(g, ts2, newzoom, missed));1460 }1461 missedTiles = newlyMissedTiles;1462 }1463 if (Main.isDebugEnabled() && !missedTiles.isEmpty()) {1464 Main.debug("still missed "+missedTiles.size()+" in the end");1465 }1466 g.setColor(Color.red);1467 g.setFont(InfoFont);1468 1469 // The current zoom tileset should have all of its tiles1470 // due to the loadAllTiles(), unless it to tooLarge()1471 for (Tile t : ts.allExistingTiles()) {1472 this.paintTileText(ts, t, g, mv, displayZoomLevel, t);1473 }1474 1475 attribution.paintAttribution(g, mv.getWidth(), mv.getHeight(), getShiftedCoord(topLeft), getShiftedCoord(botRight), displayZoomLevel, this);1476 1477 //g.drawString("currentZoomLevel=" + currentZoomLevel, 120, 120);1478 g.setColor(Color.lightGray);1479 if (!autoZoom) {1480 if (ts.insane()) {1481 myDrawString(g, tr("zoom in to load any tiles"), 120, 120);1482 } else if (ts.tooLarge()) {1483 myDrawString(g, tr("zoom in to load more tiles"), 120, 120);1484 } else if (ts.tooSmall()) {1485 myDrawString(g, tr("increase zoom level to see more detail"), 120, 120);1486 }1487 }1488 if (noTilesAtZoom) {1489 myDrawString(g, tr("No tiles at this zoom level"), 120, 120);1490 }1491 if (Main.isDebugEnabled()) {1492 myDrawString(g, tr("Current zoom: {0}", currentZoomLevel), 50, 140);1493 myDrawString(g, tr("Display zoom: {0}", displayZoomLevel), 50, 155);1494 myDrawString(g, tr("Pixel scale: {0}", getScaleFactor(currentZoomLevel)), 50, 170);1495 myDrawString(g, tr("Best zoom: {0}", getBestZoom()), 50, 185);1496 if (tileLoader instanceof TMSCachedTileLoader) {1497 TMSCachedTileLoader cachedTileLoader = (TMSCachedTileLoader) tileLoader;1498 int offset = 185;1499 for (String part: cachedTileLoader.getStats().split("\n")) {1500 myDrawString(g, tr("Cache stats: {0}", part), 50, offset += 15);1501 }1502 }1503 }1504 }1505 1506 /**1507 * This isn't very efficient, but it is only used when the1508 * user right-clicks on the map.1509 */1510 private Tile getTileForPixelpos(int px, int py) {1511 if (Main.isDebugEnabled()) {1512 Main.debug("getTileForPixelpos("+px+", "+py+")");1513 }1514 MapView mv = Main.map.mapView;1515 Point clicked = new Point(px, py);1516 EastNorth topLeft = mv.getEastNorth(0, 0);1517 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());1518 int z = currentZoomLevel;1519 TileSet ts = new TileSet(topLeft, botRight, z);1520 1521 if (!ts.tooLarge()) {1522 ts.loadAllTiles(false); // make sure there are tile objects for all tiles1523 }1524 Tile clickedTile = null;1525 for (Tile t1 : ts.allExistingTiles()) {1526 Tile t2 = tempCornerTile(t1);1527 Rectangle r = new Rectangle(pixelPos(t1));1528 r.add(pixelPos(t2));1529 if (Main.isDebugEnabled()) {1530 Main.debug("r: " + r + " clicked: " + clicked);1531 }1532 if (!r.contains(clicked)) {1533 continue;1534 }1535 clickedTile = t1;1536 break;1537 }1538 if (clickedTile == null)1539 return null;1540 /*Main.debug("Clicked on tile: " + clickedTile.getXtile() + " " + clickedTile.getYtile() +1541 " currentZoomLevel: " + currentZoomLevel);*/1542 return clickedTile;1543 }1544 1545 @Override1546 public Action[] getMenuEntries() {1547 return new Action[] {1548 LayerListDialog.getInstance().createShowHideLayerAction(),1549 LayerListDialog.getInstance().createDeleteLayerAction(),1550 SeparatorLayerAction.INSTANCE,1551 // color,1552 new OffsetAction(),1553 new RenameLayerAction(this.getAssociatedFile(), this),1554 SeparatorLayerAction.INSTANCE,1555 new LayerListPopup.InfoAction(this) };1556 }1557 1558 @Override1559 public String getToolTipText() {1560 return tr("TMS layer ({0}), downloading in zoom {1}", getName(), currentZoomLevel);1561 }1562 1563 @Override1564 public void visitBoundingBox(BoundingXYVisitor v) {1565 }1566 1567 @Override1568 public boolean isChanged() {1569 return needRedraw;1570 }1571 1572 @Override1573 public final boolean isProjectionSupported(Projection proj) {1574 return "EPSG:3857".equals(proj.toCode()) || "EPSG:4326".equals(proj.toCode());1575 }1576 1577 @Override1578 public final String nameSupportedProjections() {1579 return tr("EPSG:4326 and Mercator projection are supported");1580 }1581 146 } -
trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java
r8513 r8526 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.Component;7 import java.awt.Graphics;8 import java.awt.Graphics2D;9 import java.awt.Image;10 import java.awt.Point;11 6 import java.awt.event.ActionEvent; 12 import java.awt.event.MouseAdapter;13 import java.awt.event.MouseEvent;14 import java.awt.image.BufferedImage;15 import java.awt.image.ImageObserver;16 import java.io.Externalizable;17 import java.io.File;18 7 import java.io.IOException; 19 import java.io.InvalidClassException;20 import java.io.ObjectInput;21 import java.io.ObjectOutput;22 8 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.HashSet; 25 import java.util.Iterator; 9 import java.util.Arrays; 26 10 import java.util.List; 27 import java.util.Locale; 28 import java.util.Set; 29 import java.util.concurrent.locks.Condition; 30 import java.util.concurrent.locks.Lock; 31 import java.util.concurrent.locks.ReentrantLock; 11 import java.util.Map; 32 12 33 13 import javax.swing.AbstractAction; 34 14 import javax.swing.Action; 35 import javax.swing.JCheckBoxMenuItem;36 import javax.swing.JMenuItem;37 import javax.swing.JOptionPane;38 15 39 import org.openstreetmap.gui.jmapviewer.AttributionSupport; 16 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 17 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 18 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 40 19 import org.openstreetmap.josm.Main; 41 import org.openstreetmap.josm.actions.SaveActionBase; 42 import org.openstreetmap.josm.data.Bounds; 43 import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; 44 import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; 45 import org.openstreetmap.josm.data.ProjectionBounds; 46 import org.openstreetmap.josm.data.coor.EastNorth; 47 import org.openstreetmap.josm.data.coor.LatLon; 48 import org.openstreetmap.josm.data.imagery.GeorefImage; 49 import org.openstreetmap.josm.data.imagery.GeorefImage.State; 20 import org.openstreetmap.josm.data.imagery.CachedTileLoaderFactory; 50 21 import org.openstreetmap.josm.data.imagery.ImageryInfo; 51 22 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType; 52 23 import org.openstreetmap.josm.data.imagery.ImageryLayerInfo; 53 import org.openstreetmap.josm.data.imagery. WmsCache;54 import org.openstreetmap.josm.data.imagery. types.ObjectFactory;55 import org.openstreetmap.josm.data. osm.visitor.BoundingXYVisitor;24 import org.openstreetmap.josm.data.imagery.TemplatedWMSTileSource; 25 import org.openstreetmap.josm.data.imagery.TileLoaderFactory; 26 import org.openstreetmap.josm.data.imagery.WMSCachedTileLoader; 56 27 import org.openstreetmap.josm.data.preferences.BooleanProperty; 57 28 import org.openstreetmap.josm.data.preferences.IntegerProperty; 58 29 import org.openstreetmap.josm.data.projection.Projection; 30 import org.openstreetmap.josm.data.projection.ProjectionChangeListener; 59 31 import org.openstreetmap.josm.gui.MapView; 60 32 import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 61 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;62 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;63 import org.openstreetmap.josm.gui.progress.ProgressMonitor;64 import org.openstreetmap.josm.io.WMSLayerImporter;65 import org.openstreetmap.josm.io.imagery.HTMLGrabber;66 import org.openstreetmap.josm.io.imagery.WMSException;67 import org.openstreetmap.josm.io.imagery.WMSGrabber;68 import org.openstreetmap.josm.io.imagery.WMSRequest;69 import org.openstreetmap.josm.tools.ImageProvider;70 import org.openstreetmap.josm.tools.Utils;71 33 72 34 /** 73 35 * This is a layer that grabs the current screen from an WMS server. The data 74 36 * fetched this way is tiled and managed to the disc to reduce server load. 37 * 75 38 */ 76 public class WMSLayer extends ImageryLayer implements ImageObserver, PreferenceChangedListener, Externalizable { 77 78 public static class PrecacheTask { 79 private final ProgressMonitor progressMonitor; 80 private volatile int totalCount; 81 private volatile int processedCount; 82 private volatile boolean isCancelled; 83 84 public PrecacheTask(ProgressMonitor progressMonitor) { 85 this.progressMonitor = progressMonitor; 86 } 87 88 public boolean isFinished() { 89 return totalCount == processedCount; 90 } 91 92 public int getTotalCount() { 93 return totalCount; 94 } 95 96 public void cancel() { 97 isCancelled = true; 98 } 99 } 100 101 // Fake reference to keep build scripts from removing ObjectFactory class. This class is not used directly but it's necessary for jaxb to work 102 @SuppressWarnings("unused") 103 private static final ObjectFactory OBJECT_FACTORY = null; 104 105 // these values correspond to the zoom levels used throughout OSM and are in meters/pixel from zoom level 0 to 18. 106 // taken from http://wiki.openstreetmap.org/wiki/Zoom_levels 107 private static final Double[] snapLevels = {156412.0, 78206.0, 39103.0, 19551.0, 9776.0, 4888.0, 108 2444.0, 1222.0, 610.984, 305.492, 152.746, 76.373, 38.187, 19.093, 9.547, 4.773, 2.387, 1.193, 0.596}; 109 110 public static final BooleanProperty PROP_ALPHA_CHANNEL = new BooleanProperty("imagery.wms.alpha_channel", true); 111 public static final IntegerProperty PROP_SIMULTANEOUS_CONNECTIONS = new IntegerProperty("imagery.wms.simultaneousConnections", 3); 112 public static final BooleanProperty PROP_OVERLAP = new BooleanProperty("imagery.wms.overlap", false); 113 public static final IntegerProperty PROP_OVERLAP_EAST = new IntegerProperty("imagery.wms.overlapEast", 14); 114 public static final IntegerProperty PROP_OVERLAP_NORTH = new IntegerProperty("imagery.wms.overlapNorth", 4); 115 public static final IntegerProperty PROP_IMAGE_SIZE = new IntegerProperty("imagery.wms.imageSize", 500); 39 public class WMSLayer extends AbstractTileSourceLayer { 40 /** default tile size for WMS Layer */ 41 public static final IntegerProperty PROP_IMAGE_SIZE = new IntegerProperty("imagery.wms.imageSize", 512); 42 /** should WMS layer autozoom in default mode */ 116 43 public static final BooleanProperty PROP_DEFAULT_AUTOZOOM = new BooleanProperty("imagery.wms.default_autozoom", true); 117 118 public int messageNum = 5; //limit for messages per layer119 protected double resolution;120 protected String resolutionText;121 protected int imageSize;122 protected int dax = 10;123 protected int day = 10;124 protected int daStep = 5;125 protected int minZoom = 3;126 127 protected GeorefImage[][] images;128 protected static final int serializeFormatVersion = 5;129 protected boolean autoDownloadEnabled = true;130 protected boolean autoResolutionEnabled = PROP_DEFAULT_AUTOZOOM.get();131 protected boolean settingsChanged;132 public transient WmsCache cache;133 private transient AttributionSupport attribution = new AttributionSupport();134 135 // Image index boundary for current view136 private volatile int bminx;137 private volatile int bminy;138 private volatile int bmaxx;139 private volatile int bmaxy;140 private volatile int leftEdge;141 private volatile int bottomEdge;142 143 // Request queue144 private final transient List<WMSRequest> requestQueue = new ArrayList<>();145 private final transient List<WMSRequest> finishedRequests = new ArrayList<>();146 /**147 * List of request currently being processed by download threads148 */149 private final transient List<WMSRequest> processingRequests = new ArrayList<>();150 private final transient Lock requestQueueLock = new ReentrantLock();151 private final transient Condition queueEmpty = requestQueueLock.newCondition();152 private final transient List<WMSGrabber> grabbers = new ArrayList<>();153 private final transient List<Thread> grabberThreads = new ArrayList<>();154 private boolean canceled;155 156 /** set to true if this layer uses an invalid base url */157 private boolean usesInvalidUrl = false;158 /** set to true if the user confirmed to use an potentially invalid WMS base url */159 private boolean isInvalidUrlConfirmed = false;160 44 161 45 /** 162 46 * Constructs a new {@code WMSLayer}. 163 */ 164 public WMSLayer() { 165 this(new ImageryInfo(tr("Blank Layer"))); 166 } 167 168 /** 169 * Constructs a new {@code WMSLayer}. 47 * @param info ImageryInfo description of the layer 170 48 */ 171 49 public WMSLayer(ImageryInfo info) { 172 50 super(info); 173 imageSize = PROP_IMAGE_SIZE.get();174 setBackgroundLayer(true); /* set global background variable */175 initializeImages();176 177 attribution.initialize(this.info);178 179 Main.pref.addPreferenceChangeListener(this);180 51 } 181 52 182 53 @Override 183 54 public void hookUpMapView() { 184 if (info.getUrl() != null) { 185 startGrabberThreads(); 55 super.hookUpMapView(); 56 final ProjectionChangeListener listener = new ProjectionChangeListener() { 57 @Override 58 public void projectionChanged(Projection oldValue, Projection newValue) { 59 if (!oldValue.equals(newValue) && tileSource instanceof TemplatedWMSTileSource) { 60 ((TemplatedWMSTileSource)tileSource).initProjection(newValue); 61 } 186 62 187 for (WMSLayer layer: Main.map.mapView.getLayersOfType(WMSLayer.class)) {188 if (layer.getInfo().getUrl().equals(info.getUrl())) {189 cache = layer.cache;190 break;191 }192 }193 if (cache == null) {194 cache = new WmsCache(info.getUrl(), imageSize);195 cache.loadIndex();196 }197 }198 199 // if automatic resolution is enabled, ensure that the first zoom level200 // is already snapped. Otherwise it may load tiles that will never get201 // used again when zooming.202 updateResolutionSetting(this, autoResolutionEnabled);203 204 final MouseAdapter adapter = new MouseAdapter() {205 @Override206 public void mouseClicked(MouseEvent e) {207 if (!isVisible()) return;208 if (e.getButton() == MouseEvent.BUTTON1) {209 attribution.handleAttribution(e.getPoint(), true);210 }211 63 } 212 64 }; 213 Main. map.mapView.addMouseListener(adapter);65 Main.addProjectionChangeListener(listener); 214 66 215 67 MapView.addLayerChangeListener(new LayerChangeListener() { 216 68 @Override 217 69 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 218 // 70 // empty 219 71 } 220 72 221 73 @Override 222 74 public void layerAdded(Layer newLayer) { 223 // 75 // empty 224 76 } 225 77 … … 227 79 public void layerRemoved(Layer oldLayer) { 228 80 if (oldLayer == WMSLayer.this) { 229 Main.map.mapView.removeMouseListener(adapter); 230 MapView.removeLayerChangeListener(this); 81 Main.removeProjectionChangeListener(listener); 231 82 } 232 83 } … … 234 85 } 235 86 236 public void doSetName(String name) { 237 setName(name); 238 info.setName(name); 87 @Override 88 public Action[] getMenuEntries() { 89 List<Action> ret = new ArrayList<>(); 90 ret.addAll(Arrays.asList(super.getMenuEntries())); 91 ret.add(SeparatorLayerAction.INSTANCE); 92 ret.add(new LayerSaveAction(this)); 93 ret.add(new LayerSaveAsAction(this)); 94 ret.add(new BookmarkWmsAction()); 95 return ret.toArray(new Action[]{}); 239 96 } 240 97 241 public boolean hasAutoDownload() {242 return autoDownloadEnabled;243 }244 245 public void setAutoDownload(boolean val) {246 autoDownloadEnabled = val;247 }248 249 public boolean isAutoResolution() {250 return autoResolutionEnabled;251 }252 253 public void setAutoResolution(boolean val) {254 autoResolutionEnabled = val;255 }256 257 public void downloadAreaToCache(PrecacheTask precacheTask, List<LatLon> points, double bufferX, double bufferY) {258 Set<Point> requestedTiles = new HashSet<>();259 for (LatLon point: points) {260 EastNorth minEn = Main.getProjection().latlon2eastNorth(new LatLon(point.lat() - bufferY, point.lon() - bufferX));261 EastNorth maxEn = Main.getProjection().latlon2eastNorth(new LatLon(point.lat() + bufferY, point.lon() + bufferX));262 int minX = getImageXIndex(minEn.east());263 int maxX = getImageXIndex(maxEn.east());264 int minY = getImageYIndex(minEn.north());265 int maxY = getImageYIndex(maxEn.north());266 267 for (int x = minX; x <= maxX; x++) {268 for (int y = minY; y <= maxY; y++) {269 requestedTiles.add(new Point(x, y));270 }271 }272 }273 274 for (Point p: requestedTiles) {275 addRequest(new WMSRequest(p.x, p.y, info.getPixelPerDegree(), true, false, precacheTask));276 }277 278 precacheTask.progressMonitor.setTicksCount(precacheTask.getTotalCount());279 precacheTask.progressMonitor.setCustomText(tr("Downloaded {0}/{1} tiles", 0, precacheTask.totalCount));280 }281 98 282 99 @Override 283 p ublic void destroy(){284 super.destroy();285 cancelGrabberThreads(false);286 Main.pref.removePreferenceChangeListener(this);287 if (cache != null) {288 cache.saveIndex();100 protected TileSource getTileSource(ImageryInfo info) throws IllegalArgumentException { 101 if (info.getImageryType() == ImageryType.WMS && info.getUrl() != null) { 102 TemplatedWMSTileSource.checkUrl(info.getUrl()); 103 TemplatedWMSTileSource tileSource = new TemplatedWMSTileSource(info); 104 info.setAttribution(tileSource); 105 return tileSource; 289 106 } 290 } 291 292 public final void initializeImages() { 293 GeorefImage[][] old = images; 294 images = new GeorefImage[dax][day]; 295 if (old != null) { 296 for (GeorefImage[] row : old) { 297 for (GeorefImage image : row) { 298 images[modulo(image.getXIndex(), dax)][modulo(image.getYIndex(), day)] = image; 299 } 300 } 301 } 302 for (int x = 0; x < dax; ++x) { 303 for (int y = 0; y < day; ++y) { 304 if (images[x][y] == null) { 305 images[x][y] = new GeorefImage(this); 306 } 307 } 308 } 309 } 310 311 @Override public ImageryInfo getInfo() { 312 return info; 313 } 314 315 @Override public String getToolTipText() { 316 if (autoDownloadEnabled) 317 return tr("WMS layer ({0}), automatically downloading in zoom {1}", getName(), resolutionText); 318 else 319 return tr("WMS layer ({0}), downloading in zoom {1}", getName(), resolutionText); 320 } 321 322 private int modulo(int a, int b) { 323 return a % b >= 0 ? a % b : a % b+b; 324 } 325 326 private boolean zoomIsTooBig() { 327 //don't download when it's too outzoomed 328 return info.getPixelPerDegree() / getPPD() > minZoom; 329 } 330 331 @Override 332 public void paint(Graphics2D g, final MapView mv, Bounds b) { 333 if (info.getUrl() == null || (usesInvalidUrl && !isInvalidUrlConfirmed)) return; 334 335 if (autoResolutionEnabled && !Utils.equalsEpsilon(getBestZoom(), mv.getDist100Pixel())) { 336 changeResolution(this, true); 337 } 338 339 settingsChanged = false; 340 341 ProjectionBounds bounds = mv.getProjectionBounds(); 342 bminx = getImageXIndex(bounds.minEast); 343 bminy = getImageYIndex(bounds.minNorth); 344 bmaxx = getImageXIndex(bounds.maxEast); 345 bmaxy = getImageYIndex(bounds.maxNorth); 346 347 leftEdge = (int) (bounds.minEast * getPPD()); 348 bottomEdge = (int) (bounds.minNorth * getPPD()); 349 350 if (zoomIsTooBig()) { 351 for (int x = 0; x < images.length; ++x) { 352 for (int y = 0; y < images[0].length; ++y) { 353 GeorefImage image = images[x][y]; 354 image.paint(g, mv, image.getXIndex(), image.getYIndex(), leftEdge, bottomEdge); 355 } 356 } 357 } else { 358 downloadAndPaintVisible(g, mv, false); 359 } 360 361 attribution.paintAttribution(g, mv.getWidth(), mv.getHeight(), null, null, 0, this); 362 } 363 364 @Override 365 public void setOffset(double dx, double dy) { 366 super.setOffset(dx, dy); 367 settingsChanged = true; 368 } 369 370 public int getImageXIndex(double coord) { 371 return (int) Math.floor(((coord - dx) * info.getPixelPerDegree()) / imageSize); 372 } 373 374 public int getImageYIndex(double coord) { 375 return (int) Math.floor(((coord - dy) * info.getPixelPerDegree()) / imageSize); 376 } 377 378 public int getImageX(int imageIndex) { 379 return (int) (imageIndex * imageSize * (getPPD() / info.getPixelPerDegree()) + dx * getPPD()); 380 } 381 382 public int getImageY(int imageIndex) { 383 return (int) (imageIndex * imageSize * (getPPD() / info.getPixelPerDegree()) + dy * getPPD()); 384 } 385 386 public int getImageWidth(int xIndex) { 387 return getImageX(xIndex + 1) - getImageX(xIndex); 388 } 389 390 public int getImageHeight(int yIndex) { 391 return getImageY(yIndex + 1) - getImageY(yIndex); 392 } 393 394 /** 395 * 396 * @return Size of image in original zoom 397 */ 398 public int getBaseImageWidth() { 399 int overlap = PROP_OVERLAP.get() ? PROP_OVERLAP_EAST.get() * imageSize / 100 : 0; 400 return imageSize + overlap; 401 } 402 403 /** 404 * 405 * @return Size of image in original zoom 406 */ 407 public int getBaseImageHeight() { 408 int overlap = PROP_OVERLAP.get() ? PROP_OVERLAP_NORTH.get() * imageSize / 100 : 0; 409 return imageSize + overlap; 410 } 411 412 public int getImageSize() { 413 return imageSize; 414 } 415 416 public boolean isOverlapEnabled() { 417 return WMSLayer.PROP_OVERLAP.get() && (WMSLayer.PROP_OVERLAP_EAST.get() > 0 || WMSLayer.PROP_OVERLAP_NORTH.get() > 0); 418 } 419 420 /** 421 * 422 * @return When overlapping is enabled, return visible part of tile. Otherwise return original image 423 */ 424 public BufferedImage normalizeImage(BufferedImage img) { 425 if (isOverlapEnabled()) { 426 BufferedImage copy = img; 427 img = new BufferedImage(imageSize, imageSize, copy.getType()); 428 img.createGraphics().drawImage(copy, 0, 0, imageSize, imageSize, 429 0, copy.getHeight() - imageSize, imageSize, copy.getHeight(), null); 430 } 431 return img; 432 } 433 434 /** 435 * Returns east/north for a x/y couple. 436 * @param xIndex x index 437 * @param yIndex y index 438 * @return Real EastNorth of given tile. dx/dy is not counted in 439 */ 440 public EastNorth getEastNorth(int xIndex, int yIndex) { 441 return new EastNorth((xIndex * imageSize) / info.getPixelPerDegree(), (yIndex * imageSize) / info.getPixelPerDegree()); 442 } 443 444 protected void downloadAndPaintVisible(Graphics g, final MapView mv, boolean real) { 445 446 int newDax = dax; 447 int newDay = day; 448 449 if (bmaxx - bminx >= dax || bmaxx - bminx < dax - 2 * daStep) { 450 newDax = ((bmaxx - bminx) / daStep + 1) * daStep; 451 } 452 453 if (bmaxy - bminy >= day || bmaxy - bminx < day - 2 * daStep) { 454 newDay = ((bmaxy - bminy) / daStep + 1) * daStep; 455 } 456 457 if (newDax != dax || newDay != day) { 458 dax = newDax; 459 day = newDay; 460 initializeImages(); 461 } 462 463 for (int x = bminx; x <= bmaxx; ++x) { 464 for (int y = bminy; y <= bmaxy; ++y) { 465 images[modulo(x, dax)][modulo(y, day)].changePosition(x, y); 466 } 467 } 468 469 gatherFinishedRequests(); 470 Set<ProjectionBounds> areaToCache = new HashSet<>(); 471 472 for (int x = bminx; x <= bmaxx; ++x) { 473 for (int y = bminy; y <= bmaxy; ++y) { 474 GeorefImage img = images[modulo(x, dax)][modulo(y, day)]; 475 if (!img.paint(g, mv, x, y, leftEdge, bottomEdge)) { 476 addRequest(new WMSRequest(x, y, info.getPixelPerDegree(), real, true)); 477 areaToCache.add(new ProjectionBounds(getEastNorth(x, y), getEastNorth(x + 1, y + 1))); 478 } else if (img.getState() == State.PARTLY_IN_CACHE && autoDownloadEnabled) { 479 addRequest(new WMSRequest(x, y, info.getPixelPerDegree(), real, false)); 480 areaToCache.add(new ProjectionBounds(getEastNorth(x, y), getEastNorth(x + 1, y + 1))); 481 } 482 } 483 } 484 if (cache != null) { 485 cache.setAreaToCache(areaToCache); 486 } 487 } 488 489 @Override 490 public void visitBoundingBox(BoundingXYVisitor v) { 491 for (int x = 0; x < dax; ++x) { 492 for (int y = 0; y < day; ++y) { 493 if (images[x][y].getImage() != null) { 494 v.visit(images[x][y].getMin()); 495 v.visit(images[x][y].getMax()); 496 } 497 } 498 } 499 } 500 501 @Override 502 public Action[] getMenuEntries() { 503 return new Action[]{ 504 LayerListDialog.getInstance().createActivateLayerAction(this), 505 LayerListDialog.getInstance().createShowHideLayerAction(), 506 LayerListDialog.getInstance().createDeleteLayerAction(), 507 SeparatorLayerAction.INSTANCE, 508 new OffsetAction(), 509 new LayerSaveAction(this), 510 new LayerSaveAsAction(this), 511 new BookmarkWmsAction(), 512 SeparatorLayerAction.INSTANCE, 513 new StartStopAction(), 514 new ToggleAlphaAction(), 515 new ToggleAutoResolutionAction(), 516 new ChangeResolutionAction(), 517 new ZoomToNativeResolution(), 518 new ReloadErrorTilesAction(), 519 new DownloadAction(), 520 SeparatorLayerAction.INSTANCE, 521 new LayerListPopup.InfoAction(this) 522 }; 523 } 524 525 public GeorefImage findImage(EastNorth eastNorth) { 526 int xIndex = getImageXIndex(eastNorth.east()); 527 int yIndex = getImageYIndex(eastNorth.north()); 528 GeorefImage result = images[modulo(xIndex, dax)][modulo(yIndex, day)]; 529 if (result.getXIndex() == xIndex && result.getYIndex() == yIndex) 530 return result; 531 else 532 return null; 533 } 534 535 /** 536 * Replies request priority. 537 * @param request WMS request 538 * @return -1 if request is no longer needed, otherwise priority of request (lower number <=> more important request) 539 */ 540 private int getRequestPriority(WMSRequest request) { 541 if (!Utils.equalsEpsilon(request.getPixelPerDegree(), info.getPixelPerDegree())) 542 return -1; 543 if (bminx > request.getXIndex() 544 || bmaxx < request.getXIndex() 545 || bminy > request.getYIndex() 546 || bmaxy < request.getYIndex()) 547 return -1; 548 549 MouseEvent lastMEvent = Main.map.mapView.lastMEvent; 550 EastNorth cursorEastNorth = Main.map.mapView.getEastNorth(lastMEvent.getX(), lastMEvent.getY()); 551 int mouseX = getImageXIndex(cursorEastNorth.east()); 552 int mouseY = getImageYIndex(cursorEastNorth.north()); 553 int dx = request.getXIndex() - mouseX; 554 int dy = request.getYIndex() - mouseY; 555 556 return 1 + dx * dx + dy * dy; 557 } 558 559 private void sortRequests(boolean localOnly) { 560 Iterator<WMSRequest> it = requestQueue.iterator(); 561 while (it.hasNext()) { 562 WMSRequest item = it.next(); 563 564 if (item.getPrecacheTask() != null && item.getPrecacheTask().isCancelled) { 565 it.remove(); 566 continue; 567 } 568 569 int priority = getRequestPriority(item); 570 if (priority == -1 && item.isPrecacheOnly()) { 571 priority = Integer.MAX_VALUE; // Still download, but prefer requests in current view 572 } 573 574 if (localOnly && !item.hasExactMatch()) { 575 priority = Integer.MAX_VALUE; // Only interested in tiles that can be loaded from file immediately 576 } 577 578 if (priority == -1 579 || finishedRequests.contains(item) 580 || processingRequests.contains(item)) { 581 it.remove(); 582 } else { 583 item.setPriority(priority); 584 } 585 } 586 Collections.sort(requestQueue); 587 } 588 589 public WMSRequest getRequest(boolean localOnly) { 590 requestQueueLock.lock(); 591 try { 592 sortRequests(localOnly); 593 while (!canceled && (requestQueue.isEmpty() || (localOnly && !requestQueue.get(0).hasExactMatch()))) { 594 try { 595 queueEmpty.await(); 596 sortRequests(localOnly); 597 } catch (InterruptedException e) { 598 Main.warn("InterruptedException in "+getClass().getSimpleName()+" during WMS request"); 599 } 600 } 601 602 if (canceled) 603 return null; 604 else { 605 WMSRequest request = requestQueue.remove(0); 606 processingRequests.add(request); 607 return request; 608 } 609 610 } finally { 611 requestQueueLock.unlock(); 612 } 613 } 614 615 public void finishRequest(WMSRequest request) { 616 requestQueueLock.lock(); 617 try { 618 PrecacheTask task = request.getPrecacheTask(); 619 if (task != null) { 620 task.processedCount++; 621 if (!task.progressMonitor.isCanceled()) { 622 task.progressMonitor.worked(1); 623 task.progressMonitor.setCustomText(tr("Downloaded {0}/{1} tiles", task.processedCount, task.totalCount)); 624 } 625 } 626 processingRequests.remove(request); 627 if (request.getState() != null && !request.isPrecacheOnly()) { 628 finishedRequests.add(request); 629 if (Main.isDisplayingMapView()) { 630 Main.map.mapView.repaint(); 631 } 632 } 633 } finally { 634 requestQueueLock.unlock(); 635 } 636 } 637 638 public void addRequest(WMSRequest request) { 639 requestQueueLock.lock(); 640 try { 641 642 if (cache != null) { 643 ProjectionBounds b = getBounds(request); 644 // Checking for exact match is fast enough, no need to do it in separated thread 645 request.setHasExactMatch(cache.hasExactMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth)); 646 if (request.isPrecacheOnly() && request.hasExactMatch()) 647 return; // We already have this tile cached 648 } 649 650 if (!requestQueue.contains(request) && !finishedRequests.contains(request) && !processingRequests.contains(request)) { 651 requestQueue.add(request); 652 if (request.getPrecacheTask() != null) { 653 request.getPrecacheTask().totalCount++; 654 } 655 queueEmpty.signalAll(); 656 } 657 } finally { 658 requestQueueLock.unlock(); 659 } 660 } 661 662 public boolean requestIsVisible(WMSRequest request) { 663 return bminx <= request.getXIndex() && bmaxx >= request.getXIndex() && bminy <= request.getYIndex() && bmaxy >= request.getYIndex(); 664 } 665 666 private void gatherFinishedRequests() { 667 requestQueueLock.lock(); 668 try { 669 for (WMSRequest request: finishedRequests) { 670 GeorefImage img = images[modulo(request.getXIndex(), dax)][modulo(request.getYIndex(), day)]; 671 if (img.equalPosition(request.getXIndex(), request.getYIndex())) { 672 WMSException we = request.getException(); 673 img.changeImage(request.getState(), request.getImage(), we != null ? we.getMessage() : null); 674 } 675 } 676 } finally { 677 requestQueueLock.unlock(); 678 finishedRequests.clear(); 679 } 680 } 681 682 public class DownloadAction extends AbstractAction { 683 /** 684 * Constructs a new {@code DownloadAction}. 685 */ 686 public DownloadAction() { 687 super(tr("Download visible tiles")); 688 } 689 690 @Override 691 public void actionPerformed(ActionEvent ev) { 692 if (zoomIsTooBig()) { 693 JOptionPane.showMessageDialog( 694 Main.parent, 695 tr("The requested area is too big. Please zoom in a little, or change resolution"), 696 tr("Error"), 697 JOptionPane.ERROR_MESSAGE 698 ); 699 } else { 700 downloadAndPaintVisible(Main.map.mapView.getGraphics(), Main.map.mapView, true); 701 } 702 } 703 } 704 705 /** 706 * Finds the most suitable resolution for the current zoom level, but prefers 707 * higher resolutions. Snaps to values defined in snapLevels. 708 * @return best zoom level 709 */ 710 private static double getBestZoom() { 711 // not sure why getDist100Pixel returns values corresponding to 712 // the snapLevels, which are in meters per pixel. It works, though. 713 double dist = Main.map.mapView.getDist100Pixel(); 714 for (int i = snapLevels.length-2; i >= 0; i--) { 715 if (snapLevels[i+1]/3 + snapLevels[i]*2/3 > dist) 716 return snapLevels[i+1]; 717 } 718 return snapLevels[0]; 719 } 720 721 /** 722 * Updates the given layer’s resolution settings to the current zoom level. Does 723 * not update existing tiles, only new ones will be subject to the new settings. 724 * 725 * @param layer WMS layer 726 * @param snap Set to true if the resolution should snap to certain values instead of 727 * matching the current zoom level perfectly 728 */ 729 private static void updateResolutionSetting(WMSLayer layer, boolean snap) { 730 if (snap) { 731 layer.resolution = getBestZoom(); 732 layer.resolutionText = MapView.getDistText(layer.resolution); 733 } else { 734 layer.resolution = Main.map.mapView.getDist100Pixel(); 735 layer.resolutionText = Main.map.mapView.getDist100PixelText(); 736 } 737 layer.info.setPixelPerDegree(layer.getPPD()); 738 } 739 740 /** 741 * Updates the given layer’s resolution settings to the current zoom level and 742 * updates existing tiles. If round is true, tiles will be updated gradually, if 743 * false they will be removed instantly (and redrawn only after the new resolution 744 * image has been loaded). 745 * @param layer WMS layer 746 * @param snap Set to true if the resolution should snap to certain values instead of 747 * matching the current zoom level perfectly 748 */ 749 private static void changeResolution(WMSLayer layer, boolean snap) { 750 updateResolutionSetting(layer, snap); 751 752 layer.settingsChanged = true; 753 754 // Don’t move tiles off screen when the resolution is rounded. This 755 // prevents some flickering when zooming with auto-resolution enabled 756 // and instead gradually updates each tile. 757 if (!snap) { 758 for (int x = 0; x < layer.dax; ++x) { 759 for (int y = 0; y < layer.day; ++y) { 760 layer.images[x][y].changePosition(-1, -1); 761 } 762 } 763 } 764 } 765 766 public static class ChangeResolutionAction extends AbstractAction implements LayerAction { 767 768 /** 769 * Constructs a new {@code ChangeResolutionAction} 770 */ 771 public ChangeResolutionAction() { 772 super(tr("Change resolution")); 773 } 774 775 @Override 776 public void actionPerformed(ActionEvent ev) { 777 List<Layer> layers = LayerListDialog.getInstance().getModel().getSelectedLayers(); 778 for (Layer l: layers) { 779 changeResolution((WMSLayer) l, false); 780 } 781 Main.map.mapView.repaint(); 782 } 783 784 @Override 785 public boolean supportLayers(List<Layer> layers) { 786 for (Layer l: layers) { 787 if (!(l instanceof WMSLayer)) 788 return false; 789 } 790 return true; 791 } 792 793 @Override 794 public Component createMenuComponent() { 795 return new JMenuItem(this); 796 } 797 } 798 799 public class ReloadErrorTilesAction extends AbstractAction { 800 /** 801 * Constructs a new {@code ReloadErrorTilesAction}. 802 */ 803 public ReloadErrorTilesAction() { 804 super(tr("Reload erroneous tiles")); 805 } 806 807 @Override 808 public void actionPerformed(ActionEvent ev) { 809 // Delete small files, because they're probably blank tiles. 810 // See #2307 811 cache.cleanSmallFiles(4096); 812 813 for (int x = 0; x < dax; ++x) { 814 for (int y = 0; y < day; ++y) { 815 GeorefImage img = images[modulo(x, dax)][modulo(y, day)]; 816 if (img.getState() == State.FAILED) { 817 addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), info.getPixelPerDegree(), true, false)); 818 } 819 } 820 } 821 } 822 } 823 824 public class ToggleAlphaAction extends AbstractAction implements LayerAction { 825 /** 826 * Constructs a new {@code ToggleAlphaAction}. 827 */ 828 public ToggleAlphaAction() { 829 super(tr("Alpha channel")); 830 } 831 832 @Override 833 public void actionPerformed(ActionEvent ev) { 834 JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) ev.getSource(); 835 boolean alphaChannel = checkbox.isSelected(); 836 PROP_ALPHA_CHANNEL.put(alphaChannel); 837 Main.info("WMS Alpha channel changed to "+alphaChannel); 838 839 // clear all resized cached instances and repaint the layer 840 for (int x = 0; x < dax; ++x) { 841 for (int y = 0; y < day; ++y) { 842 GeorefImage img = images[modulo(x, dax)][modulo(y, day)]; 843 img.flushResizedCachedInstance(); 844 BufferedImage bi = img.getImage(); 845 // Completely erases images for which transparency has been forced, 846 // or images that should be forced now, as they need to be recreated 847 if (ImageProvider.isTransparencyForced(bi) || ImageProvider.hasTransparentColor(bi)) { 848 img.resetImage(); 849 } 850 } 851 } 852 Main.map.mapView.repaint(); 853 } 854 855 @Override 856 public Component createMenuComponent() { 857 JCheckBoxMenuItem item = new JCheckBoxMenuItem(this); 858 item.setSelected(PROP_ALPHA_CHANNEL.get()); 859 return item; 860 } 861 862 @Override 863 public boolean supportLayers(List<Layer> layers) { 864 return layers.size() == 1 && layers.get(0) instanceof WMSLayer; 865 } 866 } 867 868 public class ToggleAutoResolutionAction extends AbstractAction implements LayerAction { 869 870 /** 871 * Constructs a new {@code ToggleAutoResolutionAction}. 872 */ 873 public ToggleAutoResolutionAction() { 874 super(tr("Automatically change resolution")); 875 } 876 877 @Override 878 public void actionPerformed(ActionEvent ev) { 879 JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) ev.getSource(); 880 autoResolutionEnabled = checkbox.isSelected(); 881 } 882 883 @Override 884 public Component createMenuComponent() { 885 JCheckBoxMenuItem item = new JCheckBoxMenuItem(this); 886 item.setSelected(autoResolutionEnabled); 887 return item; 888 } 889 890 @Override 891 public boolean supportLayers(List<Layer> layers) { 892 return layers.size() == 1 && layers.get(0) instanceof WMSLayer; 893 } 107 return null; 894 108 } 895 109 … … 913 127 } 914 128 915 private class StartStopAction extends AbstractAction implements LayerAction {916 917 public StartStopAction() {918 super(tr("Automatic downloading"));919 }920 921 @Override922 public Component createMenuComponent() {923 JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);924 item.setSelected(autoDownloadEnabled);925 return item;926 }927 928 @Override929 public boolean supportLayers(List<Layer> layers) {930 return layers.size() == 1 && layers.get(0) instanceof WMSLayer;931 }932 933 @Override934 public void actionPerformed(ActionEvent e) {935 autoDownloadEnabled = !autoDownloadEnabled;936 if (autoDownloadEnabled) {937 for (int x = 0; x < dax; ++x) {938 for (int y = 0; y < day; ++y) {939 GeorefImage img = images[modulo(x, dax)][modulo(y, day)];940 if (img.getState() == State.NOT_IN_CACHE) {941 addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), info.getPixelPerDegree(), false, true));942 }943 }944 }945 Main.map.mapView.repaint();946 }947 }948 }949 950 private class ZoomToNativeResolution extends AbstractAction {951 952 public ZoomToNativeResolution() {953 super(tr("Zoom to native resolution"));954 }955 956 @Override957 public void actionPerformed(ActionEvent e) {958 Main.map.mapView.zoomTo(Main.map.mapView.getCenter(), 1 / info.getPixelPerDegree());959 }960 }961 962 private void cancelGrabberThreads(boolean wait) {963 requestQueueLock.lock();964 try {965 canceled = true;966 for (WMSGrabber grabber: grabbers) {967 grabber.cancel();968 }969 queueEmpty.signalAll();970 } finally {971 requestQueueLock.unlock();972 }973 if (wait) {974 for (Thread t: grabberThreads) {975 try {976 t.join();977 } catch (InterruptedException e) {978 Main.warn("InterruptedException in "+getClass().getSimpleName()+" while cancelling grabber threads");979 }980 }981 }982 }983 984 private void startGrabberThreads() {985 int threadCount = PROP_SIMULTANEOUS_CONNECTIONS.get();986 requestQueueLock.lock();987 try {988 canceled = false;989 grabbers.clear();990 grabberThreads.clear();991 for (int i = 0; i < threadCount; i++) {992 WMSGrabber grabber = getGrabber(i == 0 && threadCount > 1);993 grabbers.add(grabber);994 Thread t = new Thread(grabber, "WMS " + getName() + " " + i);995 t.setDaemon(true);996 t.start();997 grabberThreads.add(t);998 }999 } finally {1000 requestQueueLock.unlock();1001 }1002 }1003 1004 @Override1005 public boolean isChanged() {1006 requestQueueLock.lock();1007 try {1008 return !finishedRequests.isEmpty() || settingsChanged;1009 } finally {1010 requestQueueLock.unlock();1011 }1012 }1013 1014 @Override1015 public void preferenceChanged(PreferenceChangeEvent event) {1016 if (event.getKey().equals(PROP_SIMULTANEOUS_CONNECTIONS.getKey()) && info.getUrl() != null) {1017 cancelGrabberThreads(true);1018 startGrabberThreads();1019 } else if (1020 event.getKey().equals(PROP_OVERLAP.getKey())1021 || event.getKey().equals(PROP_OVERLAP_EAST.getKey())1022 || event.getKey().equals(PROP_OVERLAP_NORTH.getKey())) {1023 for (int i = 0; i < images.length; i++) {1024 for (int k = 0; k < images[i].length; k++) {1025 images[i][k] = new GeorefImage(this);1026 }1027 }1028 1029 settingsChanged = true;1030 }1031 }1032 129 1033 130 /** … … 1037 134 */ 1038 135 public void checkGrabberType() { 1039 ImageryType it = getInfo().getImageryType();1040 if (!ImageryType.HTML.equals(it) && !ImageryType.WMS.equals(it))1041 throw new IllegalStateException("getGrabber() called for non-WMS layer type");1042 136 } 1043 137 1044 protected WMSGrabber getGrabber(boolean localOnly) { 1045 checkGrabberType(); 1046 if (getInfo().getImageryType() == ImageryType.HTML) 1047 return new HTMLGrabber(Main.map.mapView, this, localOnly); 1048 else 1049 return new WMSGrabber(Main.map.mapView, this, localOnly); 1050 } 138 private static TileLoaderFactory loaderFactory = new CachedTileLoaderFactory("WMS") { 139 @Override 140 protected TileLoader getLoader(TileLoaderListener listener, String cacheName, int connectTimeout, 141 int readTimeout, Map<String, String> headers, String cacheDir) throws IOException { 142 return new WMSCachedTileLoader(listener, cacheName, connectTimeout, readTimeout, headers, cacheDir); 143 } 1051 144 1052 public ProjectionBounds getBounds(WMSRequest request) { 1053 ProjectionBounds result = new ProjectionBounds( 1054 getEastNorth(request.getXIndex(), request.getYIndex()), 1055 getEastNorth(request.getXIndex() + 1, request.getYIndex() + 1)); 145 }; 1056 146 1057 if (WMSLayer.PROP_OVERLAP.get()) { 1058 double eastSize = result.maxEast - result.minEast; 1059 double northSize = result.maxNorth - result.minNorth; 1060 1061 double eastCoef = WMSLayer.PROP_OVERLAP_EAST.get() / 100.0; 1062 double northCoef = WMSLayer.PROP_OVERLAP_NORTH.get() / 100.0; 1063 1064 result = new ProjectionBounds(result.getMin(), 1065 new EastNorth(result.maxEast + eastCoef * eastSize, 1066 result.maxNorth + northCoef * northSize)); 1067 } 1068 return result; 147 @Override 148 protected TileLoaderFactory getTileLoaderFactory() { 149 return loaderFactory; 1069 150 } 1070 151 1071 152 @Override 1072 public boolean isProjectionSupported(Projection proj) { 1073 List<String> serverProjections = info.getServerProjections(); 1074 return serverProjections.contains(proj.toCode().toUpperCase(Locale.ENGLISH)) 1075 || ("EPSG:3857".equals(proj.toCode()) && (serverProjections.contains("EPSG:4326") || serverProjections.contains("CRS:84"))) 1076 || ("EPSG:4326".equals(proj.toCode()) && serverProjections.contains("CRS:84")); 1077 } 1078 1079 @Override 1080 public String nameSupportedProjections() { 1081 StringBuilder res = new StringBuilder(); 1082 for (String p : info.getServerProjections()) { 1083 if (res.length() > 0) { 1084 res.append(", "); 1085 } 1086 res.append(p); 153 protected Map<String, String> getHeaders(TileSource tileSource) { 154 if (tileSource instanceof TemplatedWMSTileSource) { 155 return ((TemplatedWMSTileSource)tileSource).getHeaders(); 1087 156 } 1088 return tr("Supported projections are: {0}", res); 1089 } 1090 1091 @Override 1092 public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { 1093 boolean done = (infoflags & (ERROR | FRAMEBITS | ALLBITS)) != 0; 1094 Main.map.repaint(done ? 0 : 100); 1095 return !done; 1096 } 1097 1098 @Override 1099 public void writeExternal(ObjectOutput out) throws IOException { 1100 out.writeInt(serializeFormatVersion); 1101 out.writeInt(dax); 1102 out.writeInt(day); 1103 out.writeInt(imageSize); 1104 out.writeDouble(info.getPixelPerDegree()); 1105 out.writeObject(info.getName()); 1106 out.writeObject(info.getExtendedUrl()); 1107 out.writeObject(images); 1108 } 1109 1110 @Override 1111 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 1112 int sfv = in.readInt(); 1113 if (sfv != serializeFormatVersion) 1114 throw new InvalidClassException(tr("Unsupported WMS file version; found {0}, expected {1}", sfv, serializeFormatVersion)); 1115 autoDownloadEnabled = false; 1116 dax = in.readInt(); 1117 day = in.readInt(); 1118 imageSize = in.readInt(); 1119 info.setPixelPerDegree(in.readDouble()); 1120 doSetName((String) in.readObject()); 1121 info.setExtendedUrl((String) in.readObject()); 1122 images = (GeorefImage[][]) in.readObject(); 1123 1124 for (GeorefImage[] imgs : images) { 1125 for (GeorefImage img : imgs) { 1126 if (img != null) { 1127 img.setLayer(WMSLayer.this); 1128 } 1129 } 1130 } 1131 1132 settingsChanged = true; 1133 if (Main.isDisplayingMapView()) { 1134 Main.map.mapView.repaint(); 1135 } 1136 if (cache != null) { 1137 cache.saveIndex(); 1138 cache = null; 1139 } 1140 } 1141 1142 @Override 1143 public void onPostLoadFromFile() { 1144 if (info.getUrl() != null) { 1145 cache = new WmsCache(info.getUrl(), imageSize); 1146 startGrabberThreads(); 1147 } 1148 } 1149 1150 @Override 1151 public boolean isSavable() { 1152 return true; // With WMSLayerExporter 1153 } 1154 1155 @Override 1156 public File createAndOpenSaveFileChooser() { 1157 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save WMS file"), WMSLayerImporter.FILE_FILTER); 157 return null; 1158 158 } 1159 159 } -
trunk/src/org/openstreetmap/josm/gui/layer/gpx/DownloadWmsAlongTrackAction.java
r8308 r8526 25 25 import org.openstreetmap.josm.gui.ExtendedDialog; 26 26 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 27 import org.openstreetmap.josm.gui.layer. WMSLayer;28 import org.openstreetmap.josm.gui.layer. WMSLayer.PrecacheTask;27 import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer; 28 import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer.PrecacheTask; 29 29 import org.openstreetmap.josm.gui.progress.ProgressTaskId; 30 30 import org.openstreetmap.josm.gui.progress.ProgressTaskIds; … … 35 35 import org.xml.sax.SAXException; 36 36 37 /** 38 * Class downloading WMS and TMS along the GPX track 39 * 40 */ 37 41 public class DownloadWmsAlongTrackAction extends AbstractAction { 38 42 39 43 private final transient GpxData data; 40 44 45 /** 46 * @param data that represents GPX track, along which data should be downloaded 47 */ 41 48 public DownloadWmsAlongTrackAction(final GpxData data) { 42 49 super(tr("Precache imagery tiles along this track"), ImageProvider.get("downloadalongtrack")); … … 57 64 points.add(p.getCoor()); 58 65 } 59 final WMSLayer layer = askWMSLayer();66 final AbstractTileSourceLayer layer = askedLayer(); 60 67 if (layer != null) { 61 68 PleaseWaitRunnable task = new PleaseWaitRunnable(tr("Precaching WMS")) { … … 64 71 @Override 65 72 protected void realRun() throws SAXException, IOException, OsmTransferException { 66 precacheTask = new PrecacheTask(progressMonitor); 73 precacheTask = layer.new PrecacheTask(progressMonitor); 67 74 layer.downloadAreaToCache(precacheTask, points, 0, 0); 68 75 while (!precacheTask.isFinished() && !progressMonitor.isCanceled()) { … … 95 102 } 96 103 97 protected WMSLayer askWMSLayer() {98 Collection< WMSLayer> targetLayers = Main.map.mapView.getLayersOfType(WMSLayer.class);104 protected AbstractTileSourceLayer askedLayer() { 105 Collection<AbstractTileSourceLayer> targetLayers = Main.map.mapView.getLayersOfType(AbstractTileSourceLayer.class); 99 106 if (targetLayers.isEmpty()) { 100 107 warnNoImageryLayers(); 101 108 return null; 102 109 } 103 JosmComboBox< WMSLayer> layerList = new JosmComboBox<>(targetLayers.toArray(newWMSLayer[0]));110 JosmComboBox<AbstractTileSourceLayer> layerList = new JosmComboBox<>(targetLayers.toArray(new AbstractTileSourceLayer[0])); 104 111 layerList.setRenderer(new LayerListCellRenderer()); 105 112 layerList.setSelectedIndex(0); … … 114 121 return null; 115 122 } 116 return ( WMSLayer) layerList.getSelectedItem();123 return (AbstractTileSourceLayer) layerList.getSelectedItem(); 117 124 } 118 125 -
trunk/src/org/openstreetmap/josm/gui/preferences/imagery/TMSSettingsPanel.java
r8510 r8526 12 12 import javax.swing.SpinnerNumberModel; 13 13 14 import org.openstreetmap.josm.data.imagery.CachedTileLoaderFactory; 14 15 import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader; 15 16 import org.openstreetmap.josm.data.imagery.TMSCachedTileLoaderJob; … … 41 42 public TMSSettingsPanel() { 42 43 super(new GridBagLayout()); 43 minZoomLvl = new JSpinner(new SpinnerNumberModel(TMSLayer. DEFAULT_MIN_ZOOM, TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM, 1));44 maxZoomLvl = new JSpinner(new SpinnerNumberModel(TMSLayer. DEFAULT_MAX_ZOOM, TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM, 1));44 minZoomLvl = new JSpinner(new SpinnerNumberModel(TMSLayer.PROP_MIN_ZOOM_LVL.get().intValue(), TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM, 1)); 45 maxZoomLvl = new JSpinner(new SpinnerNumberModel(TMSLayer.PROP_MAX_ZOOM_LVL.get().intValue(), TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM, 1)); 45 46 maxElementsOnDisk = new JSpinner(new SpinnerNumberModel(TMSCachedTileLoader.MAX_OBJECTS_ON_DISK.get().intValue(), 0, Integer.MAX_VALUE, 1)); 46 47 maxConcurrentDownloads = new JSpinner(new SpinnerNumberModel(TMSCachedTileLoaderJob.THREAD_LIMIT.get().intValue(), 0, Integer.MAX_VALUE, 1)); … … 95 96 this.maxZoomLvl.setValue(TMSLayer.getMaxZoomLvl(null)); 96 97 this.minZoomLvl.setValue(TMSLayer.getMinZoomLvl(null)); 97 this.tilecacheDir.setText( TMSLayer.PROP_TILECACHE_DIR.get());98 this.tilecacheDir.setText(CachedTileLoaderFactory.PROP_TILECACHE_DIR.get()); 98 99 this.maxElementsOnDisk.setValue(TMSCachedTileLoader.MAX_OBJECTS_ON_DISK.get()); 99 100 this.maxConcurrentDownloads.setValue(TMSCachedTileLoaderJob.THREAD_LIMIT.get()); … … 132 133 } 133 134 134 if (! TMSLayer.PROP_TILECACHE_DIR.get().equals(this.tilecacheDir.getText())) {135 if (!CachedTileLoaderFactory.PROP_TILECACHE_DIR.get().equals(this.tilecacheDir.getText())) { 135 136 restartRequired = true; 136 TMSLayer.PROP_TILECACHE_DIR.put(this.tilecacheDir.getText());137 CachedTileLoaderFactory.PROP_TILECACHE_DIR.put(this.tilecacheDir.getText()); 137 138 } 138 139 -
trunk/src/org/openstreetmap/josm/gui/preferences/imagery/WMSSettingsPanel.java
r8426 r8526 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.FlowLayout;7 6 import java.awt.GridBagLayout; 8 7 … … 14 13 import javax.swing.SpinnerNumberModel; 15 14 15 import org.openstreetmap.josm.data.imagery.WMSCachedTileLoaderJob; 16 16 import org.openstreetmap.josm.gui.layer.WMSLayer; 17 import org.openstreetmap.josm.gui.widgets.JosmComboBox;18 import org.openstreetmap.josm.io.imagery.HTMLGrabber;19 17 import org.openstreetmap.josm.tools.GBC; 20 18 … … 27 25 // WMS Settings 28 26 private final JCheckBox autozoomActive; 29 private final JosmComboBox<String> browser;30 private final JCheckBox overlapCheckBox;31 private final JSpinner spinEast;32 private final JSpinner spinNorth;33 27 private final JSpinner spinSimConn; 28 private final JSpinner tileSize; 34 29 35 30 /** … … 45 40 add(autozoomActive, GBC.eol().fill(GBC.HORIZONTAL)); 46 41 47 // Downloader48 browser = new JosmComboBox<>(new String[] {49 "webkit-image {0}",50 "gnome-web-photo --mode=photo --format=png {0} /dev/stdout",51 "gnome-web-photo-fixed {0}",52 "webkit-image-gtk {0}"});53 browser.setEditable(true);54 add(new JLabel(tr("Downloader:")), GBC.std());55 add(GBC.glue(5, 0), GBC.std());56 add(browser, GBC.eol().fill(GBC.HORIZONTAL));57 58 42 // Simultaneous connections 59 43 add(Box.createHorizontalGlue(), GBC.eol().fill(GBC.HORIZONTAL)); 60 44 JLabel labelSimConn = new JLabel(tr("Simultaneous connections:")); 61 spinSimConn = new JSpinner(new SpinnerNumberModel(WMS Layer.PROP_SIMULTANEOUS_CONNECTIONS.get().intValue(), 1, 30, 1));45 spinSimConn = new JSpinner(new SpinnerNumberModel(WMSCachedTileLoaderJob.THREAD_LIMIT.get().intValue(), 1, 30, 1)); 62 46 labelSimConn.setLabelFor(spinSimConn); 63 47 add(labelSimConn, GBC.std()); … … 65 49 add(spinSimConn, GBC.eol()); 66 50 67 // Overlap 68 add(Box.createHorizontalGlue(), GBC.eol().fill(GBC.HORIZONTAL)); 69 70 overlapCheckBox = new JCheckBox(tr("Overlap tiles")); 71 JLabel labelEast = new JLabel(tr("% of east:")); 72 JLabel labelNorth = new JLabel(tr("% of north:")); 73 spinEast = new JSpinner(new SpinnerNumberModel(WMSLayer.PROP_OVERLAP_EAST.get().intValue(), 1, 50, 1)); 74 spinNorth = new JSpinner(new SpinnerNumberModel(WMSLayer.PROP_OVERLAP_NORTH.get().intValue(), 1, 50, 1)); 75 labelEast.setLabelFor(spinEast); 76 labelNorth.setLabelFor(spinNorth); 77 78 JPanel overlapPanel = new JPanel(new FlowLayout()); 79 overlapPanel.add(overlapCheckBox); 80 overlapPanel.add(labelEast); 81 overlapPanel.add(spinEast); 82 overlapPanel.add(labelNorth); 83 overlapPanel.add(spinNorth); 84 85 add(overlapPanel, GBC.eop()); 51 // Tile size 52 JLabel labelTileSize = new JLabel(tr("Tile size:")); 53 tileSize = new JSpinner(new SpinnerNumberModel(WMSLayer.PROP_IMAGE_SIZE.get().intValue(), 1, 4096, 128)); 54 labelTileSize.setLabelFor(tileSize); 55 add(labelTileSize, GBC.std()); 56 add(GBC.glue(5, 0), GBC.std()); 57 add(tileSize, GBC.eol()); 86 58 } 87 59 … … 91 63 public void loadSettings() { 92 64 this.autozoomActive.setSelected(WMSLayer.PROP_DEFAULT_AUTOZOOM.get()); 93 this.browser.setSelectedItem(HTMLGrabber.PROP_BROWSER.get()); 94 this.overlapCheckBox.setSelected(WMSLayer.PROP_OVERLAP.get()); 95 this.spinEast.setValue(WMSLayer.PROP_OVERLAP_EAST.get()); 96 this.spinNorth.setValue(WMSLayer.PROP_OVERLAP_NORTH.get()); 97 this.spinSimConn.setValue(WMSLayer.PROP_SIMULTANEOUS_CONNECTIONS.get()); 65 this.spinSimConn.setValue(WMSCachedTileLoaderJob.THREAD_LIMIT.get()); 66 this.tileSize.setValue(WMSLayer.PROP_IMAGE_SIZE.get()); 98 67 } 99 68 … … 104 73 public boolean saveSettings() { 105 74 WMSLayer.PROP_DEFAULT_AUTOZOOM.put(this.autozoomActive.isSelected()); 106 WMSLayer.PROP_OVERLAP.put(overlapCheckBox.getModel().isSelected()); 107 WMSLayer.PROP_OVERLAP_EAST.put((Integer) spinEast.getModel().getValue()); 108 WMSLayer.PROP_OVERLAP_NORTH.put((Integer) spinNorth.getModel().getValue()); 109 WMSLayer.PROP_SIMULTANEOUS_CONNECTIONS.put((Integer) spinSimConn.getModel().getValue()); 110 111 HTMLGrabber.PROP_BROWSER.put(browser.getEditor().getItem().toString()); 75 WMSCachedTileLoaderJob.THREAD_LIMIT.put((Integer) spinSimConn.getModel().getValue()); 76 WMSLayer.PROP_IMAGE_SIZE.put((Integer) this.tileSize.getModel().getValue()); 112 77 113 78 return false; -
trunk/src/org/openstreetmap/josm/io/WMSLayerExporter.java
r8510 r8526 7 7 import java.io.ObjectOutputStream; 8 8 9 import org.openstreetmap.josm.Main; 10 import org.openstreetmap.josm.data.Preferences; 11 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryPreferenceEntry; 12 import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer; 9 13 import org.openstreetmap.josm.gui.layer.Layer; 10 import org.openstreetmap.josm.gui.layer.WMSLayer;11 14 import org.openstreetmap.josm.tools.CheckParameterUtil; 12 15 … … 17 20 */ 18 21 public class WMSLayerExporter extends FileExporter { 22 23 /** Which version of the file we export */ 24 public static final int CURRENT_FILE_VERSION = 6; 19 25 20 26 /** … … 29 35 CheckParameterUtil.ensureParameterNotNull(file, "file"); 30 36 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 31 if (layer instanceof WMSLayer) { 37 38 if (layer instanceof AbstractTileSourceLayer) { 32 39 try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) { 33 ((WMSLayer) layer).writeExternal(oos); 40 oos.writeInt(CURRENT_FILE_VERSION); // file version 41 oos.writeObject(Main.map.mapView.getCenter()); 42 ImageryPreferenceEntry entry = new ImageryPreferenceEntry(((AbstractTileSourceLayer) layer).getInfo()); 43 oos.writeObject(Preferences.serializeStruct(entry, ImageryPreferenceEntry.class)); 34 44 } 35 45 } 46 36 47 } 37 48 38 49 @Override 39 50 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 40 setEnabled(newLayer instanceof WMSLayer);51 setEnabled(newLayer instanceof AbstractTileSourceLayer); 41 52 } 42 53 } -
trunk/src/org/openstreetmap/josm/io/WMSLayerImporter.java
r7033 r8526 7 7 import java.io.FileInputStream; 8 8 import java.io.IOException; 9 import java.io.InvalidClassException; 9 10 import java.io.ObjectInputStream; 11 import java.util.Map; 10 12 11 13 import org.openstreetmap.josm.Main; 12 14 import org.openstreetmap.josm.actions.ExtensionFileFilter; 13 import org.openstreetmap.josm.gui.layer.WMSLayer; 15 import org.openstreetmap.josm.data.Preferences; 16 import org.openstreetmap.josm.data.coor.EastNorth; 17 import org.openstreetmap.josm.data.imagery.ImageryInfo; 18 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryPreferenceEntry; 19 import org.openstreetmap.josm.gui.layer.ImageryLayer; 14 20 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 15 21 import org.openstreetmap.josm.gui.util.GuiHelper; … … 28 34 "wms", "wms", tr("WMS Files (*.wms)")); 29 35 30 private final WMSLayer wmsLayer;31 32 36 /** 33 37 * Constructs a new {@code WMSLayerImporter}. 34 38 */ 35 39 public WMSLayerImporter() { 36 this(new WMSLayer());40 super(FILE_FILTER); 37 41 } 38 42 39 /**40 * Constructs a new {@code WMSLayerImporter} that will import data to the specified WMS layer.41 * @param wmsLayer The WMS layer.42 */43 public WMSLayerImporter(WMSLayer wmsLayer) {44 super(FILE_FILTER);45 this.wmsLayer = wmsLayer;46 }47 43 48 44 @Override 49 45 public void importData(File file, ProgressMonitor progressMonitor) throws IOException, IllegalDataException { 50 46 CheckParameterUtil.ensureParameterNotNull(file, "file"); 47 final EastNorth zoomTo; 48 ImageryInfo info = null; 49 final ImageryLayer layer; 50 51 51 try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { 52 wmsLayer.readExternal(ois); 52 int sfv = ois.readInt(); 53 if (sfv < 5) { 54 throw new InvalidClassException(tr("Unsupported WMS file version; found {0}, expected {1}", sfv, 5)); 55 } else if (sfv == 5) { 56 ois.readInt(); // dax - not needed 57 ois.readInt(); // day - not needed 58 zoomTo = null; 59 60 int imageSize = ois.readInt(); 61 double pixelPerDegree = ois.readDouble(); 62 63 String name = (String)ois.readObject(); 64 String extendedUrl = (String)ois.readObject(); 65 66 info = new ImageryInfo(name); 67 info.setExtendedUrl(extendedUrl); 68 info.setPixelPerDegree(pixelPerDegree); 69 info.setTileSize(imageSize); 70 } else if (sfv == WMSLayerExporter.CURRENT_FILE_VERSION){ 71 zoomTo = (EastNorth) ois.readObject(); 72 73 @SuppressWarnings("unchecked") 74 ImageryPreferenceEntry entry = Preferences.deserializeStruct( 75 (Map<String, String>)ois.readObject(), 76 ImageryPreferenceEntry.class); 77 info = new ImageryInfo(entry); 78 } else { 79 throw new InvalidClassException(tr("Unsupported WMS file version; found {0}, expected {1}", sfv, 6)); 80 } 53 81 } catch (ClassNotFoundException e) { 54 82 throw new IllegalDataException(e); 55 83 } 84 layer = ImageryLayer.create(info); 85 56 86 57 87 // FIXME: remove UI stuff from IO subsystem … … 59 89 @Override 60 90 public void run() { 61 Main.main.addLayer(wmsLayer); 62 wmsLayer.onPostLoadFromFile(); 91 Main.main.addLayer(layer); 92 if (zoomTo != null) { 93 Main.map.mapView.zoomTo(zoomTo); 94 } 63 95 } 64 96 }); 65 97 } 66 67 /**68 * Replies the imported WMS layer.69 * @return The imported WMS layer.70 * @see #importData(File, ProgressMonitor)71 */72 public final WMSLayer getWmsLayer() {73 return wmsLayer;74 }75 98 } -
trunk/src/org/openstreetmap/josm/io/session/ImagerySessionExporter.java
r8510 r8526 17 17 import org.openstreetmap.josm.data.Preferences; 18 18 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryPreferenceEntry; 19 import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer; 19 20 import org.openstreetmap.josm.gui.layer.ImageryLayer; 20 21 import org.openstreetmap.josm.gui.layer.Layer; … … 81 82 ImageryPreferenceEntry e = new ImageryPreferenceEntry(layer.getInfo()); 82 83 Map<String, String> data = new LinkedHashMap<>(Preferences.serializeStruct(e, ImageryPreferenceEntry.class)); 83 if (layer instanceof WMSLayer) { 84 WMSLayer wms = (WMSLayer) layer; 85 data.put("automatic-downloading", Boolean.toString(wms.hasAutoDownload())); 86 data.put("automatically-change-resolution", Boolean.toString(wms.isAutoResolution())); 84 if (layer instanceof AbstractTileSourceLayer) { 85 AbstractTileSourceLayer tsLayer = (AbstractTileSourceLayer) layer; 86 data.put("automatic-downloading", Boolean.toString(tsLayer.autoLoad)); 87 data.put("automatically-change-resolution", Boolean.toString(tsLayer.autoZoom)); 88 data.put("show-errors", Boolean.toString(tsLayer.showErrors)); 87 89 } 88 90 for (Map.Entry<String, String> entry : data.entrySet()) { -
trunk/src/org/openstreetmap/josm/io/session/ImagerySessionImporter.java
r8510 r8526 11 11 import org.openstreetmap.josm.data.imagery.ImageryInfo; 12 12 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryPreferenceEntry; 13 import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer; 13 14 import org.openstreetmap.josm.gui.layer.ImageryLayer; 14 15 import org.openstreetmap.josm.gui.layer.Layer; 15 import org.openstreetmap.josm.gui.layer.WMSLayer;16 16 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 17 17 import org.openstreetmap.josm.io.IllegalDataException; … … 46 46 ImageryInfo i = new ImageryInfo(prefEntry); 47 47 ImageryLayer layer = ImageryLayer.create(i); 48 if (layer instanceof WMSLayer) { 49 WMSLayer wms = (WMSLayer) layer; 50 String autoDownload = attributes.get("automatic-downloading"); 51 if (autoDownload != null) { 52 wms.setAutoDownload(Boolean.parseBoolean(autoDownload)); 48 if (layer instanceof AbstractTileSourceLayer) { 49 AbstractTileSourceLayer tsLayer = (AbstractTileSourceLayer) layer; 50 if (attributes.containsKey("automatic-downloading")) { 51 tsLayer.autoLoad = new Boolean(attributes.get("automatic-downloading")).booleanValue(); 53 52 } 54 String autoResolution = attributes.get("automatically-change-resolution"); 55 if (autoResolution != null) { 56 wms.setAutoResolution(Boolean.parseBoolean(autoResolution)); 53 54 if (attributes.containsKey("automatically-change-resolution")) { 55 tsLayer.autoZoom = new Boolean(attributes.get("automatically-change-resolution")).booleanValue(); 56 } 57 58 if (attributes.containsKey("show-errors")) { 59 tsLayer.showErrors = new Boolean(attributes.get("show-errors")).booleanValue(); 57 60 } 58 61 }
Note:
See TracChangeset
for help on using the changeset viewer.