forked from heigeo/leaflet.wms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathleaflet.wms.js
430 lines (378 loc) · 12.1 KB
/
leaflet.wms.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
/*!
* leaflet.wms.js
* A collection of Leaflet utilities for working with Web Mapping services.
* (c) 2014, Houston Engineering, Inc.
* MIT License
*/
(function (factory) {
// Module systems magic dance, Leaflet edition
if (typeof define === 'function' && define.amd) {
// AMD
define(['leaflet'], factory);
} else if (typeof module !== 'undefined') {
// Node/CommonJS
module.exports = factory(require('leaflet'));
} else {
// Browser globals
if (typeof this.L === 'undefined')
throw 'Leaflet must be loaded first!';
// Namespace
this.L.WMS = this.L.wms = factory(this.L);
}
}(function (L) {
// Module object
var wms = {};
/*
* wms.Source
* The Source object manages a single WMS connection. Multiple "layers" can be
* created with the getLayer function, but a single request will be sent for
* each image update. Can be used in non-tiled "overlay" mode (default), or
* tiled mode, via an internal wms.Overlay or wms.TileLayer, respectively.
*/
wms.Source = L.Layer.extend({
'options': {
'tiled': false,
'identify': true
},
'initialize': function(url, options) {
L.setOptions(this, options);
this._url = url;
this._subLayers = {};
this._overlay = this.createOverlay(this.options.tiled);
},
'createOverlay': function(tiled) {
// Create overlay with all options other than tiled & identify
var overlayOptions = {};
for (var opt in this.options) {
if (opt != 'tiled' && opt != 'identify') {
overlayOptions[opt] = this.options[opt];
}
}
if (tiled) {
return wms.tileLayer(this._url, overlayOptions);
} else {
return wms.overlay(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);
}
},
'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) {
// Identify map features in response to map clicks. To customize this
// behavior, create a class extending wms.Source and override one or
// more of the following hook functions.
var layers = this.getIdentifyLayers();
if (!layers.length) {
return;
}
this.getFeatureInfo(
evt.containerPoint, evt.latlng, layers,
this.showFeatureInfo
);
},
'getFeatureInfo': function(point, latlng, layers, callback) {
// Request WMS GetFeatureInfo and call callback with results
// (split from identify() to faciliate use outside of map events)
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() {
// Hook to determine which layers to identify
if (this.options.identifyLayers)
return this.options.identifyLayers;
return Object.keys(this._subLayers);
},
'getFeatureInfoParams': function(point, layers) {
// Hook to generate parameters for WMS service GetFeatureInfo request
var wmsParams, overlay;
if (this.options.tiled) {
// Create overlay instance to leverage updateWmsParams
overlay = this.createOverlay();
overlay.updateWmsParams(this._map);
wmsParams = overlay.wmsParams;
wmsParams.layers = layers.join(',');
} else {
// Use existing overlay
wmsParams = this._overlay.wmsParams;
}
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) {
// Hook to handle parsing AJAX response
if (result == "error") {
// AJAX failed, possibly due to CORS issues.
// Try loading content in <iframe>.
result = "<iframe src='" + url + "' style='border:none'>";
}
return result;
},
'showFeatureInfo': function(latlng, info) {
// Hook to handle displaying parsed AJAX response to the user
if (!this._map) {
return;
}
this._map.openPopup(info, latlng);
},
'showWaiting': function() {
// Hook to customize AJAX wait animation
if (!this._map)
return;
this._map._container.style.cursor = "progress";
},
'hideWaiting': function() {
// Hook to remove AJAX wait animation
if (!this._map)
return;
this._map._container.style.cursor = "default";
}
});
wms.source = function(url, options) {
return new wms.Source(url, options);
};
/*
* Layer
* Leaflet "layer" with all actual rendering handled via an underlying Source
* object. Can be called directly with a URL to automatically create or reuse
* an existing Source. Note that the auto-source feature doesn't work well in
* multi-map environments; so for best results, create a Source first and use
* getLayer() to retrieve wms.Layer instances.
*/
wms.Layer = L.Layer.extend({
'initialize': function(source, layerName, options) {
if (!source.addSubLayer) {
// Assume source is a URL
source = wms.getSourceForUrl(source, options);
}
this._source = source;
this._name = layerName;
},
'onAdd': function() {
if (!this._source._map)
this._source.addTo(this._map);
this._source.addSubLayer(this._name);
},
'onRemove': function() {
this._source.removeSubLayer(this._name);
},
'setOpacity': function(opacity) {
this._source.setOpacity(opacity);
}
});
wms.layer = function(source, options) {
return new wms.Layer(source, options);
};
// Cache of sources for use with wms.Layer auto-source option
var sources = {};
wms.getSourceForUrl = function(url, options) {
if (!sources[url]) {
sources[url] = wms.source(url, options);
}
return sources[url];
};
// Copy tiled WMS layer from leaflet core, in case we need to subclass it later
wms.TileLayer = L.TileLayer.WMS;
wms.tileLayer = L.tileLayer.wms;
/*
* wms.Overlay:
* "Single Tile" WMS image overlay that updates with map changes.
* Portions of wms.Overlay are directly extracted from L.TileLayer.WMS.
* See Leaflet license.
*/
wms.Overlay = L.Layer.extend({
'defaultWmsParams': {
'service': 'WMS',
'request': 'GetMap',
'version': '1.1.1',
'layers': '',
'styles': '',
'format': 'image/jpeg',
'transparent': false
},
'options': {
'crs': null,
'uppercase': false,
'attribution': '',
'opacity': 1
},
'initialize': function(url, options) {
this._url = url;
// Move WMS parameters to params object
var params = {};
for (var opt in options) {
if (!(opt in this.options)) {
params[opt] = options[opt];
delete options[opt];
}
}
L.setOptions(this, options);
this.wmsParams = L.extend({}, this.defaultWmsParams, params);
},
'setParams': function(params) {
L.extend(this.wmsParams, params);
this.update();
},
'getAttribution': function() {
return this.options.attribution;
},
'onAdd': function() {
this.update();
},
'onRemove': function(map) {
if (this._currentOverlay) {
map.removeLayer(this._currentOverlay);
delete this._currentOverlay;
}
if (this._currentUrl) {
delete this._currentUrl;
}
},
'getEvents': function() {
return {
'moveend': this.update
};
},
'update': function() {
if (!this._map) {
return;
}
// Determine image URL and whether it has changed since last update
this.updateWmsParams();
var url = this.getImageUrl();
if (this._currentUrl == url) {
return;
}
this._currentUrl = url;
// Keep current image overlay in place until new one loads
// (inspired by esri.leaflet)
var bounds = this._map.getBounds();
var overlay = L.imageOverlay(url, bounds, {'opacity': 0});
overlay.addTo(this._map);
overlay.once('load', _swap, this);
function _swap() {
if (!this._map) {
return;
}
if (overlay._url != this._currentUrl) {
this._map.removeLayer(overlay);
return;
} else if (this._currentOverlay) {
this._map.removeLayer(this._currentOverlay);
}
this._currentOverlay = overlay;
overlay.setOpacity(
this.options.opacity ? this.options.opacity : 1
);
}
},
'setOpacity': function(opacity) {
this.options.opacity = opacity;
if (this._currentOverlay) {
this._currentOverlay.setOpacity(opacity);
}
},
// See L.TileLayer.WMS: onAdd() & getTileUrl()
'updateWmsParams': function(map) {
if (!map) {
map = this._map;
}
// Compute WMS options
var bounds = map.getBounds();
var size = map.getSize();
var wmsVersion = parseFloat(this.wmsParams.version);
var crs = this.options.crs || map.options.crs;
var projectionKey = wmsVersion >= 1.3 ? 'crs' : 'srs';
var nw = crs.project(bounds.getNorthWest());
var se = crs.project(bounds.getSouthEast());
// Assemble WMS parameter string
var params = {
'width': size.x,
'height': size.y
};
params[projectionKey] = crs.code;
params.bbox = (
wmsVersion >= 1.3 && crs === L.CRS.EPSG4326 ?
[se.y, nw.x, nw.y, se.x] :
[nw.x, se.y, se.x, nw.y]
).join(',');
L.extend(this.wmsParams, params);
},
'getImageUrl': function() {
var uppercase = this.options.uppercase || false;
var pstr = L.Util.getParamString(this.wmsParams, this._url, uppercase);
return this._url + pstr;
}
});
wms.overlay = function(url, options) {
return new wms.Overlay(url, options);
};
// Simple AJAX helper (since we can't assume jQuery etc. are present)
function ajax(url, callback) {
var context = this,
request = new XMLHttpRequest();
request.onreadystatechange = change;
request.open('GET', url);
request.send();
function change() {
if (request.readyState === 4) {
if (request.status === 200) {
callback.call(context, request.responseText);
} else {
callback.call(context, "error");
}
}
}
}
return wms;
}));