text: /*!
   * 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 = {};
  
  // 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 = {};
          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
              );
              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;
  
  }));
  