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

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

see #8902 - Small performance enhancements / coding style (patch by shinigami):

  • while -> foreach
  • for -> for each

plus:

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