source: josm/trunk/src/org/openstreetmap/josm/gui/layer/TMSLayer.java@ 4932

Last change on this file since 4932 was 4825, checked in by simon04, 12 years ago

see #7289 - remove JOSM specific stuff from jmapviewer

  • Property svn:eol-style set to native
File size: 49.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Font;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.awt.Image;
11import java.awt.Point;
12import java.awt.Rectangle;
13import java.awt.Toolkit;
14import java.awt.event.ActionEvent;
15import java.awt.event.MouseAdapter;
16import java.awt.event.MouseEvent;
17import java.awt.image.ImageObserver;
18import java.io.File;
19import java.io.IOException;
20import java.io.StringReader;
21import java.net.URL;
22import java.util.*;
23import java.util.Map.Entry;
24import java.util.concurrent.Callable;
25import java.util.regex.Matcher;
26import java.util.regex.Pattern;
27
28import javax.swing.AbstractAction;
29import javax.swing.Action;
30import javax.swing.JCheckBoxMenuItem;
31import javax.swing.JMenuItem;
32import javax.swing.JOptionPane;
33import javax.swing.JPopupMenu;
34import javax.swing.SwingUtilities;
35
36import org.openstreetmap.gui.jmapviewer.AttributionSupport;
37import org.openstreetmap.gui.jmapviewer.Coordinate;
38import org.openstreetmap.gui.jmapviewer.JobDispatcher;
39import org.openstreetmap.gui.jmapviewer.MemoryTileCache;
40import org.openstreetmap.gui.jmapviewer.OsmFileCacheTileLoader;
41import org.openstreetmap.gui.jmapviewer.OsmTileLoader;
42import org.openstreetmap.gui.jmapviewer.Tile;
43import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
44import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
45import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
46import org.openstreetmap.gui.jmapviewer.tilesources.BingAerialTileSource;
47import org.openstreetmap.gui.jmapviewer.tilesources.ScanexTileSource;
48import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
49import org.openstreetmap.gui.jmapviewer.tilesources.TemplatedTMSTileSource;
50import org.openstreetmap.josm.Main;
51import org.openstreetmap.josm.actions.RenameLayerAction;
52import org.openstreetmap.josm.data.Bounds;
53import org.openstreetmap.josm.data.coor.EastNorth;
54import org.openstreetmap.josm.data.coor.LatLon;
55import org.openstreetmap.josm.data.imagery.ImageryInfo;
56import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
57import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
58import org.openstreetmap.josm.data.preferences.BooleanProperty;
59import org.openstreetmap.josm.data.preferences.IntegerProperty;
60import org.openstreetmap.josm.data.preferences.StringProperty;
61import org.openstreetmap.josm.data.projection.Epsg4326;
62import org.openstreetmap.josm.data.projection.Mercator;
63import org.openstreetmap.josm.data.projection.Projection;
64import org.openstreetmap.josm.gui.MapView;
65import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
66import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
67import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
68import org.openstreetmap.josm.io.CacheCustomContent;
69import org.openstreetmap.josm.io.UTFInputStreamReader;
70import org.xml.sax.InputSource;
71
72/**
73 * Class that displays a slippy map layer.
74 *
75 * @author Frederik Ramm <frederik@remote.org>
76 * @author LuVar <lubomir.varga@freemap.sk>
77 * @author Dave Hansen <dave@sr71.net>
78 * @author Upliner <upliner@gmail.com>
79 *
80 */
81public class TMSLayer extends ImageryLayer implements ImageObserver, TileLoaderListener {
82 public static final String PREFERENCE_PREFIX = "imagery.tms";
83
84 public static final int MAX_ZOOM = 30;
85 public static final int MIN_ZOOM = 2;
86 public static final int DEFAULT_MAX_ZOOM = 20;
87 public static final int DEFAULT_MIN_ZOOM = 2;
88
89 public static final BooleanProperty PROP_DEFAULT_AUTOZOOM = new BooleanProperty(PREFERENCE_PREFIX + ".default_autozoom", true);
90 public static final BooleanProperty PROP_DEFAULT_AUTOLOAD = new BooleanProperty(PREFERENCE_PREFIX + ".default_autoload", true);
91 public static final BooleanProperty PROP_DEFAULT_SHOWERRORS = new BooleanProperty(PREFERENCE_PREFIX + ".default_showerrors", true);
92 public static final IntegerProperty PROP_MIN_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".min_zoom_lvl", DEFAULT_MIN_ZOOM);
93 public static final IntegerProperty PROP_MAX_ZOOM_LVL = new IntegerProperty(PREFERENCE_PREFIX + ".max_zoom_lvl", DEFAULT_MAX_ZOOM);
94 //public static final BooleanProperty PROP_DRAW_DEBUG = new BooleanProperty(PREFERENCE_PREFIX + ".draw_debug", false);
95 public static final BooleanProperty PROP_ADD_TO_SLIPPYMAP_CHOOSER = new BooleanProperty(PREFERENCE_PREFIX + ".add_to_slippymap_chooser", true);
96 public static final StringProperty PROP_TILECACHE_DIR;
97
98 static {
99 String defPath = null;
100 try {
101 defPath = OsmFileCacheTileLoader.getDefaultCacheDir().getAbsolutePath();
102 } catch (SecurityException e) {
103 }
104 PROP_TILECACHE_DIR = new StringProperty(PREFERENCE_PREFIX + ".tilecache_path", defPath);
105 }
106
107 /*boolean debug = true;*/
108
109 protected MemoryTileCache tileCache;
110 protected TileSource tileSource;
111 protected OsmTileLoader tileLoader;
112 JobDispatcher jobDispatcher = JobDispatcher.getInstance();
113
114 HashSet<Tile> tileRequestsOutstanding = new HashSet<Tile>();
115 @Override
116 public synchronized void tileLoadingFinished(Tile tile, boolean success)
117 {
118 if (tile.hasError()) {
119 success = false;
120 tile.setImage(null);
121 }
122 if (sharpenLevel != 0 && success) {
123 tile.setImage(sharpenImage(tile.getImage()));
124 }
125 tile.setLoaded(true);
126 needRedraw = true;
127 Main.map.repaint(100);
128 tileRequestsOutstanding.remove(tile);
129 /*if (debug) {
130 Main.debug("tileLoadingFinished() tile: " + tile + " success: " + success);
131 }*/
132 }
133
134 @Override
135 public TileCache getTileCache()
136 {
137 return tileCache;
138 }
139
140 void clearTileCache()
141 {
142 tileCache.clear();
143 if (tileLoader instanceof OsmFileCacheTileLoader) {
144 ((OsmFileCacheTileLoader)tileLoader).clearCache(tileSource);
145 }
146 }
147
148 /**
149 * Zoomlevel at which tiles is currently downloaded.
150 * Initial zoom lvl is set to bestZoom
151 */
152 public int currentZoomLevel;
153
154 private Tile clickedTile;
155 private boolean needRedraw;
156 private JPopupMenu tileOptionMenu;
157 JCheckBoxMenuItem autoZoomPopup;
158 JCheckBoxMenuItem autoLoadPopup;
159 JCheckBoxMenuItem showErrorsPopup;
160 Tile showMetadataTile;
161 private AttributionSupport attribution = new AttributionSupport();
162 private static final Font InfoFont = new Font("sansserif", Font.BOLD, 13);
163
164 protected boolean autoZoom;
165 protected boolean autoLoad;
166 protected boolean showErrors;
167
168 void redraw()
169 {
170 needRedraw = true;
171 Main.map.repaint();
172 }
173
174 static int checkMaxZoomLvl(int maxZoomLvl, TileSource ts)
175 {
176 if(maxZoomLvl > MAX_ZOOM) {
177 /*Main.debug("Max. zoom level should not be more than 30! Setting to 30.");*/
178 maxZoomLvl = MAX_ZOOM;
179 }
180 if(maxZoomLvl < PROP_MIN_ZOOM_LVL.get()) {
181 /*Main.debug("Max. zoom level should not be more than min. zoom level! Setting to min.");*/
182 maxZoomLvl = PROP_MIN_ZOOM_LVL.get();
183 }
184 if (ts != null && ts.getMaxZoom() != 0 && ts.getMaxZoom() < maxZoomLvl) {
185 maxZoomLvl = ts.getMaxZoom();
186 }
187 return maxZoomLvl;
188 }
189
190 public static int getMaxZoomLvl(TileSource ts)
191 {
192 return checkMaxZoomLvl(PROP_MAX_ZOOM_LVL.get(), ts);
193 }
194
195 public static void setMaxZoomLvl(int maxZoomLvl) {
196 maxZoomLvl = checkMaxZoomLvl(maxZoomLvl, null);
197 PROP_MAX_ZOOM_LVL.put(maxZoomLvl);
198 }
199
200 static int checkMinZoomLvl(int minZoomLvl, TileSource ts)
201 {
202 if(minZoomLvl < MIN_ZOOM) {
203 /*Main.debug("Min. zoom level should not be less than "+MIN_ZOOM+"! Setting to that.");*/
204 minZoomLvl = MIN_ZOOM;
205 }
206 if(minZoomLvl > PROP_MAX_ZOOM_LVL.get()) {
207 /*Main.debug("Min. zoom level should not be more than Max. zoom level! Setting to Max.");*/
208 minZoomLvl = getMaxZoomLvl(ts);
209 }
210 if (ts != null && ts.getMinZoom() > minZoomLvl) {
211 /*Main.debug("Increasing min. zoom level to match tile source");*/
212 minZoomLvl = ts.getMinZoom();
213 }
214 return minZoomLvl;
215 }
216
217 public static int getMinZoomLvl(TileSource ts)
218 {
219 return checkMinZoomLvl(PROP_MIN_ZOOM_LVL.get(), ts);
220 }
221
222 public static void setMinZoomLvl(int minZoomLvl) {
223 minZoomLvl = checkMinZoomLvl(minZoomLvl, null);
224 PROP_MIN_ZOOM_LVL.put(minZoomLvl);
225 }
226
227 private static class CachedAttributionBingAerialTileSource extends BingAerialTileSource {
228
229 class BingAttributionData extends CacheCustomContent<IOException> {
230
231 public BingAttributionData() {
232 super("bing.attribution.xml", CacheCustomContent.INTERVAL_WEEKLY);
233 }
234
235 @Override
236 protected byte[] updateData() throws IOException {
237 URL u = getAttributionUrl();
238 UTFInputStreamReader in = UTFInputStreamReader.create(u.openStream(), "utf-8");
239 String r = new Scanner(in).useDelimiter("\\A").next();
240 System.out.println("Successfully loaded Bing attribution data.");
241 return r.getBytes("utf-8");
242 }
243 }
244
245 @Override
246 protected Callable<List<Attribution>> getAttributionLoaderCallable() {
247 return new Callable<List<Attribution>>() {
248
249 @Override
250 public List<Attribution> call() throws Exception {
251 BingAttributionData attributionLoader = new BingAttributionData();
252 int waitTimeSec = 1;
253 while (true) {
254 try {
255 String xml = attributionLoader.updateIfRequiredString();
256 return parseAttributionText(new InputSource(new StringReader((xml))));
257 } catch (IOException ex) {
258 System.err.println("Could not connect to Bing API. Will retry in " + waitTimeSec + " seconds.");
259 Thread.sleep(waitTimeSec * 1000L);
260 waitTimeSec *= 2;
261 }
262 }
263 }
264 };
265 }
266 }
267
268 public static TileSource getTileSource(ImageryInfo info) throws IllegalArgumentException {
269 if (info.getImageryType() == ImageryType.TMS) {
270 checkUrl(info.getUrl());
271 TMSTileSource t = new TemplatedTMSTileSource(info.getName(), info.getUrl(), info.getMinZoom(), info.getMaxZoom());
272 info.setAttribution(t);
273 return t;
274 } else if (info.getImageryType() == ImageryType.BING)
275 return new CachedAttributionBingAerialTileSource();
276 else if (info.getImageryType() == ImageryType.SCANEX) {
277 return new ScanexTileSource(info.getUrl());
278 }
279 return null;
280 }
281
282 public static void checkUrl(String url) throws IllegalArgumentException {
283 if (url == null) {
284 throw new IllegalArgumentException();
285 } else {
286 Matcher m = Pattern.compile("\\{[^}]*\\}").matcher(url);
287 while (m.find()) {
288 boolean isSupportedPattern = false;
289 for (String pattern : TemplatedTMSTileSource.ALL_PATTERNS) {
290 if (m.group().matches(pattern)) {
291 isSupportedPattern = true;
292 break;
293 }
294 }
295 if (!isSupportedPattern) {
296 throw new IllegalArgumentException(tr("{0} is not a valid TMS argument. Please check this server URL:\n{1}", m.group(), url));
297 }
298 }
299 }
300 }
301
302 private void initTileSource(TileSource tileSource)
303 {
304 this.tileSource = tileSource;
305 attribution.initialize(tileSource);
306
307 currentZoomLevel = getBestZoom();
308
309 tileCache = new MemoryTileCache();
310
311 String cachePath = TMSLayer.PROP_TILECACHE_DIR.get();
312 tileLoader = null;
313 if (cachePath != null && !cachePath.isEmpty()) {
314 try {
315 tileLoader = new OsmFileCacheTileLoader(this, new File(cachePath));
316 } catch (IOException e) {
317 }
318 }
319 if (tileLoader == null) {
320 tileLoader = new OsmTileLoader(this);
321 }
322 tileLoader.timeoutConnect = Main.pref.getInteger("socket.timeout.connect",15) * 1000;
323 tileLoader.timeoutRead = Main.pref.getInteger("socket.timeout.read", 30) * 1000;
324 if (tileSource instanceof TemplatedTMSTileSource) {
325 for(Entry<String, String> e : ((TemplatedTMSTileSource)tileSource).getHeaders().entrySet()) {
326 tileLoader.headers.put(e.getKey(), e.getValue());
327 }
328 }
329 }
330
331 @Override
332 public void setOffset(double dx, double dy) {
333 super.setOffset(dx, dy);
334 needRedraw = true;
335 }
336
337 /**
338 * Returns average number of screen pixels per tile pixel for current mapview
339 */
340 private double getScaleFactor(int zoom) {
341 if (Main.map == null || Main.map.mapView == null) return 1;
342 MapView mv = Main.map.mapView;
343 LatLon topLeft = mv.getLatLon(0, 0);
344 LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());
345 double x1 = tileSource.lonToTileX(topLeft.lon(), zoom);
346 double y1 = tileSource.latToTileY(topLeft.lat(), zoom);
347 double x2 = tileSource.lonToTileX(botRight.lon(), zoom);
348 double y2 = tileSource.latToTileY(botRight.lat(), zoom);
349
350 int screenPixels = mv.getWidth()*mv.getHeight();
351 double tilePixels = Math.abs((y2-y1)*(x2-x1)*tileSource.getTileSize()*tileSource.getTileSize());
352 if (screenPixels == 0 || tilePixels == 0) return 1;
353 return screenPixels/tilePixels;
354 }
355
356 private int getBestZoom() {
357 double factor = getScaleFactor(1);
358 double result = Math.log(factor)/Math.log(2)/2+1;
359 // In general, smaller zoom levels are more readable. We prefer big,
360 // block, pixelated (but readable) map text to small, smeared,
361 // unreadable underzoomed text. So, use .floor() instead of rounding
362 // to skew things a bit toward the lower zooms.
363 int intResult = (int)Math.floor(result);
364 if (intResult > getMaxZoomLvl())
365 return getMaxZoomLvl();
366 if (intResult < getMinZoomLvl())
367 return getMinZoomLvl();
368 return intResult;
369 }
370
371 @SuppressWarnings("serial")
372 public TMSLayer(ImageryInfo info) {
373 super(info);
374
375 if(!isProjectionSupported(Main.getProjection())) {
376 JOptionPane.showMessageDialog(Main.parent,
377 tr("TMS layers do not support the projection {0}.\n{1}\n"
378 + "Change the projection or remove the layer.",
379 Main.getProjection().toCode(), nameSupportedProjections()),
380 tr("Warning"),
381 JOptionPane.WARNING_MESSAGE);
382 }
383
384 setBackgroundLayer(true);
385 this.setVisible(true);
386
387 TileSource source = getTileSource(info);
388 if (source == null)
389 throw new IllegalStateException("Cannot create TMSLayer with non-TMS ImageryInfo");
390 initTileSource(source);
391
392 tileOptionMenu = new JPopupMenu();
393
394 autoZoom = PROP_DEFAULT_AUTOZOOM.get();
395 autoZoomPopup = new JCheckBoxMenuItem();
396 autoZoomPopup.setAction(new AbstractAction(tr("Auto Zoom")) {
397 @Override
398 public void actionPerformed(ActionEvent ae) {
399 autoZoom = !autoZoom;
400 }
401 });
402 autoZoomPopup.setSelected(autoZoom);
403 tileOptionMenu.add(autoZoomPopup);
404
405 autoLoad = PROP_DEFAULT_AUTOLOAD.get();
406 autoLoadPopup = new JCheckBoxMenuItem();
407 autoLoadPopup.setAction(new AbstractAction(tr("Auto load tiles")) {
408 @Override
409 public void actionPerformed(ActionEvent ae) {
410 autoLoad= !autoLoad;
411 }
412 });
413 autoLoadPopup.setSelected(autoLoad);
414 tileOptionMenu.add(autoLoadPopup);
415
416 showErrors = PROP_DEFAULT_SHOWERRORS.get();
417 showErrorsPopup = new JCheckBoxMenuItem();
418 showErrorsPopup.setAction(new AbstractAction(tr("Show Errors")) {
419 @Override
420 public void actionPerformed(ActionEvent ae) {
421 showErrors = !showErrors;
422 }
423 });
424 showErrorsPopup.setSelected(showErrors);
425 tileOptionMenu.add(showErrorsPopup);
426
427 tileOptionMenu.add(new JMenuItem(new AbstractAction(tr("Load Tile")) {
428 @Override
429 public void actionPerformed(ActionEvent ae) {
430 if (clickedTile != null) {
431 loadTile(clickedTile, true);
432 redraw();
433 }
434 }
435 }));
436
437 tileOptionMenu.add(new JMenuItem(new AbstractAction(
438 tr("Show Tile Info")) {
439 @Override
440 public void actionPerformed(ActionEvent ae) {
441 //Main.debug("info tile: " + clickedTile);
442 if (clickedTile != null) {
443 showMetadataTile = clickedTile;
444 redraw();
445 }
446 }
447 }));
448
449 /* FIXME
450 tileOptionMenu.add(new JMenuItem(new AbstractAction(
451 tr("Request Update")) {
452 public void actionPerformed(ActionEvent ae) {
453 if (clickedTile != null) {
454 clickedTile.requestUpdate();
455 redraw();
456 }
457 }
458 }));*/
459
460 tileOptionMenu.add(new JMenuItem(new AbstractAction(
461 tr("Load All Tiles")) {
462 @Override
463 public void actionPerformed(ActionEvent ae) {
464 loadAllTiles(true);
465 redraw();
466 }
467 }));
468
469 tileOptionMenu.add(new JMenuItem(new AbstractAction(
470 tr("Load All Error Tiles")) {
471 @Override
472 public void actionPerformed(ActionEvent ae) {
473 loadAllErrorTiles(true);
474 redraw();
475 }
476 }));
477
478 // increase and decrease commands
479 tileOptionMenu.add(new JMenuItem(
480 new AbstractAction(tr("Increase zoom")) {
481 @Override
482 public void actionPerformed(ActionEvent ae) {
483 increaseZoomLevel();
484 redraw();
485 }
486 }));
487
488 tileOptionMenu.add(new JMenuItem(
489 new AbstractAction(tr("Decrease zoom")) {
490 @Override
491 public void actionPerformed(ActionEvent ae) {
492 decreaseZoomLevel();
493 redraw();
494 }
495 }));
496
497 // FIXME: currently ran in errors
498
499 tileOptionMenu.add(new JMenuItem(
500 new AbstractAction(tr("Snap to tile size")) {
501 @Override
502 public void actionPerformed(ActionEvent ae) {
503 double new_factor = Math.sqrt(getScaleFactor(currentZoomLevel));
504 Main.map.mapView.zoomToFactor(new_factor);
505 redraw();
506 }
507 }));
508 // end of adding menu commands
509
510 tileOptionMenu.add(new JMenuItem(
511 new AbstractAction(tr("Flush Tile Cache")) {
512 @Override
513 public void actionPerformed(ActionEvent ae) {
514 clearTileCache();
515 }
516 }));
517 // end of adding menu commands
518
519 SwingUtilities.invokeLater(new Runnable() {
520 @Override
521 public void run() {
522 final MouseAdapter adapter = new MouseAdapter() {
523 @Override
524 public void mouseClicked(MouseEvent e) {
525 if (!isVisible()) return;
526 if (e.getButton() == MouseEvent.BUTTON3) {
527 clickedTile = getTileForPixelpos(e.getX(), e.getY());
528 tileOptionMenu.show(e.getComponent(), e.getX(), e.getY());
529 } else if (e.getButton() == MouseEvent.BUTTON1) {
530 attribution.handleAttribution(e.getPoint(), true);
531 }
532 }
533 };
534 Main.map.mapView.addMouseListener(adapter);
535
536 MapView.addLayerChangeListener(new LayerChangeListener() {
537 @Override
538 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
539 //
540 }
541
542 @Override
543 public void layerAdded(Layer newLayer) {
544 //
545 }
546
547 @Override
548 public void layerRemoved(Layer oldLayer) {
549 if (oldLayer == TMSLayer.this) {
550 Main.map.mapView.removeMouseListener(adapter);
551 MapView.removeLayerChangeListener(this);
552 }
553 }
554 });
555 }
556 });
557 }
558
559 void zoomChanged()
560 {
561 /*if (debug) {
562 Main.debug("zoomChanged(): " + currentZoomLevel);
563 }*/
564 needRedraw = true;
565 jobDispatcher.cancelOutstandingJobs();
566 tileRequestsOutstanding.clear();
567 }
568
569 int getMaxZoomLvl()
570 {
571 if (info.getMaxZoom() != 0)
572 return checkMaxZoomLvl(info.getMaxZoom(), tileSource);
573 else
574 return getMaxZoomLvl(tileSource);
575 }
576
577 int getMinZoomLvl()
578 {
579 return getMinZoomLvl(tileSource);
580 }
581
582 /**
583 * Zoom in, go closer to map.
584 *
585 * @return true, if zoom increasing was successfull, false othervise
586 */
587 public boolean zoomIncreaseAllowed()
588 {
589 boolean zia = currentZoomLevel < this.getMaxZoomLvl();
590 /*if (debug) {
591 Main.debug("zoomIncreaseAllowed(): " + zia + " " + currentZoomLevel + " vs. " + this.getMaxZoomLvl() );
592 }*/
593 return zia;
594 }
595 public boolean increaseZoomLevel()
596 {
597 if (zoomIncreaseAllowed()) {
598 currentZoomLevel++;
599 /*if (debug) {
600 Main.debug("increasing zoom level to: " + currentZoomLevel);
601 }*/
602 zoomChanged();
603 } else {
604 Main.warn("Current zoom level ("+currentZoomLevel+") could not be increased. "+
605 "Max.zZoom Level "+this.getMaxZoomLvl()+" reached.");
606 return false;
607 }
608 return true;
609 }
610
611 public boolean setZoomLevel(int zoom)
612 {
613 if (zoom == currentZoomLevel) return true;
614 if (zoom > this.getMaxZoomLvl()) return false;
615 if (zoom < this.getMinZoomLvl()) return false;
616 currentZoomLevel = zoom;
617 zoomChanged();
618 return true;
619 }
620
621 /**
622 * Zoom out from map.
623 *
624 * @return true, if zoom increasing was successfull, false othervise
625 */
626 public boolean zoomDecreaseAllowed()
627 {
628 return currentZoomLevel > this.getMinZoomLvl();
629 }
630 public boolean decreaseZoomLevel() {
631 //int minZoom = this.getMinZoomLvl();
632 if (zoomDecreaseAllowed()) {
633 /*if (debug) {
634 Main.debug("decreasing zoom level to: " + currentZoomLevel);
635 }*/
636 currentZoomLevel--;
637 zoomChanged();
638 } else {
639 /*Main.debug("Current zoom level could not be decreased. Min. zoom level "+minZoom+" reached.");*/
640 return false;
641 }
642 return true;
643 }
644
645 /*
646 * We use these for quick, hackish calculations. They
647 * are temporary only and intentionally not inserted
648 * into the tileCache.
649 */
650 synchronized Tile tempCornerTile(Tile t) {
651 int x = t.getXtile() + 1;
652 int y = t.getYtile() + 1;
653 int zoom = t.getZoom();
654 Tile tile = getTile(x, y, zoom);
655 if (tile != null)
656 return tile;
657 return new Tile(tileSource, x, y, zoom);
658 }
659 synchronized Tile getOrCreateTile(int x, int y, int zoom) {
660 Tile tile = getTile(x, y, zoom);
661 if (tile == null) {
662 tile = new Tile(tileSource, x, y, zoom);
663 tileCache.addTile(tile);
664 tile.loadPlaceholderFromCache(tileCache);
665 }
666 return tile;
667 }
668
669 /*
670 * This can and will return null for tiles that are not
671 * already in the cache.
672 */
673 synchronized Tile getTile(int x, int y, int zoom) {
674 int max = (1 << zoom);
675 if (x < 0 || x >= max || y < 0 || y >= max)
676 return null;
677 Tile tile = tileCache.getTile(tileSource, x, y, zoom);
678 return tile;
679 }
680
681 synchronized boolean loadTile(Tile tile, boolean force)
682 {
683 if (tile == null)
684 return false;
685 if (!force && (tile.hasError() || tile.isLoaded()))
686 return false;
687 if (tile.isLoading())
688 return false;
689 if (tileRequestsOutstanding.contains(tile))
690 return false;
691 tileRequestsOutstanding.add(tile);
692 jobDispatcher.addJob(tileLoader.createTileLoaderJob(tileSource,
693 tile.getXtile(), tile.getYtile(), tile.getZoom()));
694 return true;
695 }
696
697 void loadAllTiles(boolean force) {
698 MapView mv = Main.map.mapView;
699 EastNorth topLeft = mv.getEastNorth(0, 0);
700 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());
701
702 TileSet ts = new TileSet(topLeft, botRight, currentZoomLevel);
703
704 // if there is more than 18 tiles on screen in any direction, do not
705 // load all tiles!
706 if (ts.tooLarge()) {
707 Main.warn("Not downloading all tiles because there is more than 18 tiles on an axis!");
708 return;
709 }
710 ts.loadAllTiles(force);
711 }
712
713 void loadAllErrorTiles(boolean force) {
714 MapView mv = Main.map.mapView;
715 EastNorth topLeft = mv.getEastNorth(0, 0);
716 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());
717
718 TileSet ts = new TileSet(topLeft, botRight, currentZoomLevel);
719
720 ts.loadAllErrorTiles(force);
721 }
722
723 /*
724 * Attempt to approximate how much the image is being scaled. For instance,
725 * a 100x100 image being scaled to 50x50 would return 0.25.
726 */
727 Image lastScaledImage = null;
728 @Override
729 public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
730 boolean done = ((infoflags & (ERROR | FRAMEBITS | ALLBITS)) != 0);
731 needRedraw = true;
732 /*if (debug) {
733 Main.debug("imageUpdate() done: " + done + " calling repaint");
734 }*/
735 Main.map.repaint(done ? 0 : 100);
736 return !done;
737 }
738 boolean imageLoaded(Image i) {
739 if (i == null)
740 return false;
741 int status = Toolkit.getDefaultToolkit().checkImage(i, -1, -1, this);
742 if ((status & ALLBITS) != 0)
743 return true;
744 return false;
745 }
746 Image getLoadedTileImage(Tile tile)
747 {
748 if (!tile.isLoaded())
749 return null;
750 Image img = tile.getImage();
751 if (!imageLoaded(img))
752 return null;
753 return img;
754 }
755
756 LatLon tileLatLon(Tile t)
757 {
758 int zoom = t.getZoom();
759 return new LatLon(tileSource.tileYToLat(t.getYtile(), zoom),
760 tileSource.tileXToLon(t.getXtile(), zoom));
761 }
762
763 Rectangle tileToRect(Tile t1)
764 {
765 /*
766 * We need to get a box in which to draw, so advance by one tile in
767 * each direction to find the other corner of the box.
768 * Note: this somewhat pollutes the tile cache
769 */
770 Tile t2 = tempCornerTile(t1);
771 Rectangle rect = new Rectangle(pixelPos(t1));
772 rect.add(pixelPos(t2));
773 return rect;
774 }
775
776 // 'source' is the pixel coordinates for the area that
777 // the img is capable of filling in. However, we probably
778 // only want a portion of it.
779 //
780 // 'border' is the screen cordinates that need to be drawn.
781 // We must not draw outside of it.
782 void drawImageInside(Graphics g, Image sourceImg, Rectangle source, Rectangle border)
783 {
784 Rectangle target = source;
785
786 // If a border is specified, only draw the intersection
787 // if what we have combined with what we are supposed
788 // to draw.
789 if (border != null) {
790 target = source.intersection(border);
791 /*if (debug) {
792 Main.debug("source: " + source + "\nborder: " + border + "\nintersection: " + target);
793 }*/
794 }
795
796 // All of the rectangles are in screen coordinates. We need
797 // to how these correlate to the sourceImg pixels. We could
798 // avoid doing this by scaling the image up to the 'source' size,
799 // but this should be cheaper.
800 //
801 // In some projections, x any y are scaled differently enough to
802 // cause a pixel or two of fudge. Calculate them separately.
803 double imageYScaling = sourceImg.getHeight(this) / source.getHeight();
804 double imageXScaling = sourceImg.getWidth(this) / source.getWidth();
805
806 // How many pixels into the 'source' rectangle are we drawing?
807 int screen_x_offset = target.x - source.x;
808 int screen_y_offset = target.y - source.y;
809 // And how many pixels into the image itself does that
810 // correlate to?
811 int img_x_offset = (int)(screen_x_offset * imageXScaling);
812 int img_y_offset = (int)(screen_y_offset * imageYScaling);
813 // Now calculate the other corner of the image that we need
814 // by scaling the 'target' rectangle's dimensions.
815 int img_x_end = img_x_offset + (int)(target.getWidth() * imageXScaling);
816 int img_y_end = img_y_offset + (int)(target.getHeight() * imageYScaling);
817
818 /*if (debug) {
819 Main.debug("drawing image into target rect: " + target);
820 }*/
821 g.drawImage(sourceImg,
822 target.x, target.y,
823 target.x + target.width, target.y + target.height,
824 img_x_offset, img_y_offset,
825 img_x_end, img_y_end,
826 this);
827 if (PROP_FADE_AMOUNT.get() != 0) {
828 // dimm by painting opaque rect...
829 g.setColor(getFadeColorWithAlpha());
830 g.fillRect(target.x, target.y,
831 target.width, target.height);
832 }
833 }
834 // This function is called for several zoom levels, not just
835 // the current one. It should not trigger any tiles to be
836 // downloaded. It should also avoid polluting the tile cache
837 // with any tiles since these tiles are not mandatory.
838 //
839 // The "border" tile tells us the boundaries of where we may
840 // draw. It will not be from the zoom level that is being
841 // drawn currently. If drawing the displayZoomLevel,
842 // border is null and we draw the entire tile set.
843 List<Tile> paintTileImages(Graphics g, TileSet ts, int zoom, Tile border) {
844 if (zoom <= 0) return Collections.emptyList();
845 Rectangle borderRect = null;
846 if (border != null) {
847 borderRect = tileToRect(border);
848 }
849 List<Tile> missedTiles = new LinkedList<Tile>();
850 // The callers of this code *require* that we return any tiles
851 // that we do not draw in missedTiles. ts.allExistingTiles() by
852 // default will only return already-existing tiles. However, we
853 // need to return *all* tiles to the callers, so force creation
854 // here.
855 //boolean forceTileCreation = true;
856 for (Tile tile : ts.allTilesCreate()) {
857 Image img = getLoadedTileImage(tile);
858 if (img == null || tile.hasError()) {
859 /*if (debug) {
860 Main.debug("missed tile: " + tile);
861 }*/
862 missedTiles.add(tile);
863 continue;
864 }
865 Rectangle sourceRect = tileToRect(tile);
866 if (borderRect != null && !sourceRect.intersects(borderRect)) {
867 continue;
868 }
869 drawImageInside(g, img, sourceRect, borderRect);
870 }// end of for
871 return missedTiles;
872 }
873
874 void myDrawString(Graphics g, String text, int x, int y) {
875 Color oldColor = g.getColor();
876 g.setColor(Color.black);
877 g.drawString(text,x+1,y+1);
878 g.setColor(oldColor);
879 g.drawString(text,x,y);
880 }
881
882 void paintTileText(TileSet ts, Tile tile, Graphics g, MapView mv, int zoom, Tile t) {
883 int fontHeight = g.getFontMetrics().getHeight();
884 if (tile == null)
885 return;
886 Point p = pixelPos(t);
887 int texty = p.y + 2 + fontHeight;
888
889 /*if (PROP_DRAW_DEBUG.get()) {
890 myDrawString(g, "x=" + t.getXtile() + " y=" + t.getYtile() + " z=" + zoom + "", p.x + 2, texty);
891 texty += 1 + fontHeight;
892 if ((t.getXtile() % 32 == 0) && (t.getYtile() % 32 == 0)) {
893 myDrawString(g, "x=" + t.getXtile() / 32 + " y=" + t.getYtile() / 32 + " z=7", p.x + 2, texty);
894 texty += 1 + fontHeight;
895 }
896 }*/// end of if draw debug
897
898 if (tile == showMetadataTile) {
899 String md = tile.toString();
900 if (md != null) {
901 myDrawString(g, md, p.x + 2, texty);
902 texty += 1 + fontHeight;
903 }
904 Map<String, String> meta = tile.getMetadata();
905 if (meta != null) {
906 for (Map.Entry<String, String> entry : meta.entrySet()) {
907 myDrawString(g, entry.getKey() + ": " + entry.getValue(), p.x + 2, texty);
908 texty += 1 + fontHeight;
909 }
910 }
911 }
912
913 /*String tileStatus = tile.getStatus();
914 if (!tile.isLoaded() && PROP_DRAW_DEBUG.get()) {
915 myDrawString(g, tr("image " + tileStatus), p.x + 2, texty);
916 texty += 1 + fontHeight;
917 }*/
918
919 if (tile.hasError() && showErrors) {
920 myDrawString(g, tr("Error") + ": " + tr(tile.getErrorMessage()), p.x + 2, texty);
921 texty += 1 + fontHeight;
922 }
923
924 /*int xCursor = -1;
925 int yCursor = -1;
926 if (PROP_DRAW_DEBUG.get()) {
927 if (yCursor < t.getYtile()) {
928 if (t.getYtile() % 32 == 31) {
929 g.fillRect(0, p.y - 1, mv.getWidth(), 3);
930 } else {
931 g.drawLine(0, p.y, mv.getWidth(), p.y);
932 }
933 yCursor = t.getYtile();
934 }
935 // This draws the vertical lines for the entire
936 // column. Only draw them for the top tile in
937 // the column.
938 if (xCursor < t.getXtile()) {
939 if (t.getXtile() % 32 == 0) {
940 // level 7 tile boundary
941 g.fillRect(p.x - 1, 0, 3, mv.getHeight());
942 } else {
943 g.drawLine(p.x, 0, p.x, mv.getHeight());
944 }
945 xCursor = t.getXtile();
946 }
947 }*/
948 }
949
950 private Point pixelPos(LatLon ll) {
951 return Main.map.mapView.getPoint(Main.getProjection().latlon2eastNorth(ll).add(getDx(), getDy()));
952 }
953 private Point pixelPos(Tile t) {
954 double lon = tileSource.tileXToLon(t.getXtile(), t.getZoom());
955 LatLon tmpLL = new LatLon(tileSource.tileYToLat(t.getYtile(), t.getZoom()), lon);
956 return pixelPos(tmpLL);
957 }
958 private LatLon getShiftedLatLon(EastNorth en) {
959 return Main.getProjection().eastNorth2latlon(en.add(-getDx(), -getDy()));
960 }
961 private Coordinate getShiftedCoord(EastNorth en) {
962 LatLon ll = getShiftedLatLon(en);
963 return new Coordinate(ll.lat(),ll.lon());
964 }
965
966 private final TileSet nullTileSet = new TileSet((LatLon)null, (LatLon)null, 0);
967 private class TileSet {
968 int x0, x1, y0, y1;
969 int zoom;
970 int tileMax = -1;
971
972 /**
973 * Create a TileSet by EastNorth bbox taking a layer shift in account
974 */
975 TileSet(EastNorth topLeft, EastNorth botRight, int zoom) {
976 this(getShiftedLatLon(topLeft), getShiftedLatLon(botRight),zoom);
977 }
978
979 /**
980 * Create a TileSet by known LatLon bbox without layer shift correction
981 */
982 TileSet(LatLon topLeft, LatLon botRight, int zoom) {
983 this.zoom = zoom;
984 if (zoom == 0)
985 return;
986
987 x0 = (int)tileSource.lonToTileX(topLeft.lon(), zoom);
988 y0 = (int)tileSource.latToTileY(topLeft.lat(), zoom);
989 x1 = (int)tileSource.lonToTileX(botRight.lon(), zoom);
990 y1 = (int)tileSource.latToTileY(botRight.lat(), zoom);
991 if (x0 > x1) {
992 int tmp = x0;
993 x0 = x1;
994 x1 = tmp;
995 }
996 if (y0 > y1) {
997 int tmp = y0;
998 y0 = y1;
999 y1 = tmp;
1000 }
1001 tileMax = (int)Math.pow(2.0, zoom);
1002 if (x0 < 0) {
1003 x0 = 0;
1004 }
1005 if (y0 < 0) {
1006 y0 = 0;
1007 }
1008 if (x1 > tileMax) {
1009 x1 = tileMax;
1010 }
1011 if (y1 > tileMax) {
1012 y1 = tileMax;
1013 }
1014 }
1015 boolean tooSmall() {
1016 return this.tilesSpanned() < 2.1;
1017 }
1018 boolean tooLarge() {
1019 return this.tilesSpanned() > 10;
1020 }
1021 boolean insane() {
1022 return this.tilesSpanned() > 100;
1023 }
1024 double tilesSpanned() {
1025 return Math.sqrt(1.0 * this.size());
1026 }
1027
1028 int size() {
1029 int x_span = x1 - x0 + 1;
1030 int y_span = y1 - y0 + 1;
1031 return x_span * y_span;
1032 }
1033
1034 /*
1035 * Get all tiles represented by this TileSet that are
1036 * already in the tileCache.
1037 */
1038 List<Tile> allExistingTiles()
1039 {
1040 return this.__allTiles(false);
1041 }
1042 List<Tile> allTilesCreate()
1043 {
1044 return this.__allTiles(true);
1045 }
1046 private List<Tile> __allTiles(boolean create)
1047 {
1048 // Tileset is either empty or too large
1049 if (zoom == 0 || this.insane())
1050 return Collections.emptyList();
1051 List<Tile> ret = new ArrayList<Tile>();
1052 for (int x = x0; x <= x1; x++) {
1053 for (int y = y0; y <= y1; y++) {
1054 Tile t;
1055 if (create) {
1056 t = getOrCreateTile(x % tileMax, y % tileMax, zoom);
1057 } else {
1058 t = getTile(x % tileMax, y % tileMax, zoom);
1059 }
1060 if (t != null) {
1061 ret.add(t);
1062 }
1063 }
1064 }
1065 return ret;
1066 }
1067 private List<Tile> allLoadedTiles()
1068 {
1069 List<Tile> ret = new ArrayList<Tile>();
1070 for (Tile t : this.allExistingTiles()) {
1071 if (t.isLoaded())
1072 ret.add(t);
1073 }
1074 return ret;
1075 }
1076
1077 void loadAllTiles(boolean force)
1078 {
1079 if (!autoLoad && !force)
1080 return;
1081 for (Tile t : this.allTilesCreate()) {
1082 loadTile(t, false);
1083 }
1084 }
1085
1086 void loadAllErrorTiles(boolean force)
1087 {
1088 if (!autoLoad && !force)
1089 return;
1090 for (Tile t : this.allTilesCreate()) {
1091 if (t.hasError()) {
1092 loadTile(t, true);
1093 }
1094 }
1095 }
1096 }
1097
1098
1099 private static class TileSetInfo {
1100 public boolean hasVisibleTiles = false;
1101 public boolean hasOverzoomedTiles = false;
1102 public boolean hasLoadingTiles = false;
1103 }
1104
1105 private static TileSetInfo getTileSetInfo(TileSet ts) {
1106 List<Tile> allTiles = ts.allExistingTiles();
1107 TileSetInfo result = new TileSetInfo();
1108 result.hasLoadingTiles = allTiles.size() < ts.size();
1109 for (Tile t : allTiles) {
1110 if (t.isLoaded()) {
1111 if (!t.hasError()) {
1112 result.hasVisibleTiles = true;
1113 }
1114 if ("no-tile".equals(t.getValue("tile-info"))) {
1115 result.hasOverzoomedTiles = true;
1116 }
1117 } else {
1118 result.hasLoadingTiles = true;
1119 }
1120 }
1121 return result;
1122 }
1123
1124 private class DeepTileSet {
1125 final EastNorth topLeft, botRight;
1126 final int minZoom, maxZoom;
1127 private final TileSet[] tileSets;
1128 private final TileSetInfo[] tileSetInfos;
1129 public DeepTileSet(EastNorth topLeft, EastNorth botRight, int minZoom, int maxZoom) {
1130 this.topLeft = topLeft;
1131 this.botRight = botRight;
1132 this.minZoom = minZoom;
1133 this.maxZoom = maxZoom;
1134 this.tileSets = new TileSet[maxZoom - minZoom + 1];
1135 this.tileSetInfos = new TileSetInfo[maxZoom - minZoom + 1];
1136 }
1137 public TileSet getTileSet(int zoom) {
1138 if (zoom < minZoom)
1139 return nullTileSet;
1140 TileSet ts = tileSets[zoom-minZoom];
1141 if (ts == null) {
1142 ts = new TileSet(topLeft, botRight, zoom);
1143 tileSets[zoom-minZoom] = ts;
1144 }
1145 return ts;
1146 }
1147 public TileSetInfo getTileSetInfo(int zoom) {
1148 if (zoom < minZoom)
1149 return new TileSetInfo();
1150 TileSetInfo tsi = tileSetInfos[zoom-minZoom];
1151 if (tsi == null) {
1152 tsi = TMSLayer.getTileSetInfo(getTileSet(zoom));
1153 tileSetInfos[zoom-minZoom] = tsi;
1154 }
1155 return tsi;
1156 }
1157 }
1158
1159 /**
1160 */
1161 @Override
1162 public void paint(Graphics2D g, MapView mv, Bounds bounds) {
1163 //long start = System.currentTimeMillis();
1164 EastNorth topLeft = mv.getEastNorth(0, 0);
1165 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());
1166
1167 if (botRight.east() == 0.0 || botRight.north() == 0) {
1168 /*Main.debug("still initializing??");*/
1169 // probably still initializing
1170 return;
1171 }
1172
1173 needRedraw = false;
1174
1175 int zoom = currentZoomLevel;
1176 if (autoZoom) {
1177 double pixelScaling = getScaleFactor(zoom);
1178 if (pixelScaling > 3 || pixelScaling < 0.7) {
1179 zoom = getBestZoom();
1180 }
1181 }
1182
1183 DeepTileSet dts = new DeepTileSet(topLeft, botRight, getMinZoomLvl(), zoom);
1184 TileSet ts = dts.getTileSet(zoom);
1185
1186 int displayZoomLevel = zoom;
1187
1188 boolean noTilesAtZoom = false;
1189 if (autoZoom && autoLoad) {
1190 // Auto-detection of tilesource maxzoom (currently fully works only for Bing)
1191 TileSetInfo tsi = dts.getTileSetInfo(zoom);
1192 if (!tsi.hasVisibleTiles && (!tsi.hasLoadingTiles || tsi.hasOverzoomedTiles)) {
1193 noTilesAtZoom = true;
1194 }
1195 // Find highest zoom level with at least one visible tile
1196 for (int tmpZoom = zoom; tmpZoom > dts.minZoom; tmpZoom--) {
1197 if (dts.getTileSetInfo(tmpZoom).hasVisibleTiles) {
1198 displayZoomLevel = tmpZoom;
1199 break;
1200 }
1201 }
1202 // Do binary search between currentZoomLevel and displayZoomLevel
1203 while (zoom > displayZoomLevel && !tsi.hasVisibleTiles && tsi.hasOverzoomedTiles){
1204 zoom = (zoom + displayZoomLevel)/2;
1205 tsi = dts.getTileSetInfo(zoom);
1206 }
1207
1208 setZoomLevel(zoom);
1209
1210 // If all tiles at displayZoomLevel is loaded, load all tiles at next zoom level
1211 // to make sure there're really no more zoom levels
1212 if (zoom == displayZoomLevel && !tsi.hasLoadingTiles && zoom < dts.maxZoom) {
1213 zoom++;
1214 tsi = dts.getTileSetInfo(zoom);
1215 }
1216 // When we have overzoomed tiles and all tiles at current zoomlevel is loaded,
1217 // load tiles at previovus zoomlevels until we have all tiles on screen is loaded.
1218 while (zoom > dts.minZoom && tsi.hasOverzoomedTiles && !tsi.hasLoadingTiles) {
1219 zoom--;
1220 tsi = dts.getTileSetInfo(zoom);
1221 }
1222 ts = dts.getTileSet(zoom);
1223 } else if (autoZoom) {
1224 setZoomLevel(zoom);
1225 }
1226
1227 // Too many tiles... refuse to download
1228 if (!ts.tooLarge()) {
1229 //Main.debug("size: " + ts.size() + " spanned: " + ts.tilesSpanned());
1230 ts.loadAllTiles(false);
1231 }
1232
1233 if (displayZoomLevel != zoom) {
1234 ts = dts.getTileSet(displayZoomLevel);
1235 }
1236
1237 g.setColor(Color.DARK_GRAY);
1238
1239 List<Tile> missedTiles = this.paintTileImages(g, ts, displayZoomLevel, null);
1240 int otherZooms[] = { -1, 1, -2, 2, -3, -4, -5};
1241 for (int zoomOffset : otherZooms) {
1242 if (!autoZoom) {
1243 break;
1244 }
1245 int newzoom = displayZoomLevel + zoomOffset;
1246 if (newzoom < MIN_ZOOM) {
1247 continue;
1248 }
1249 if (missedTiles.size() <= 0) {
1250 break;
1251 }
1252 List<Tile> newlyMissedTiles = new LinkedList<Tile>();
1253 for (Tile missed : missedTiles) {
1254 if ("no-tile".equals(missed.getValue("tile-info")) && zoomOffset > 0) {
1255 // Don't try to paint from higher zoom levels when tile is overzoomed
1256 newlyMissedTiles.add(missed);
1257 continue;
1258 }
1259 Tile t2 = tempCornerTile(missed);
1260 LatLon topLeft2 = tileLatLon(missed);
1261 LatLon botRight2 = tileLatLon(t2);
1262 TileSet ts2 = new TileSet(topLeft2, botRight2, newzoom);
1263 // Instantiating large TileSets is expensive. If there
1264 // are no loaded tiles, don't bother even trying.
1265 if (ts2.allLoadedTiles().size() == 0) {
1266 newlyMissedTiles.add(missed);
1267 continue;
1268 }
1269 if (ts2.tooLarge()) {
1270 continue;
1271 }
1272 newlyMissedTiles.addAll(this.paintTileImages(g, ts2, newzoom, missed));
1273 }
1274 missedTiles = newlyMissedTiles;
1275 }
1276 /*if (debug && missedTiles.size() > 0) {
1277 Main.debug("still missed "+missedTiles.size()+" in the end");
1278 }*/
1279 g.setColor(Color.red);
1280 g.setFont(InfoFont);
1281
1282 // The current zoom tileset should have all of its tiles
1283 // due to the loadAllTiles(), unless it to tooLarge()
1284 for (Tile t : ts.allExistingTiles()) {
1285 this.paintTileText(ts, t, g, mv, displayZoomLevel, t);
1286 }
1287
1288 attribution.paintAttribution(g, mv.getWidth(), mv.getHeight(), getShiftedCoord(topLeft), getShiftedCoord(botRight), displayZoomLevel, this);
1289
1290 //g.drawString("currentZoomLevel=" + currentZoomLevel, 120, 120);
1291 g.setColor(Color.lightGray);
1292 if (!autoZoom) {
1293 if (ts.insane()) {
1294 myDrawString(g, tr("zoom in to load any tiles"), 120, 120);
1295 } else if (ts.tooLarge()) {
1296 myDrawString(g, tr("zoom in to load more tiles"), 120, 120);
1297 } else if (ts.tooSmall()) {
1298 myDrawString(g, tr("increase zoom level to see more detail"), 120, 120);
1299 }
1300 }
1301 if (noTilesAtZoom) {
1302 myDrawString(g, tr("No tiles at this zoom level"), 120, 120);
1303 }
1304 /*if (debug) {
1305 myDrawString(g, tr("Current zoom: {0}", currentZoomLevel), 50, 140);
1306 myDrawString(g, tr("Display zoom: {0}", displayZoomLevel), 50, 155);
1307 myDrawString(g, tr("Pixel scale: {0}", getScaleFactor(currentZoomLevel)), 50, 170);
1308 myDrawString(g, tr("Best zoom: {0}", Math.log(getScaleFactor(1))/Math.log(2)/2+1), 50, 185);
1309 }*/
1310 }
1311
1312 /**
1313 * This isn't very efficient, but it is only used when the
1314 * user right-clicks on the map.
1315 */
1316 Tile getTileForPixelpos(int px, int py) {
1317 /*if (debug) {
1318 Main.debug("getTileForPixelpos("+px+", "+py+")");
1319 }*/
1320 MapView mv = Main.map.mapView;
1321 Point clicked = new Point(px, py);
1322 EastNorth topLeft = mv.getEastNorth(0, 0);
1323 EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());
1324 int z = currentZoomLevel;
1325 TileSet ts = new TileSet(topLeft, botRight, z);
1326
1327 if (!ts.tooLarge()) {
1328 ts.loadAllTiles(false); // make sure there are tile objects for all tiles
1329 }
1330 Tile clickedTile = null;
1331 for (Tile t1 : ts.allExistingTiles()) {
1332 Tile t2 = tempCornerTile(t1);
1333 Rectangle r = new Rectangle(pixelPos(t1));
1334 r.add(pixelPos(t2));
1335 /*if (debug) {
1336 Main.debug("r: " + r + " clicked: " + clicked);
1337 }*/
1338 if (!r.contains(clicked)) {
1339 continue;
1340 }
1341 clickedTile = t1;
1342 break;
1343 }
1344 if (clickedTile == null)
1345 return null;
1346 /*Main.debug("Clicked on tile: " + clickedTile.getXtile() + " " + clickedTile.getYtile() +
1347 " currentZoomLevel: " + currentZoomLevel);*/
1348 return clickedTile;
1349 }
1350
1351 @Override
1352 public Action[] getMenuEntries() {
1353 return new Action[] {
1354 LayerListDialog.getInstance().createShowHideLayerAction(),
1355 LayerListDialog.getInstance().createDeleteLayerAction(),
1356 SeparatorLayerAction.INSTANCE,
1357 // color,
1358 new OffsetAction(),
1359 new RenameLayerAction(this.getAssociatedFile(), this),
1360 SeparatorLayerAction.INSTANCE,
1361 new LayerListPopup.InfoAction(this) };
1362 }
1363
1364 @Override
1365 public String getToolTipText() {
1366 return null;
1367 }
1368
1369 @Override
1370 public void visitBoundingBox(BoundingXYVisitor v) {
1371 }
1372
1373 @Override
1374 public boolean isChanged() {
1375 return needRedraw;
1376 }
1377
1378 @Override
1379 public boolean isProjectionSupported(Projection proj) {
1380 return proj instanceof Mercator || proj instanceof Epsg4326;
1381 }
1382
1383 @Override
1384 public String nameSupportedProjections() {
1385 return tr("EPSG:4326 and Mercator projection are supported");
1386 }
1387}
Note: See TracBrowser for help on using the repository browser.