Skip to content

Instantly share code, notes, and snippets.

@fnicollet
Created June 12, 2013 09:50
Show Gist options
  • Save fnicollet/5764080 to your computer and use it in GitHub Desktop.
Save fnicollet/5764080 to your computer and use it in GitHub Desktop.
Single tile WMS layer for Leaflet. Kind of hacked on top of ImageOverlay, a new image is requested when the viewport is changed. Supports reprojection through proj4-leaflet There are actually 2 images (_image and _imageSwap) because if you use the _image from ImageOverlay, and set his "src" attribute to the new WMS bbox, your layer will disappea…
goog.provide('L.SingleTileWMSLayer');
goog.require('L.Map');
L.SingleTileWMSLayer = L.ImageOverlay.extend({
defaultWmsParams: {
service: 'WMS',
request: 'GetMap',
version: '1.1.1',
layers: '',
styles: '',
format: 'image/jpeg',
transparent: false
},
initialize: function (url, options) { // (String, Object)
this._url = url;
if (url.indexOf("{s}") != -1){
this.options.subdomains = options.subdomains = '1234';
}
var wmsParams = L.extend({}, this.defaultWmsParams);
/*
if (options.detectRetina && L.Browser.retina) {
wmsParams.width = wmsParams.height = this.options.tileSize * 2;
} else {
wmsParams.width = wmsParams.height = this.options.tileSize;
}
*/
for (var i in options) {
if (!this.options.hasOwnProperty(i)) {
wmsParams[i] = options[i];
}
}
this.wmsParams = wmsParams;
// = imageSwap et affichée now
this._isSwap = false;
this._imageSwap = null;
L.setOptions(this, options);
},
onAdd: function (map) {
this._map = map;
var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
this.wmsParams[projectionKey] = map.options.crs.code;
//
this._bounds = map.getBounds();
// pan
map.on('moveend', this._onViewReset, this);
// hide on zoom
if (map.options.zoomAnimation && L.Browser.any3d) {
map.on('zoomanim', this._onZoomAnim, this);
}
// request a first image on add
this._onViewReset();
// override
//L.ImageOverlay.prototype.onAdd.call(this, map);
},
onRemove: function (map) {
// super()
L.ImageOverlay.prototype.onRemove.call(this, map);
// add
if (this._imageSwap){
map.getPanes().overlayPane.removeChild(this._imageSwap);
}
map.off('moveend', this._onViewReset, this);
map.off('zoomanim', this._onZoomAnim, this);
},
_onViewReset: function () {
this._futureBounds = this._map.getBounds();
var map = this._map;
var crs = map.options.crs;
var nwLatLng = this._futureBounds.getNorthWest();
var seLatLng = this._futureBounds.getSouthEast();
var topLeft = this._map.latLngToLayerPoint(nwLatLng);
var bottomRight = this._map.latLngToLayerPoint(seLatLng);
var size = bottomRight.subtract(topLeft);
var nw = crs.project(nwLatLng),
se = crs.project(seLatLng);
var bbox = [nw.x, se.y, se.x, nw.y].join(',');
var url = this._url;
this.wmsParams.width = size.x;
this.wmsParams.height = size.y;
var imageSrc = url + L.Util.getParamString(this.wmsParams, url) + "&bbox=" + bbox;
this.swapImage(imageSrc, this._futureBounds);
},
_reset: function () {
var el = this._isSwap ? this._imageSwap : this._image;
if (!el){
return;
}
/** @type {L.LatLng} */
var nwLatLng = this._bounds.getNorthWest();
var seLatLng = this._bounds.getSouthEast();
var topLeft = this._map.latLngToLayerPoint(nwLatLng);
var bottomRight = this._map.latLngToLayerPoint(seLatLng);
var size = bottomRight.subtract(topLeft);
L.DomUtil.setPosition(el, topLeft);
el.width = size.x;
el.height = size.y;
},
_onZoomAnim: function(){
if (this._imageSwap){
this._imageSwap.style.visibility = 'hidden';
}
if (this._image){
this._image.style.visibility = 'hidden';
}
},
_onSwapImageLoad:function () {
if (this._isSwap){
this._imageSwap.style.visibility = 'hidden';
this._image.style.visibility = '';
} else {
this._imageSwap.style.visibility = '';
this._image.style.visibility = 'hidden';
}
this._isSwap = !this._isSwap;
this._bounds = this._futureBounds;
this._reset();
},
swapImage:function (src, bounds) {
if (!this._imagesCreated){
this._image = this._createImageSwap();
this._imageSwap = this._createImageSwap();
this._imagesCreated = true;
}
if (this._isSwap){
this._image.src = src;
} else {
this._imageSwap.src = src;
}
// do not assign the bound here, this will be done after the next image
this._futureBounds = bounds;
// allows to re-position the image while waiting for the swap.
// attention : the does not work while resizing, because of the wrong bound (size in pixel)
this._reset();
},
_createImageSwap:function () {
var el = L.DomUtil.create('img', 'leaflet-image-layer');
L.Util.extend(el, {
galleryimg: 'no',
onselectstart: L.Util.falseFn,
onmousemove: L.Util.falseFn,
onload: L.Util.bind(this._onSwapImageLoad, this)
});
this._map._panes.overlayPane.appendChild(el);
el.style.visibility = '';
return el;
}
});
@indus
Copy link

