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

Last change on this file since 4330 was 4330, checked in by hansendc, 13 years ago

renane TileSet.allTiles()

The previous bug with paintAllImages() shows that allTiles() is
badly named. It really has two incarnations: one that creates
tiles and one that does not. Make this clear in the function
names, and do not epose allTiles(bool) any longer.

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