From 01db084bbed00fa3c49f7110cc6972d24b2735f6 Mon Sep 17 00:00:00 2001 From: Villem Alango Date: Fri, 19 May 2017 15:01:53 +0300 Subject: [PATCH 1/3] implemented: WMS.Overlay events --- README.md | 32 +++++++++++++++++++++++++------- dist/leaflet.wms.js | 2 +- examples/app.js | 17 ++++++++++++++--- examples/index.html | 4 +++- src/leaflet.wms.js | 8 +++++++- 5 files changed, 50 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6ac6c16..6ea8b90 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,19 @@ var tiles = L.WMS.tileLayer("http://example.com/mapserv", { }); tiles.addTo(map); ``` - +`` ### L.WMS.Overlay This class provides a "single-tile"/untiled/non-tiled WMS layer. Every time the map is panned or zoomed, a new WMS image will be requested for the entire display. To make transitions smoother, the existing image will be kept visible until the new one is loaded. An internal [L.ImageOverlay] instance is created for this purpose. (This technique was inspired by the [esri-leaflet] plugin.) -The API is nearly identical to `L.WMS.TileLayer`, except that the tile options do not apply. +The API is nearly identical to `L.WMS.TileLayer`, except that the tile options do not apply. + +#### Events +**`wms.loading`** - fired before requesting external service; + +**`wms.load`** - fired after data from external service has been received; + +**`error`** - forwarded from internal `L.ImageOverlay` instance. ```javascript var overlay = L.WMS.overlay("http://example.com/mapserv", { @@ -102,11 +109,22 @@ overlay.addTo(map); ### L.WMS.Source -`L.WMS.Source` is a virtual Leaflet "layer" that manages multiple WMS layers coming from a single WMS source. By using the same source for multiple layers, you can have the WMS service composite the image, and avoid overloading the client with multiple large images. `L.WMS.Source` is a virtual layer, as it does not load the WMS image directly. Instead, it creates an internal `L.WMS.Overlay` or `L.WMS.TileLayer` to handle the actual loading. - -Like the other WMS layers, `L.WMS.Source` takes a URL and an options object as initialization parameters. The options are passed on to the underlying `Overlay` or `TileLayer`. An additional option, `untiled`, toggles whether to use `Overlay` or `TileLayer`. The default is `true`, which uses the non-tiled `Overlay`. Unless your WMS service is optimized for tiling, the default should provide the best performance. To use the `TileLayer`, set `untiled` to `false`. You can also set `tiled` to `true`, which will both use the `TileLayer` backend and set `tiled=true` in the WMS request (see [#16](https://github.com/heigeo/leaflet.wms/issues/16). - -`L.WMS.Source` provides two functions for toggling on and off individual WMS layers (`addSubLayer` and `removeSubLayer`, respectively). That said, it is usually more convenient to use `L.WMS.Layer` instances (described next). +`L.WMS.Source` is a virtual Leaflet "layer" that manages multiple WMS layers coming from a single WMS source. +By using the same source for multiple layers, you can have the WMS service composite the image, +and avoid overloading the client with multiple large images. +`L.WMS.Source` is a virtual layer, as it does not load the WMS image directly. +Instead, it creates an internal `L.WMS.Overlay` or `L.WMS.TileLayer` to handle the actual loading. + +Like the other WMS layers, `L.WMS.Source` takes a URL and an options object as initialization parameters. +The options are passed on to the underlying `Overlay` or `TileLayer`. +An additional option, `untiled`, toggles whether to use `Overlay` or `TileLayer`. +The default is `true`, which uses the non-tiled `Overlay`. +Unless your WMS service is optimized for tiling, the default should provide the best performance. +To use the `TileLayer`, set `untiled` to `false`. +You can also set `tiled` to `true`, which will both use the `TileLayer` backend and +set `tiled=true` in the WMS request (see [#16](https://github.com/heigeo/leaflet.wms/issues/16). + +`L.WMS.Source` forwards all `L.WMS.Overlay` events and provides two functions for toggling on and off individual WMS layers (`addSubLayer` and `removeSubLayer`, respectively). That said, it is usually more convenient to use `L.WMS.Layer` instances (described next). ```javascript var options = {'transparent': true}; diff --git a/dist/leaflet.wms.js b/dist/leaflet.wms.js index 83c680a..4d6ee17 100644 --- a/dist/leaflet.wms.js +++ b/dist/leaflet.wms.js @@ -1 +1 @@ -(function(factory){if(typeof define==="function"&&define.amd){define(["leaflet"],factory)}else if(typeof module!=="undefined"){module.exports=factory(require("leaflet"))}else{if(typeof this.L==="undefined")throw"Leaflet must be loaded first!";this.L.WMS=this.L.wms=factory(this.L)}})(function(L){var wms={};if(!("keys"in Object)){Object.keys=function(obj){var result=[];for(var i in obj){if(obj.hasOwnProperty(i)){result.push(i)}}return result}}wms.Source=L.Layer.extend({options:{untiled:true,identify:true},initialize:function(url,options){L.setOptions(this,options);if(this.options.tiled){this.options.untiled=false}this._url=url;this._subLayers={};this._overlay=this.createOverlay(this.options.untiled)},createOverlay:function(untiled){var overlayOptions={};for(var opt in this.options){if(opt!="untiled"&&opt!="identify"){overlayOptions[opt]=this.options[opt]}}if(untiled){return wms.overlay(this._url,overlayOptions)}else{return wms.tileLayer(this._url,overlayOptions)}},onAdd:function(){this.refreshOverlay()},getEvents:function(){if(this.options.identify){return{click:this.identify}}else{return{}}},setOpacity:function(opacity){this.options.opacity=opacity;if(this._overlay){this._overlay.setOpacity(opacity)}},bringToBack:function(){this.options.isBack=true;if(this._overlay){this._overlay.bringToBack()}},bringToFront:function(){this.options.isBack=false;if(this._overlay){this._overlay.bringToFront()}},getLayer:function(name){return wms.layer(this,name)},addSubLayer:function(name){this._subLayers[name]=true;this.refreshOverlay()},removeSubLayer:function(name){delete this._subLayers[name];this.refreshOverlay()},refreshOverlay:function(){var subLayers=Object.keys(this._subLayers).join(",");if(!this._map){return}if(!subLayers){this._overlay.remove()}else{this._overlay.setParams({layers:subLayers});this._overlay.addTo(this._map)}},identify:function(evt){var layers=this.getIdentifyLayers();if(!layers.length){return}this.getFeatureInfo(evt.containerPoint,evt.latlng,layers,this.showFeatureInfo)},getFeatureInfo:function(point,latlng,layers,callback){var params=this.getFeatureInfoParams(point,layers),url=this._url+L.Util.getParamString(params,this._url);this.showWaiting();this.ajax(url,done);function done(result){this.hideWaiting();var text=this.parseFeatureInfo(result,url);callback.call(this,latlng,text)}},ajax:function(url,callback){ajax.call(this,url,callback)},getIdentifyLayers:function(){if(this.options.identifyLayers)return this.options.identifyLayers;return Object.keys(this._subLayers)},getFeatureInfoParams:function(point,layers){var wmsParams,overlay;if(this.options.untiled){wmsParams=this._overlay.wmsParams}else{overlay=this.createOverlay(true);overlay.updateWmsParams(this._map);wmsParams=overlay.wmsParams;wmsParams.layers=layers.join(",")}var infoParams={request:"GetFeatureInfo",query_layers:layers.join(","),X:Math.round(point.x),Y:Math.round(point.y)};return L.extend({},wmsParams,infoParams)},parseFeatureInfo:function(result,url){if(result=="error"){result=" - +

Overlay ("Single-Tile"/Untiled Mode)

Click the map to get information about the underlying data.

+ LOADING...

Tiled Layer

@@ -36,6 +37,7 @@

Tiled Layer

+ LOADING...
diff --git a/src/leaflet.wms.js b/src/leaflet.wms.js index d676c0d..14c7d5c 100644 --- a/src/leaflet.wms.js +++ b/src/leaflet.wms.js @@ -59,6 +59,9 @@ wms.Source = L.Layer.extend({ this._url = url; this._subLayers = {}; this._overlay = this.createOverlay(this.options.untiled); + // this._overlay.on('wms.loading', L.bind(this.fire, this, 'wms.loading')); + // this._overlay.on('wms.load', L.bind(this.fire, this, 'wms.load')); + this._overlay.addEventParent(this); }, 'createOverlay': function(untiled) { @@ -94,7 +97,7 @@ wms.Source = L.Layer.extend({ this._overlay.setOpacity(opacity); } }, - + 'bringToBack': function() { this.options.isBack = true; if (this._overlay) { @@ -381,8 +384,11 @@ wms.Overlay = L.Layer.extend({ var bounds = this._map.getBounds(); var overlay = L.imageOverlay(url, bounds, {'opacity': 0}); overlay.addTo(this._map); + this.fire('wms.loading',{layer:this},true); + overlay.on('error', L.bind(this.fire, this, 'error')); overlay.once('load', _swap, this); function _swap() { + this.fire('wms.load',{layer:this},true); if (!this._map) { return; } From 44bc356d6771dfa2976a5baa2395716c94f6481e Mon Sep 17 00:00:00 2001 From: Villem Alango Date: Fri, 19 May 2017 15:34:16 +0300 Subject: [PATCH 2/3] implemented: WMS.TileLayer event forwarding --- README.md | 2 +- examples/app.js | 5 +++-- src/leaflet.wms.js | 14 +++++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6ea8b90..83de07f 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ To use the `TileLayer`, set `untiled` to `false`. You can also set `tiled` to `true`, which will both use the `TileLayer` backend and set `tiled=true` in the WMS request (see [#16](https://github.com/heigeo/leaflet.wms/issues/16). -`L.WMS.Source` forwards all `L.WMS.Overlay` events and provides two functions for toggling on and off individual WMS layers (`addSubLayer` and `removeSubLayer`, respectively). That said, it is usually more convenient to use `L.WMS.Layer` instances (described next). +`L.WMS.Source` forwards all `L.WMS.Overlay` or `L.WMS.TileLayer` events and provides two functions for toggling on and off individual WMS layers (`addSubLayer` and `removeSubLayer`, respectively). That said, it is usually more convenient to use `L.WMS.Layer` instances (described next). ```javascript var options = {'transparent': true}; diff --git a/examples/app.js b/examples/app.js index dbcb63a..a639c25 100644 --- a/examples/app.js +++ b/examples/app.js @@ -33,7 +33,8 @@ function createMap(div, tiled) { "tiled": tiled } ); - _count(0); + source.on('loading', _count.bind(null, 1)); + source.on('load', _count.bind(null, -1)); source.on('wms.loading', _count.bind(null, 1)); source.on('wms.load', _count.bind(null, -1)); var layers = { @@ -56,6 +57,7 @@ function createMap(div, tiled) { statusEl.style.visibility = "visible"; } else { statusEl.style.visibility = "hidden"; + count = 0; // Because repeated 'load' events initially. } } } @@ -84,4 +86,3 @@ return { }; }); - diff --git a/src/leaflet.wms.js b/src/leaflet.wms.js index 14c7d5c..ff03fab 100644 --- a/src/leaflet.wms.js +++ b/src/leaflet.wms.js @@ -59,9 +59,13 @@ wms.Source = L.Layer.extend({ this._url = url; this._subLayers = {}; this._overlay = this.createOverlay(this.options.untiled); - // this._overlay.on('wms.loading', L.bind(this.fire, this, 'wms.loading')); - // this._overlay.on('wms.load', L.bind(this.fire, this, 'wms.load')); - this._overlay.addEventParent(this); + + var events = this.options.untiled ? ['error','wms.load','wms.loading'] : + ['load','loading','tileerror']; + + for (var i = 0, ev; (ev=events[i]); i += 1) { + this._overlay.on(ev, L.bind(this.fire, this, ev)); + } }, 'createOverlay': function(untiled) { @@ -384,11 +388,11 @@ wms.Overlay = L.Layer.extend({ var bounds = this._map.getBounds(); var overlay = L.imageOverlay(url, bounds, {'opacity': 0}); overlay.addTo(this._map); - this.fire('wms.loading',{layer:this},true); + this.fire('wms.loading'); overlay.on('error', L.bind(this.fire, this, 'error')); overlay.once('load', _swap, this); function _swap() { - this.fire('wms.load',{layer:this},true); + this.fire('wms.load'); if (!this._map) { return; } From d8908f142384f5a4a35e11ca7301cfea6352ddaa Mon Sep 17 00:00:00 2001 From: Villem Alango Date: Fri, 19 May 2017 16:33:24 +0300 Subject: [PATCH 3/3] implemented: unified 'loading'/'load' protocol for both layer types --- README.md | 28 +++++++++++++++++++--------- examples/app.js | 2 -- src/leaflet.wms.js | 29 ++++++++++++++++++++++------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 83de07f..85d2f9d 100644 --- a/README.md +++ b/README.md @@ -85,20 +85,20 @@ var tiles = L.WMS.tileLayer("http://example.com/mapserv", { }); tiles.addTo(map); ``` -`` + ### L.WMS.Overlay This class provides a "single-tile"/untiled/non-tiled WMS layer. Every time the map is panned or zoomed, a new WMS image will be requested for the entire display. To make transitions smoother, the existing image will be kept visible until the new one is loaded. An internal [L.ImageOverlay] instance is created for this purpose. (This technique was inspired by the [esri-leaflet] plugin.) -The API is nearly identical to `L.WMS.TileLayer`, except that the tile options do not apply. - #### Events -**`wms.loading`** - fired before requesting external service; +**`loading`** - fired before requesting external service for map layer content; -**`wms.load`** - fired after data from external service has been received; +**`load`** - fired after data from external service has been received; **`error`** - forwarded from internal `L.ImageOverlay` instance. +The API is nearly identical to `L.WMS.TileLayer`, except that the tile options do not apply. + ```javascript var overlay = L.WMS.overlay("http://example.com/mapserv", { 'layers': 'layer1,layer2', @@ -124,7 +124,17 @@ To use the `TileLayer`, set `untiled` to `false`. You can also set `tiled` to `true`, which will both use the `TileLayer` backend and set `tiled=true` in the WMS request (see [#16](https://github.com/heigeo/leaflet.wms/issues/16). -`L.WMS.Source` forwards all `L.WMS.Overlay` or `L.WMS.TileLayer` events and provides two functions for toggling on and off individual WMS layers (`addSubLayer` and `removeSubLayer`, respectively). That said, it is usually more convenient to use `L.WMS.Layer` instances (described next). +#### Events +**`loading`** - fired before requesting external service for map layer content; + +**`load`** - fired after data from external service has been received; + +**`error`** - forwarded from internal `L.ImageOverlay` instance. + +**NB:** `loading` and `load` events are related to layer visual content loading, +_not_ to information queries like `showWaiting()` and `hideWaiting()` hooks described below. + +`L.WMS.Source` provides two functions for toggling on and off individual WMS layers (`addSubLayer` and `removeSubLayer`, respectively). That said, it is usually more convenient to use `L.WMS.Layer` instances (described next). ```javascript var options = {'transparent': true}; @@ -183,7 +193,7 @@ var MySource = L.WMS.Source.extend({ }); ``` -The following hooks are available: +The following `WMS.Source` hooks are available: Name | Description -----|------------- @@ -192,8 +202,8 @@ Name | Description `ajax(url, callback)` | Actual AJAX call. The default implementation is a rudimentary `XMLHttpRequest` wrapper. Override this if you want to use jQuery or something with more robust support for older browsers. If you override this, be sure to preserve the value of `this` when calling the callback function (e.g. `callback.call(this, result)`). `parseFeatureInfo(result, url)` | Parse the AJAX response into HTML `showFeatureInfo(latlng, info)` | Display parsed AJAX response to the user (e.g in a popup) -`showWaiting()` | Start AJAX wait animation (spinner, etc.) -`hideWaiting()` | Stop AJAX wait animation +`showWaiting()` | Start AJAX wait animation (spinner, etc.) during layer info request +`hideWaiting()` | Stop AJAX wait animation after layer info request is completed [Leaflet]: http://leafletjs.com [esri-leaflet]: https://github.com/Esri/esri-leaflet diff --git a/examples/app.js b/examples/app.js index a639c25..9441513 100644 --- a/examples/app.js +++ b/examples/app.js @@ -35,8 +35,6 @@ function createMap(div, tiled) { ); source.on('loading', _count.bind(null, 1)); source.on('load', _count.bind(null, -1)); - source.on('wms.loading', _count.bind(null, 1)); - source.on('wms.load', _count.bind(null, -1)); var layers = { 'Topographic': source.getLayer("TOPO-WMS").addTo(map), 'OSM Overlay': source.getLayer("OSM-Overlay-WMS").addTo(map) diff --git a/src/leaflet.wms.js b/src/leaflet.wms.js index ff03fab..2e8a871 100644 --- a/src/leaflet.wms.js +++ b/src/leaflet.wms.js @@ -58,13 +58,27 @@ wms.Source = L.Layer.extend({ } this._url = url; this._subLayers = {}; - this._overlay = this.createOverlay(this.options.untiled); + var overlay = this._overlay = this.createOverlay(this.options.untiled); - var events = this.options.untiled ? ['error','wms.load','wms.loading'] : - ['load','loading','tileerror']; + // TileLayer may emit multiple load events, we need to pass only one. + var counter = 0; + overlay.on('error', L.bind(this.fire, this, 'error')); + overlay.on('loading', L.bind(this.fire, this, 'loading')); + overlay.on('load', _onLoad, this); + overlay.on('tileloadstart', _count.bind(this, 1)); + overlay.on('tileload', _count.bind(this, -1)); + overlay.on('tileerror', _count.bind(this, -1)); + + function _count(delta) { + if ((counter += delta) <0) { + counter = 0; + } + } - for (var i = 0, ev; (ev=events[i]); i += 1) { - this._overlay.on(ev, L.bind(this.fire, this, ev)); + function _onLoad(ev) { + if (counter === 0) { + this.fire('load', ev) + } } }, @@ -388,11 +402,12 @@ wms.Overlay = L.Layer.extend({ var bounds = this._map.getBounds(); var overlay = L.imageOverlay(url, bounds, {'opacity': 0}); overlay.addTo(this._map); - this.fire('wms.loading'); + // + this.fire('loading'); overlay.on('error', L.bind(this.fire, this, 'error')); overlay.once('load', _swap, this); function _swap() { - this.fire('wms.load'); + this.fire('load'); if (!this._map) { return; }