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