source: josm/trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java@ 5557

Last change on this file since 5557 was 5557, checked in by Don-vip, 12 years ago

fix #8147 - NPE in WMS grabber thread

  • Property svn:eol-style set to native
File size: 36.2 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.Component;
7import java.awt.Graphics;
8import java.awt.Graphics2D;
9import java.awt.Image;
10import java.awt.Point;
11import java.awt.event.ActionEvent;
12import java.awt.event.MouseAdapter;
13import java.awt.event.MouseEvent;
14import java.awt.image.BufferedImage;
15import java.awt.image.ImageObserver;
16import java.io.Externalizable;
17import java.io.File;
18import java.io.IOException;
19import java.io.InvalidClassException;
20import java.io.ObjectInput;
21import java.io.ObjectOutput;
22import java.util.ArrayList;
23import java.util.Collections;
24import java.util.HashSet;
25import java.util.Iterator;
26import java.util.List;
27import java.util.Set;
28import java.util.concurrent.locks.Condition;
29import java.util.concurrent.locks.Lock;
30import java.util.concurrent.locks.ReentrantLock;
31
32import javax.swing.AbstractAction;
33import javax.swing.Action;
34import javax.swing.JCheckBoxMenuItem;
35import javax.swing.JMenuItem;
36import javax.swing.JOptionPane;
37
38import org.openstreetmap.gui.jmapviewer.AttributionSupport;
39import org.openstreetmap.josm.Main;
40import org.openstreetmap.josm.actions.SaveActionBase;
41import org.openstreetmap.josm.data.Bounds;
42import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
43import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
44import org.openstreetmap.josm.data.ProjectionBounds;
45import org.openstreetmap.josm.data.coor.EastNorth;
46import org.openstreetmap.josm.data.coor.LatLon;
47import org.openstreetmap.josm.data.imagery.GeorefImage;
48import org.openstreetmap.josm.data.imagery.GeorefImage.State;
49import org.openstreetmap.josm.data.imagery.ImageryInfo;
50import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
51import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
52import org.openstreetmap.josm.data.imagery.WmsCache;
53import org.openstreetmap.josm.data.imagery.types.ObjectFactory;
54import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
55import org.openstreetmap.josm.data.preferences.BooleanProperty;
56import org.openstreetmap.josm.data.preferences.IntegerProperty;
57import org.openstreetmap.josm.data.projection.Projection;
58import org.openstreetmap.josm.gui.MapView;
59import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
60import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
61import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
62import org.openstreetmap.josm.gui.progress.ProgressMonitor;
63import org.openstreetmap.josm.io.WMSLayerImporter;
64import org.openstreetmap.josm.io.imagery.Grabber;
65import org.openstreetmap.josm.io.imagery.HTMLGrabber;
66import org.openstreetmap.josm.io.imagery.WMSGrabber;
67import org.openstreetmap.josm.io.imagery.WMSRequest;
68
69
70/**
71 * This is a layer that grabs the current screen from an WMS server. The data
72 * fetched this way is tiled and managed to the disc to reduce server load.
73 */
74public class WMSLayer extends ImageryLayer implements ImageObserver, PreferenceChangedListener, Externalizable {
75
76 public static class PrecacheTask {
77 private final ProgressMonitor progressMonitor;
78 private volatile int totalCount;
79 private volatile int processedCount;
80 private volatile boolean isCancelled;
81
82 public PrecacheTask(ProgressMonitor progressMonitor) {
83 this.progressMonitor = progressMonitor;
84 }
85
86 boolean isFinished() {
87 return totalCount == processedCount;
88 }
89
90 public int getTotalCount() {
91 return totalCount;
92 }
93
94 public void cancel() {
95 isCancelled = true;
96 }
97 }
98
99 private static final ObjectFactory OBJECT_FACTORY = null; // Fake reference to keep build scripts from removing ObjectFactory class. This class is not used directly but it's necessary for jaxb to work
100
101 public static final BooleanProperty PROP_ALPHA_CHANNEL = new BooleanProperty("imagery.wms.alpha_channel", true);
102 public static final IntegerProperty PROP_SIMULTANEOUS_CONNECTIONS = new IntegerProperty("imagery.wms.simultaneousConnections", 3);
103 public static final BooleanProperty PROP_OVERLAP = new BooleanProperty("imagery.wms.overlap", false);
104 public static final IntegerProperty PROP_OVERLAP_EAST = new IntegerProperty("imagery.wms.overlapEast", 14);
105 public static final IntegerProperty PROP_OVERLAP_NORTH = new IntegerProperty("imagery.wms.overlapNorth", 4);
106 public static final IntegerProperty PROP_IMAGE_SIZE = new IntegerProperty("imagery.wms.imageSize", 500);
107
108 public int messageNum = 5; //limit for messages per layer
109 protected String resolution;
110 protected int imageSize;
111 protected int dax = 10;
112 protected int day = 10;
113 protected int daStep = 5;
114 protected int minZoom = 3;
115
116 protected GeorefImage[][] images;
117 protected final int serializeFormatVersion = 5;
118 protected boolean autoDownloadEnabled = true;
119 protected boolean settingsChanged;
120 protected ImageryInfo info;
121 public WmsCache cache;
122 private AttributionSupport attribution = new AttributionSupport();
123
124 // Image index boundary for current view
125 private volatile int bminx;
126 private volatile int bminy;
127 private volatile int bmaxx;
128 private volatile int bmaxy;
129 private volatile int leftEdge;
130 private volatile int bottomEdge;
131
132 // Request queue
133 private final List<WMSRequest> requestQueue = new ArrayList<WMSRequest>();
134 private final List<WMSRequest> finishedRequests = new ArrayList<WMSRequest>();
135 /**
136 * List of request currently being processed by download threads
137 */
138 private final List<WMSRequest> processingRequests = new ArrayList<WMSRequest>();
139 private final Lock requestQueueLock = new ReentrantLock();
140 private final Condition queueEmpty = requestQueueLock.newCondition();
141 private final List<Grabber> grabbers = new ArrayList<Grabber>();
142 private final List<Thread> grabberThreads = new ArrayList<Thread>();
143 private int threadCount;
144 private int workingThreadCount;
145 private boolean canceled;
146
147 /** set to true if this layer uses an invalid base url */
148 private boolean usesInvalidUrl = false;
149 /** set to true if the user confirmed to use an potentially invalid WMS base url */
150 private boolean isInvalidUrlConfirmed = false;
151
152 public WMSLayer() {
153 this(new ImageryInfo(tr("Blank Layer")));
154 }
155
156 public WMSLayer(ImageryInfo info) {
157 super(info);
158 imageSize = PROP_IMAGE_SIZE.get();
159 setBackgroundLayer(true); /* set global background variable */
160 initializeImages();
161 this.info = new ImageryInfo(info);
162
163 attribution.initialize(this.info);
164
165 if(info.getUrl() != null) {
166 startGrabberThreads();
167 }
168
169 Main.pref.addPreferenceChangeListener(this);
170 }
171
172 @Override
173 public void hookUpMapView() {
174 if (info.getUrl() != null) {
175 for (WMSLayer layer: Main.map.mapView.getLayersOfType(WMSLayer.class)) {
176 if (layer.getInfo().getUrl().equals(info.getUrl())) {
177 cache = layer.cache;
178 break;
179 }
180 }
181 if (cache == null) {
182 cache = new WmsCache(info.getUrl(), imageSize);
183 cache.loadIndex();
184 }
185 }
186 if(this.info.getPixelPerDegree() == 0.0) {
187 this.info.setPixelPerDegree(getPPD());
188 }
189 resolution = Main.map.mapView.getDist100PixelText();
190
191 final MouseAdapter adapter = new MouseAdapter() {
192 @Override
193 public void mouseClicked(MouseEvent e) {
194 if (!isVisible()) return;
195 if (e.getButton() == MouseEvent.BUTTON1) {
196 attribution.handleAttribution(e.getPoint(), true);
197 }
198 }
199 };
200 Main.map.mapView.addMouseListener(adapter);
201
202 MapView.addLayerChangeListener(new LayerChangeListener() {
203 @Override
204 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
205 //
206 }
207
208 @Override
209 public void layerAdded(Layer newLayer) {
210 //
211 }
212
213 @Override
214 public void layerRemoved(Layer oldLayer) {
215 if (oldLayer == WMSLayer.this) {
216 Main.map.mapView.removeMouseListener(adapter);
217 MapView.removeLayerChangeListener(this);
218 }
219 }
220 });
221 }
222
223 public void doSetName(String name) {
224 setName(name);
225 info.setName(name);
226 }
227
228 public boolean hasAutoDownload(){
229 return autoDownloadEnabled;
230 }
231
232 public void downloadAreaToCache(PrecacheTask precacheTask, List<LatLon> points, double bufferX, double bufferY) {
233 Set<Point> requestedTiles = new HashSet<Point>();
234 for (LatLon point: points) {
235 EastNorth minEn = Main.getProjection().latlon2eastNorth(new LatLon(point.lat() - bufferY, point.lon() - bufferX));
236 EastNorth maxEn = Main.getProjection().latlon2eastNorth(new LatLon(point.lat() + bufferY, point.lon() + bufferX));
237 int minX = getImageXIndex(minEn.east());
238 int maxX = getImageXIndex(maxEn.east());
239 int minY = getImageYIndex(minEn.north());
240 int maxY = getImageYIndex(maxEn.north());
241
242 for (int x=minX; x<=maxX; x++) {
243 for (int y=minY; y<=maxY; y++) {
244 requestedTiles.add(new Point(x, y));
245 }
246 }
247 }
248
249 for (Point p: requestedTiles) {
250 addRequest(new WMSRequest(p.x, p.y, info.getPixelPerDegree(), true, false, precacheTask));
251 }
252
253 precacheTask.progressMonitor.setTicksCount(precacheTask.getTotalCount());
254 precacheTask.progressMonitor.setCustomText(tr("Downloaded {0}/{1} tiles", 0, precacheTask.totalCount));
255 }
256
257 @Override
258 public void destroy() {
259 super.destroy();
260 cancelGrabberThreads(false);
261 Main.pref.removePreferenceChangeListener(this);
262 if (cache != null) {
263 cache.saveIndex();
264 }
265 }
266
267 public void initializeImages() {
268 GeorefImage[][] old = images;
269 images = new GeorefImage[dax][day];
270 if (old != null) {
271 for (int i=0; i<old.length; i++) {
272 for (int k=0; k<old[i].length; k++) {
273 GeorefImage o = old[i][k];
274 images[modulo(o.getXIndex(),dax)][modulo(o.getYIndex(),day)] = old[i][k];
275 }
276 }
277 }
278 for(int x = 0; x<dax; ++x) {
279 for(int y = 0; y<day; ++y) {
280 if (images[x][y] == null) {
281 images[x][y]= new GeorefImage(this);
282 }
283 }
284 }
285 }
286
287 @Override public ImageryInfo getInfo() {
288 return info;
289 }
290
291 @Override public String getToolTipText() {
292 if(autoDownloadEnabled)
293 return tr("WMS layer ({0}), automatically downloading in zoom {1}", getName(), resolution);
294 else
295 return tr("WMS layer ({0}), downloading in zoom {1}", getName(), resolution);
296 }
297
298 private int modulo (int a, int b) {
299 return a % b >= 0 ? a%b : a%b+b;
300 }
301
302 private boolean zoomIsTooBig() {
303 //don't download when it's too outzoomed
304 return info.getPixelPerDegree() / getPPD() > minZoom;
305 }
306
307 @Override public void paint(Graphics2D g, final MapView mv, Bounds b) {
308 if(info.getUrl() == null || (usesInvalidUrl && !isInvalidUrlConfirmed)) return;
309
310 settingsChanged = false;
311
312 ProjectionBounds bounds = mv.getProjectionBounds();
313 bminx= getImageXIndex(bounds.minEast);
314 bminy= getImageYIndex(bounds.minNorth);
315 bmaxx= getImageXIndex(bounds.maxEast);
316 bmaxy= getImageYIndex(bounds.maxNorth);
317
318 leftEdge = (int)(bounds.minEast * getPPD());
319 bottomEdge = (int)(bounds.minNorth * getPPD());
320
321 if (zoomIsTooBig()) {
322 for(int x = 0; x<images.length; ++x) {
323 for(int y = 0; y<images[0].length; ++y) {
324 GeorefImage image = images[x][y];
325 image.paint(g, mv, image.getXIndex(), image.getYIndex(), leftEdge, bottomEdge);
326 }
327 }
328 } else {
329 downloadAndPaintVisible(g, mv, false);
330 }
331
332 attribution.paintAttribution(g, mv.getWidth(), mv.getHeight(), null, null, 0, this);
333
334 }
335
336 @Override
337 public void setOffset(double dx, double dy) {
338 super.setOffset(dx, dy);
339 settingsChanged = true;
340 }
341
342 public int getImageXIndex(double coord) {
343 return (int)Math.floor( ((coord - dx) * info.getPixelPerDegree()) / imageSize);
344 }
345
346 public int getImageYIndex(double coord) {
347 return (int)Math.floor( ((coord - dy) * info.getPixelPerDegree()) / imageSize);
348 }
349
350 public int getImageX(int imageIndex) {
351 return (int)(imageIndex * imageSize * (getPPD() / info.getPixelPerDegree()) + dx * getPPD());
352 }
353
354 public int getImageY(int imageIndex) {
355 return (int)(imageIndex * imageSize * (getPPD() / info.getPixelPerDegree()) + dy * getPPD());
356 }
357
358 public int getImageWidth(int xIndex) {
359 return getImageX(xIndex + 1) - getImageX(xIndex);
360 }
361
362 public int getImageHeight(int yIndex) {
363 return getImageY(yIndex + 1) - getImageY(yIndex);
364 }
365
366 /**
367 *
368 * @return Size of image in original zoom
369 */
370 public int getBaseImageWidth() {
371 int overlap = PROP_OVERLAP.get() ? (PROP_OVERLAP_EAST.get() * imageSize / 100) : 0;
372 return imageSize + overlap;
373 }
374
375 /**
376 *
377 * @return Size of image in original zoom
378 */
379 public int getBaseImageHeight() {
380 int overlap = PROP_OVERLAP.get() ? (PROP_OVERLAP_NORTH.get() * imageSize / 100) : 0;
381 return imageSize + overlap;
382 }
383
384 public int getImageSize() {
385 return imageSize;
386 }
387
388 public boolean isOverlapEnabled() {
389 return WMSLayer.PROP_OVERLAP.get() && (WMSLayer.PROP_OVERLAP_EAST.get() > 0 || WMSLayer.PROP_OVERLAP_NORTH.get() > 0);
390 }
391
392 /**
393 *
394 * @return When overlapping is enabled, return visible part of tile. Otherwise return original image
395 */
396 public BufferedImage normalizeImage(BufferedImage img) {
397 if (isOverlapEnabled()) {
398 BufferedImage copy = img;
399 img = new BufferedImage(imageSize, imageSize, copy.getType());
400 img.createGraphics().drawImage(copy, 0, 0, imageSize, imageSize,
401 0, copy.getHeight() - imageSize, imageSize, copy.getHeight(), null);
402 }
403 return img;
404 }
405
406 /**
407 *
408 * @param xIndex
409 * @param yIndex
410 * @return Real EastNorth of given tile. dx/dy is not counted in
411 */
412 public EastNorth getEastNorth(int xIndex, int yIndex) {
413 return new EastNorth((xIndex * imageSize) / info.getPixelPerDegree(), (yIndex * imageSize) / info.getPixelPerDegree());
414 }
415
416 protected void downloadAndPaintVisible(Graphics g, final MapView mv, boolean real){
417
418 int newDax = dax;
419 int newDay = day;
420
421 if (bmaxx - bminx >= dax || bmaxx - bminx < dax - 2 * daStep) {
422 newDax = ((bmaxx - bminx) / daStep + 1) * daStep;
423 }
424
425 if (bmaxy - bminy >= day || bmaxy - bminx < day - 2 * daStep) {
426 newDay = ((bmaxy - bminy) / daStep + 1) * daStep;
427 }
428
429 if (newDax != dax || newDay != day) {
430 dax = newDax;
431 day = newDay;
432 initializeImages();
433 }
434
435 for(int x = bminx; x<=bmaxx; ++x) {
436 for(int y = bminy; y<=bmaxy; ++y){
437 images[modulo(x,dax)][modulo(y,day)].changePosition(x, y);
438 }
439 }
440
441 gatherFinishedRequests();
442 Set<ProjectionBounds> areaToCache = new HashSet<ProjectionBounds>();
443
444 for(int x = bminx; x<=bmaxx; ++x) {
445 for(int y = bminy; y<=bmaxy; ++y){
446 GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
447 if (!img.paint(g, mv, x, y, leftEdge, bottomEdge)) {
448 WMSRequest request = new WMSRequest(x, y, info.getPixelPerDegree(), real, true);
449 addRequest(request);
450 areaToCache.add(new ProjectionBounds(getEastNorth(x, y), getEastNorth(x + 1, y + 1)));
451 } else if (img.getState() == State.PARTLY_IN_CACHE && autoDownloadEnabled) {
452 WMSRequest request = new WMSRequest(x, y, info.getPixelPerDegree(), real, false);
453 addRequest(request);
454 areaToCache.add(new ProjectionBounds(getEastNorth(x, y), getEastNorth(x + 1, y + 1)));
455 }
456 }
457 }
458 if (cache != null) {
459 cache.setAreaToCache(areaToCache);
460 }
461 }
462
463 @Override public void visitBoundingBox(BoundingXYVisitor v) {
464 for(int x = 0; x<dax; ++x) {
465 for(int y = 0; y<day; ++y)
466 if(images[x][y].getImage() != null){
467 v.visit(images[x][y].getMin());
468 v.visit(images[x][y].getMax());
469 }
470 }
471 }
472
473 @Override public Action[] getMenuEntries() {
474 return new Action[]{
475 LayerListDialog.getInstance().createActivateLayerAction(this),
476 LayerListDialog.getInstance().createShowHideLayerAction(),
477 LayerListDialog.getInstance().createDeleteLayerAction(),
478 SeparatorLayerAction.INSTANCE,
479 new OffsetAction(),
480 new LayerSaveAction(this),
481 new LayerSaveAsAction(this),
482 new BookmarkWmsAction(),
483 SeparatorLayerAction.INSTANCE,
484 new ZoomToNativeResolution(),
485 new StartStopAction(),
486 new ToggleAlphaAction(),
487 new ChangeResolutionAction(),
488 new ReloadErrorTilesAction(),
489 new DownloadAction(),
490 SeparatorLayerAction.INSTANCE,
491 new LayerListPopup.InfoAction(this)
492 };
493 }
494
495 public GeorefImage findImage(EastNorth eastNorth) {
496 int xIndex = getImageXIndex(eastNorth.east());
497 int yIndex = getImageYIndex(eastNorth.north());
498 GeorefImage result = images[modulo(xIndex, dax)][modulo(yIndex, day)];
499 if (result.getXIndex() == xIndex && result.getYIndex() == yIndex)
500 return result;
501 else
502 return null;
503 }
504
505 /**
506 *
507 * @param request
508 * @return -1 if request is no longer needed, otherwise priority of request (lower number <=> more important request)
509 */
510 private int getRequestPriority(WMSRequest request) {
511 if (request.getPixelPerDegree() != info.getPixelPerDegree())
512 return -1;
513 if (bminx > request.getXIndex()
514 || bmaxx < request.getXIndex()
515 || bminy > request.getYIndex()
516 || bmaxy < request.getYIndex())
517 return -1;
518
519 MouseEvent lastMEvent = Main.map.mapView.lastMEvent;
520 EastNorth cursorEastNorth = Main.map.mapView.getEastNorth(lastMEvent.getX(), lastMEvent.getY());
521 int mouseX = getImageXIndex(cursorEastNorth.east());
522 int mouseY = getImageYIndex(cursorEastNorth.north());
523 int dx = request.getXIndex() - mouseX;
524 int dy = request.getYIndex() - mouseY;
525
526 return 1 + dx * dx + dy * dy;
527 }
528
529 private void sortRequests(boolean localOnly) {
530 Iterator<WMSRequest> it = requestQueue.iterator();
531 while (it.hasNext()) {
532 WMSRequest item = it.next();
533
534 if (item.getPrecacheTask() != null && item.getPrecacheTask().isCancelled) {
535 it.remove();
536 continue;
537 }
538
539 int priority = getRequestPriority(item);
540 if (priority == -1 && item.isPrecacheOnly()) {
541 priority = Integer.MAX_VALUE; // Still download, but prefer requests in current view
542 }
543
544 if (localOnly && !item.hasExactMatch()) {
545 priority = Integer.MAX_VALUE; // Only interested in tiles that can be loaded from file immediately
546 }
547
548 if ( priority == -1
549 || finishedRequests.contains(item)
550 || processingRequests.contains(item)) {
551 it.remove();
552 } else {
553 item.setPriority(priority);
554 }
555 }
556 Collections.sort(requestQueue);
557 }
558
559 public WMSRequest getRequest(boolean localOnly) {
560 requestQueueLock.lock();
561 try {
562 workingThreadCount--;
563
564 sortRequests(localOnly);
565 while (!canceled && (requestQueue.isEmpty() || (localOnly && !requestQueue.get(0).hasExactMatch()))) {
566 try {
567 queueEmpty.await();
568 sortRequests(localOnly);
569 } catch (InterruptedException e) {
570 // Shouldn't happen
571 }
572 }
573
574 workingThreadCount++;
575 if (canceled)
576 return null;
577 else {
578 WMSRequest request = requestQueue.remove(0);
579 processingRequests.add(request);
580 return request;
581 }
582
583 } finally {
584 requestQueueLock.unlock();
585 }
586 }
587
588 public void finishRequest(WMSRequest request) {
589 requestQueueLock.lock();
590 try {
591 PrecacheTask task = request.getPrecacheTask();
592 if (task != null) {
593 task.processedCount++;
594 if (!task.progressMonitor.isCanceled()) {
595 task.progressMonitor.worked(1);
596 task.progressMonitor.setCustomText(tr("Downloaded {0}/{1} tiles", task.processedCount, task.totalCount));
597 }
598 }
599 processingRequests.remove(request);
600 if (request.getState() != null && !request.isPrecacheOnly()) {
601 finishedRequests.add(request);
602 if (Main.map != null && Main.map.mapView != null) {
603 Main.map.mapView.repaint();
604 }
605 }
606 } finally {
607 requestQueueLock.unlock();
608 }
609 }
610
611 public void addRequest(WMSRequest request) {
612 requestQueueLock.lock();
613 try {
614
615 if (cache != null) {
616 ProjectionBounds b = getBounds(request);
617 // Checking for exact match is fast enough, no need to do it in separated thread
618 request.setHasExactMatch(cache.hasExactMatch(Main.getProjection(), request.getPixelPerDegree(), b.minEast, b.minNorth));
619 if (request.isPrecacheOnly() && request.hasExactMatch())
620 return; // We already have this tile cached
621 }
622
623 if (!requestQueue.contains(request) && !finishedRequests.contains(request) && !processingRequests.contains(request)) {
624 requestQueue.add(request);
625 if (request.getPrecacheTask() != null) {
626 request.getPrecacheTask().totalCount++;
627 }
628 queueEmpty.signalAll();
629 }
630 } finally {
631 requestQueueLock.unlock();
632 }
633 }
634
635 public boolean requestIsVisible(WMSRequest request) {
636 return bminx <= request.getXIndex() && bmaxx >= request.getXIndex() && bminy <= request.getYIndex() && bmaxy >= request.getYIndex();
637 }
638
639 private void gatherFinishedRequests() {
640 requestQueueLock.lock();
641 try {
642 for (WMSRequest request: finishedRequests) {
643 GeorefImage img = images[modulo(request.getXIndex(),dax)][modulo(request.getYIndex(),day)];
644 if (img.equalPosition(request.getXIndex(), request.getYIndex())) {
645 img.changeImage(request.getState(), request.getImage());
646 }
647 }
648 } finally {
649 requestQueueLock.unlock();
650 finishedRequests.clear();
651 }
652 }
653
654 public class DownloadAction extends AbstractAction {
655 public DownloadAction() {
656 super(tr("Download visible tiles"));
657 }
658 @Override
659 public void actionPerformed(ActionEvent ev) {
660 if (zoomIsTooBig()) {
661 JOptionPane.showMessageDialog(
662 Main.parent,
663 tr("The requested area is too big. Please zoom in a little, or change resolution"),
664 tr("Error"),
665 JOptionPane.ERROR_MESSAGE
666 );
667 } else {
668 downloadAndPaintVisible(Main.map.mapView.getGraphics(), Main.map.mapView, true);
669 }
670 }
671 }
672
673 public static class ChangeResolutionAction extends AbstractAction implements LayerAction {
674 public ChangeResolutionAction() {
675 super(tr("Change resolution"));
676 }
677
678 private void changeResolution(WMSLayer layer) {
679 layer.resolution = Main.map.mapView.getDist100PixelText();
680 layer.info.setPixelPerDegree(layer.getPPD());
681 layer.settingsChanged = true;
682 for(int x = 0; x<layer.dax; ++x) {
683 for(int y = 0; y<layer.day; ++y) {
684 layer.images[x][y].changePosition(-1, -1);
685 }
686 }
687 }
688
689 @Override
690 public void actionPerformed(ActionEvent ev) {
691
692 if (LayerListDialog.getInstance() == null)
693 return;
694
695 List<Layer> layers = LayerListDialog.getInstance().getModel().getSelectedLayers();
696 for (Layer l: layers) {
697 changeResolution((WMSLayer) l);
698 }
699 Main.map.mapView.repaint();
700 }
701
702 @Override
703 public boolean supportLayers(List<Layer> layers) {
704 for (Layer l: layers) {
705 if (!(l instanceof WMSLayer))
706 return false;
707 }
708 return true;
709 }
710
711 @Override
712 public Component createMenuComponent() {
713 return new JMenuItem(this);
714 }
715
716 @Override
717 public boolean equals(Object obj) {
718 return obj instanceof ChangeResolutionAction;
719 }
720 }
721
722 public class ReloadErrorTilesAction extends AbstractAction {
723 public ReloadErrorTilesAction() {
724 super(tr("Reload erroneous tiles"));
725 }
726 @Override
727 public void actionPerformed(ActionEvent ev) {
728 // Delete small files, because they're probably blank tiles.
729 // See https://josm.openstreetmap.de/ticket/2307
730 cache.cleanSmallFiles(4096);
731
732 for (int x = 0; x < dax; ++x) {
733 for (int y = 0; y < day; ++y) {
734 GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
735 if(img.getState() == State.FAILED){
736 addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), info.getPixelPerDegree(), true, false));
737 }
738 }
739 }
740 }
741 }
742
743 public class ToggleAlphaAction extends AbstractAction implements LayerAction {
744 public ToggleAlphaAction() {
745 super(tr("Alpha channel"));
746 }
747 @Override
748 public void actionPerformed(ActionEvent ev) {
749 JCheckBoxMenuItem checkbox = (JCheckBoxMenuItem) ev.getSource();
750 boolean alphaChannel = checkbox.isSelected();
751 PROP_ALPHA_CHANNEL.put(alphaChannel);
752
753 // clear all resized cached instances and repaint the layer
754 for (int x = 0; x < dax; ++x) {
755 for (int y = 0; y < day; ++y) {
756 GeorefImage img = images[modulo(x, dax)][modulo(y, day)];
757 img.flushedResizedCachedInstance();
758 }
759 }
760 Main.map.mapView.repaint();
761 }
762 @Override
763 public Component createMenuComponent() {
764 JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);
765 item.setSelected(PROP_ALPHA_CHANNEL.get());
766 return item;
767 }
768 @Override
769 public boolean supportLayers(List<Layer> layers) {
770 return layers.size() == 1 && layers.get(0) instanceof WMSLayer;
771 }
772 }
773
774 /**
775 * This action will add a WMS layer menu entry with the current WMS layer
776 * URL and name extended by the current resolution.
777 * When using the menu entry again, the WMS cache will be used properly.
778 */
779 public class BookmarkWmsAction extends AbstractAction {
780 public BookmarkWmsAction() {
781 super(tr("Set WMS Bookmark"));
782 }
783 @Override
784 public void actionPerformed(ActionEvent ev) {
785 ImageryLayerInfo.addLayer(new ImageryInfo(info));
786 }
787 }
788
789 private class StartStopAction extends AbstractAction implements LayerAction {
790
791 public StartStopAction() {
792 super(tr("Automatic downloading"));
793 }
794
795 @Override
796 public Component createMenuComponent() {
797 JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);
798 item.setSelected(autoDownloadEnabled);
799 return item;
800 }
801
802 @Override
803 public boolean supportLayers(List<Layer> layers) {
804 return layers.size() == 1 && layers.get(0) instanceof WMSLayer;
805 }
806
807 @Override
808 public void actionPerformed(ActionEvent e) {
809 autoDownloadEnabled = !autoDownloadEnabled;
810 if (autoDownloadEnabled) {
811 for (int x = 0; x < dax; ++x) {
812 for (int y = 0; y < day; ++y) {
813 GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
814 if(img.getState() == State.NOT_IN_CACHE){
815 addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), info.getPixelPerDegree(), false, true));
816 }
817 }
818 }
819 Main.map.mapView.repaint();
820 }
821 }
822 }
823
824 private class ZoomToNativeResolution extends AbstractAction {
825
826 public ZoomToNativeResolution() {
827 super(tr("Zoom to native resolution"));
828 }
829
830 @Override
831 public void actionPerformed(ActionEvent e) {
832 Main.map.mapView.zoomTo(Main.map.mapView.getCenter(), 1 / info.getPixelPerDegree());
833 }
834
835 }
836
837 private void cancelGrabberThreads(boolean wait) {
838 requestQueueLock.lock();
839 try {
840 canceled = true;
841 for (Grabber grabber: grabbers) {
842 grabber.cancel();
843 }
844 queueEmpty.signalAll();
845 } finally {
846 requestQueueLock.unlock();
847 }
848 if (wait) {
849 for (Thread t: grabberThreads) {
850 try {
851 t.join();
852 } catch (InterruptedException e) {
853 // Shouldn't happen
854 e.printStackTrace();
855 }
856 }
857 }
858 }
859
860 private void startGrabberThreads() {
861 int threadCount = PROP_SIMULTANEOUS_CONNECTIONS.get();
862 requestQueueLock.lock();
863 try {
864 canceled = false;
865 grabbers.clear();
866 grabberThreads.clear();
867 for (int i=0; i<threadCount; i++) {
868 Grabber grabber = getGrabber(i == 0 && threadCount > 1);
869 grabbers.add(grabber);
870 Thread t = new Thread(grabber, "WMS " + getName() + " " + i);
871 t.setDaemon(true);
872 t.start();
873 grabberThreads.add(t);
874 }
875 this.workingThreadCount = grabbers.size();
876 this.threadCount = grabbers.size();
877 } finally {
878 requestQueueLock.unlock();
879 }
880 }
881
882 @Override
883 public boolean isChanged() {
884 requestQueueLock.lock();
885 try {
886 return !finishedRequests.isEmpty() || settingsChanged;
887 } finally {
888 requestQueueLock.unlock();
889 }
890 }
891
892 @Override
893 public void preferenceChanged(PreferenceChangeEvent event) {
894 if (event.getKey().equals(PROP_SIMULTANEOUS_CONNECTIONS.getKey())) {
895 cancelGrabberThreads(true);
896 startGrabberThreads();
897 } else if (
898 event.getKey().equals(PROP_OVERLAP.getKey())
899 || event.getKey().equals(PROP_OVERLAP_EAST.getKey())
900 || event.getKey().equals(PROP_OVERLAP_NORTH.getKey())) {
901 for (int i=0; i<images.length; i++) {
902 for (int k=0; k<images[i].length; k++) {
903 images[i][k] = new GeorefImage(this);
904 }
905 }
906
907 settingsChanged = true;
908 }
909 }
910
911 protected Grabber getGrabber(boolean localOnly) {
912 if (getInfo().getImageryType() == ImageryType.HTML)
913 return new HTMLGrabber(Main.map.mapView, this, localOnly);
914 else if (getInfo().getImageryType() == ImageryType.WMS)
915 return new WMSGrabber(Main.map.mapView, this, localOnly);
916 else throw new IllegalStateException("getGrabber() called for non-WMS layer type");
917 }
918
919 public ProjectionBounds getBounds(WMSRequest request) {
920 ProjectionBounds result = new ProjectionBounds(
921 getEastNorth(request.getXIndex(), request.getYIndex()),
922 getEastNorth(request.getXIndex() + 1, request.getYIndex() + 1));
923
924 if (WMSLayer.PROP_OVERLAP.get()) {
925 double eastSize = result.maxEast - result.minEast;
926 double northSize = result.maxNorth - result.minNorth;
927
928 double eastCoef = WMSLayer.PROP_OVERLAP_EAST.get() / 100.0;
929 double northCoef = WMSLayer.PROP_OVERLAP_NORTH.get() / 100.0;
930
931 result = new ProjectionBounds(result.getMin(),
932 new EastNorth(result.maxEast + eastCoef * eastSize,
933 result.maxNorth + northCoef * northSize));
934 }
935 return result;
936 }
937
938 @Override
939 public boolean isProjectionSupported(Projection proj) {
940 List<String> serverProjections = info.getServerProjections();
941 return serverProjections.contains(proj.toCode().toUpperCase())
942 || ("EPSG:3857".equals(proj.toCode()) && (serverProjections.contains("EPSG:4326") || serverProjections.contains("CRS:84")))
943 || ("EPSG:4326".equals(proj.toCode()) && serverProjections.contains("CRS:84"));
944 }
945
946 @Override
947 public String nameSupportedProjections() {
948 String res = "";
949 for(String p : info.getServerProjections()) {
950 if(!res.isEmpty()) {
951 res += ", ";
952 }
953 res += p;
954 }
955 return tr("Supported projections are: {0}", res);
956 }
957
958 @Override
959 public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
960 boolean done = ((infoflags & (ERROR | FRAMEBITS | ALLBITS)) != 0);
961 Main.map.repaint(done ? 0 : 100);
962 return !done;
963 }
964
965 @Override
966 public void writeExternal(ObjectOutput out) throws IOException {
967 out.writeInt(serializeFormatVersion);
968 out.writeInt(dax);
969 out.writeInt(day);
970 out.writeInt(imageSize);
971 out.writeDouble(info.getPixelPerDegree());
972 out.writeObject(info.getName());
973 out.writeObject(info.getExtendedUrl());
974 out.writeObject(images);
975 }
976
977 @Override
978 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
979 int sfv = in.readInt();
980 if (sfv != serializeFormatVersion)
981 throw new InvalidClassException(tr("Unsupported WMS file version; found {0}, expected {1}", sfv, serializeFormatVersion));
982 autoDownloadEnabled = false;
983 dax = in.readInt();
984 day = in.readInt();
985 imageSize = in.readInt();
986 info.setPixelPerDegree(in.readDouble());
987 doSetName((String)in.readObject());
988 info.setExtendedUrl((String)in.readObject());
989 images = (GeorefImage[][])in.readObject();
990
991 for (GeorefImage[] imgs : images) {
992 for (GeorefImage img : imgs) {
993 if (img != null) {
994 img.setLayer(WMSLayer.this);
995 }
996 }
997 }
998
999 settingsChanged = true;
1000 if (Main.isDisplayingMapView()) {
1001 Main.map.mapView.repaint();
1002 }
1003 if (cache != null) {
1004 cache.saveIndex();
1005 cache = null;
1006 }
1007 }
1008
1009 @Override
1010 public void onPostLoadFromFile() {
1011 if (info.getUrl() != null) {
1012 cache = new WmsCache(info.getUrl(), imageSize);
1013 startGrabberThreads();
1014 }
1015 }
1016
1017 @Override
1018 public boolean isSavable() {
1019 return true; // With WMSLayerExporter
1020 }
1021
1022 @Override
1023 public File createAndOpenSaveFileChooser() {
1024 return SaveActionBase.createAndOpenSaveFileChooser(tr("Save WMS file"), WMSLayerImporter.FILE_FILTER);
1025 }
1026}
Note: See TracBrowser for help on using the repository browser.