You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
14 KiB
496 lines
14 KiB
2 years ago
|
/*!
|
||
|
* leaflet.wms.js
|
||
|
* A collection of Leaflet utilities for working with Web Mapping services.
|
||
|
* (c) 2014-2016, 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 = {};
|
||
|
|
||
|
// Quick shim for Object.keys()
|
||
|
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
|
||
|
* 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': {
|
||
|
'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) {
|
||
|
// Create overlay with all options other than untiled & identify
|
||
|
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) {
|
||
|
// 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.untiled) {
|
||
|
// Use existing overlay
|
||
|
wmsParams = this._overlay.wmsParams;
|
||
|
} else {
|
||
|
// Create overlay instance to leverage updateWmsParams
|
||
|
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) {
|
||
|
// 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) {
|
||
|
L.setOptions(this, 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);
|
||
|
},
|
||
|
'bringToBack': function() {
|
||
|
this._source.bringToBack();
|
||
|
},
|
||
|
'bringToFront': function() {
|
||
|
this._source.bringToFront();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
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,
|
||
|
'isBack': false,
|
||
|
'minZoom': 0,
|
||
|
'maxZoom': 18
|
||
|
},
|
||
|
|
||
|
'initialize': function(url, options) {
|
||
|
this._url = url;
|
||
|
|
||
|
// Move WMS parameters to params object
|
||
|
var params = {}, opts = {};
|
||
|
for (var opt in options) {
|
||
|
if (opt in this.options) {
|
||
|
opts[opt] = options[opt];
|
||
|
} else {
|
||
|
params[opt] = options[opt];
|
||
|
}
|
||
|
}
|
||
|
L.setOptions(this, opts);
|
||
|
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
|
||
|
);
|
||
|
if (this.options.isBack === true) {
|
||
|
overlay.bringToBack();
|
||
|
}
|
||
|
if (this.options.isBack === false) {
|
||
|
overlay.bringToFront();
|
||
|
}
|
||
|
}
|
||
|
if ((this._map.getZoom() < this.options.minZoom) ||
|
||
|
(this._map.getZoom() > this.options.maxZoom)){
|
||
|
this._map.removeLayer(overlay);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
'setOpacity': function(opacity) {
|
||
|
this.options.opacity = opacity;
|
||
|
if (this._currentOverlay) {
|
||
|
this._currentOverlay.setOpacity(opacity);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
'bringToBack': function() {
|
||
|
this.options.isBack = true;
|
||
|
if (this._currentOverlay) {
|
||
|
this._currentOverlay.bringToBack();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
'bringToFront': function() {
|
||
|
this.options.isBack = false;
|
||
|
if (this._currentOverlay) {
|
||
|
this._currentOverlay.bringToFront();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// 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;
|
||
|
|
||
|
}));
|