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

Last change on this file since 4065 was 4065, checked in by jttt, 13 years ago

Improved wms cache

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