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

Last change on this file since 4489 was 4489, checked in by bastiK, 13 years ago

unified redundant attribution code from org.openstreetmap.gui.jmapviewer.JMapViewer and org.openstreetmap.josm.gui.layer.TMSLayer

wire the i18n support for the jmapviewer component (see [o26783])

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