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

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

fix WMS projection checking, use Marcator when WMS supports it, drop old style imagery source reader

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