indus commented Jul 5, 2013

do you want to test my implementation in this fiddle:
http://jsfiddle.net/69bqf/8/embedded/result/
with this source:
https://github.com/indus/Leaflet/blob/master/src/layer/tile/TileLayer.WMS.incrementalSingleTile.js
discussed in this issue:
Leaflet/Leaflet#558
???

its a about a innovative technique to load singleTiles incrementally to get better performance.

@gregallensworth
Copy link

A patch for this, to support the setParams() method:

setParams: function (params, noRedraw) {
this.wmsParams = L.extend(this.wmsParams, params);
if (!noRedraw) this._onViewReset();
return this;
}

@apollolm
Copy link

Thanks for this! It doesn't seem to honor the opacity option as an L.ImageOverlay should. I'm experimenting with adding this._updateOpacity in there somewhere, not exactly sure yet. Or is there just a trick I don't know about?

Currently, I have:

  swapImage: function (src, bounds) {
    if (!this._imagesCreated) {
        this._image = this._createImageSwap();
        this._imageSwap = this._createImageSwap();
        this._imagesCreated = true; 
    }
    if (this._isSwap) {
        this._image.src = src;
    } else {
        this._imageSwap.src = src;
    }

    this._updateOpacity();

    // do not assign the bound here, this will be done after the next image
    this._futureBounds = bounds;
    // allows to re-position the image while waiting for the swap.
    // attention : the does not work while resizing, because of the wrong bound (size in pixel)
    this._reset();
  }

and it will set the opacity correctly every other time I pan.
The reason for this, I discovered, was because

   this._updateOpacity

was only applying opacity to

   this._image

So....

Here's what worked for me - I overrode the this._updateOpacity funtion to apply the opacity to both this._image and this._imageSwap:

    _updateOpacity : function (){
    L.DomUtil.setOpacity(this._image, this.options.opacity);
    L.DomUtil.setOpacity(this._imageSwap, this.options.opacity);
}

@zuoyangyang
Copy link

Thank you @fnicollet That SingleTileWMSLayer is exactly what I need and I have made some change on the "onRemove" method.I think we shoud reset "_imagesCreated "status for the next adding. Here is the code.

onRemove: function (map) {
        // super()
        L.ImageOverlay.prototype.onRemove.call(this, map);
        // add
        if (this._imageSwap){
            map.getPanes().overlayPane.removeChild(this._imageSwap);
        }
        //reset _imagesCreated status
        this._imagesCreated = false;
        map.off('moveend', this._onViewReset, this);
        map.off('zoomanim', this._onZoomAnim, this);
    }

By the way, I use

Leaflet

[email protected]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment