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

Last change on this file since 5017 was 5017, checked in by stoecker, 12 years ago

use EPSG codes instead of instanceof to verify projection (allows to use proj4j projections instead of internally supported projections)

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