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

Last change on this file since 4306 was 4306, checked in by stoecker, 13 years ago

add TMS menu entry to reload error tiles

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