text: (function (factory) {
  	// Packaging/modules magic dance
  	var L;
  	if (typeof define === 'function' && define.amd) {
  		// AMD
  		//define(['leaflet'], factory);
  	} else if (typeof module !== 'undefined') {
  		// Node/CommonJS
  		L = require('leaflet');
  		module.exports = factory(L);
  	} else {
  		// Browser globals
  		if (typeof window.L === 'undefined')
  			throw 'Leaflet must be loaded first';
  		factory(window.L);
  	}
  }(function (L) {
  	'use strict';
  	L.Control.Geocoder = L.Control.extend({
  		options: {
  			showResultIcons: false,
  			collapsed: true,
  			expand: 'click',
  			position: 'topright',
  			placeholder: 'Search...',
  			errorMessage: 'Nothing found.'
  		},
  
  		_callbackId: 0,
  
  		initialize: function (options) {
  			L.Util.setOptions(this, options);
  			if (!this.options.geocoder) {
  				this.options.geocoder = new L.Control.Geocoder.Nominatim();
  			}
  		},
  
  		onAdd: function (map) {
  			var className = 'leaflet-control-geocoder',
  			    container = L.DomUtil.create('div', className),
  				icon = L.DomUtil.create('div', 'leaflet-control-geocoder-icon', container),
  			    form = this._form = L.DomUtil.create('form', className + '-form', container),
  			    input;
  
  			this._map = map;
  			this._container = container;
  			input = this._input = L.DomUtil.create('input');
  			input.type = 'text';
  			input.placeholder = this.options.placeholder;
  
  			L.DomEvent.addListener(input, 'keydown', this._keydown, this);
  			//L.DomEvent.addListener(input, 'onpaste', this._clearResults, this);
  			//L.DomEvent.addListener(input, 'oninput', this._clearResults, this);
  
  			this._errorElement = document.createElement('div');
  			this._errorElement.className = className + '-form-no-error';
  			this._errorElement.innerHTML = this.options.errorMessage;
  
  			this._alts = L.DomUtil.create('ul', className + '-alternatives leaflet-control-geocoder-alternatives-minimized');
  
  			form.appendChild(input);
  			form.appendChild(this._errorElement);
  			container.appendChild(this._alts);
  
  			L.DomEvent.addListener(form, 'submit', this._geocode, this);
  
  			if (this.options.collapsed) {
  				if (this.options.expand === 'click') {
  					L.DomEvent.addListener(icon, 'click', function(e) {
  						// TODO: touch
  						if (e.button === 0 && e.detail === 1) {
  							this._toggle();
  						}
  					}, this);
  				} else {
  					L.DomEvent.addListener(icon, 'mouseover', this._expand, this);
  					L.DomEvent.addListener(icon, 'mouseout', this._collapse, this);
  					this._map.on('movestart', this._collapse, this);
  				}
  			} else {
  				this._expand();
  			}
  
  			L.DomEvent.disableClickPropagation(container);
  
  			return container;
  		},
  
  		_geocodeResult: function (results) {
  			L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-throbber');
  			if (results.length === 1) {
  				this._geocodeResultSelected(results[0]);
  			} else if (results.length > 0) {
  				this._alts.innerHTML = '';
  				this._results = results;
  				L.DomUtil.removeClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
  				for (var i = 0; i < results.length; i++) {
  					this._alts.appendChild(this._createAlt(results[i], i));
  				}
  			} else {
  				L.DomUtil.addClass(this._errorElement, 'leaflet-control-geocoder-error');
  			}
  		},
  
  		markGeocode: function(result) {
  			this._map.fitBounds(result.bbox);
  
  			if (this._geocodeMarker) {
  				this._map.removeLayer(this._geocodeMarker);
  			}
  
  			this._geocodeMarker = new L.Marker(result.center)
  				.bindPopup(result.html || result.name)
  				.addTo(this._map)
  				.openPopup();
  
  			return this;
  		},
  
  		_geocode: function(event) {
  			L.DomEvent.preventDefault(event);
  
  			L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-throbber');
  			this._clearResults();
  			this.options.geocoder.geocode(this._input.value, this._geocodeResult, this);
  
  			return false;
  		},
  
  		_geocodeResultSelected: function(result) {
  			if (this.options.collapsed) {
  				this._collapse();
  			} else {
  				this._clearResults();
  			}
  			this.markGeocode(result);
  		},
  
  		_toggle: function() {
  			if (this._container.className.indexOf('leaflet-control-geocoder-expanded') >= 0) {
  				this._collapse();
  			} else {
  				this._expand();
  			}
  		},
  
  		_expand: function () {
  			L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-expanded');
  			this._input.select();
  		},
  
  		_collapse: function () {
  			this._container.className = this._container.className.replace(' leaflet-control-geocoder-expanded', '');
  			L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
  			L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
  		},
  
  		_clearResults: function () {
  			L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
  			this._selection = null;
  			L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
  		},
  
  		_createAlt: function(result, index) {
  			var li = document.createElement('li'),
  			    a = L.DomUtil.create('a', '', li),
  			    icon = this.options.showResultIcons && result.icon ? L.DomUtil.create('img', '', a) : null,
  			    text = result.html ? undefined : document.createTextNode(result.name);
  
  			if (icon) {
  				icon.src = result.icon;
  			}
  
  			a.href = '#';
  			a.setAttribute('data-result-index', index);
  
  			if (result.html) {
  				a.innerHTML = result.html;
  			} else {
  				a.appendChild(text);
  			}
  
  			L.DomEvent.addListener(li, 'click', function clickHandler(e) {
  				L.DomEvent.preventDefault(e);
  				this._geocodeResultSelected(result);
  			}, this);
  
  			return li;
  		},
  
  		_keydown: function(e) {
  			var _this = this,
  				select = function select(dir) {
  					if (_this._selection) {
  						L.DomUtil.removeClass(_this._selection.firstChild, 'leaflet-control-geocoder-selected');
  						_this._selection = _this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];
  					}
  					if (!_this._selection) {
  						_this._selection = _this._alts[dir > 0 ? 'firstChild' : 'lastChild'];
  					}
  
  					if (_this._selection) {
  						L.DomUtil.addClass(_this._selection.firstChild, 'leaflet-control-geocoder-selected');
  					}
  				};
  
  			switch (e.keyCode) {
  			// Escape
  			case 27:
  				this._collapse();
  				break;
  			// Up
  			case 38:
  				select(-1);
  				L.DomEvent.preventDefault(e);
  				break;
  			// Up
  			case 40:
  				select(1);
  				L.DomEvent.preventDefault(e);
  				break;
  			// Enter
  			case 13:
  				if (this._selection) {
  					var index = parseInt(this._selection.firstChild.getAttribute('data-result-index'), 10);
  					this._geocodeResultSelected(this._results[index]);
  					this._clearResults();
  					L.DomEvent.preventDefault(e);
  				}
  			}
  			return true;
  		}
  	});
  
  	L.Control.geocoder = function(id, options) {
  		return new L.Control.Geocoder(id, options);
  	};
  
  	L.Control.Geocoder.callbackId = 0;
  	L.Control.Geocoder.jsonp = function(url, params, callback, context, jsonpParam) {
  		var callbackId = '_l_geocoder_' + (L.Control.Geocoder.callbackId++);
  		params[jsonpParam || 'callback'] = callbackId;
  		window[callbackId] = L.Util.bind(callback, context);
  		var script = document.createElement('script');
  		script.type = 'text/javascript';
  		script.src = url + L.Util.getParamString(params);
  		script.id = callbackId;
  		document.getElementsByTagName('head')[0].appendChild(script);
  	};
  	L.Control.Geocoder.getJSON = function(url, params, callback) {
  		var xmlHttp = new XMLHttpRequest();
  		xmlHttp.open( "GET", url + L.Util.getParamString(params), true);
  		xmlHttp.send(null);
  		xmlHttp.onreadystatechange = function () {
  			if (xmlHttp.readyState != 4) return;
  			if (xmlHttp.status != 200 && req.status != 304) return;
  			callback(JSON.parse(xmlHttp.response));
  		};
  	};
  
  	L.Control.Geocoder.template = function (str, data, htmlEscape) {
  		return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
  			var value = data[key];
  			if (value === undefined) {
  				value = '';
  			} else if (typeof value === 'function') {
  				value = value(data);
  			}
  			return L.Control.Geocoder.htmlEscape(value);
  		});
  	};
  
  	// Adapted from handlebars.js
  	// https://github.com/wycats/handlebars.js/
  	L.Control.Geocoder.htmlEscape = (function() {
  		var badChars = /[&<>"'`]/g;
  		var possible = /[&<>"'`]/;
  		var escape = {
  		  '&': '&amp;',
  		  '<': '&lt;',
  		  '>': '&gt;',
  		  '"': '&quot;',
  		  '\'': '&#x27;',
  		  '`': '&#x60;'
  		};
  
  		function escapeChar(chr) {
  		  return escape[chr];
  		}
  
  		return function(string) {
  			if (string == null) {
  				return '';
  			} else if (!string) {
  				return string + '';
  			}
  
  			// Force a string conversion as this will be done by the append regardless and
  			// the regex test will do this transparently behind the scenes, causing issues if
  			// an object's to string has escaped characters in it.
  			string = '' + string;
  
  			if (!possible.test(string)) {
  				return string;
  			}
  			return string.replace(badChars, escapeChar);
  		};
  	})();
  
  	L.Control.Geocoder.Nominatim = L.Class.extend({
  		options: {
  			serviceUrl: '//nominatim.openstreetmap.org/',
  			geocodingQueryParams: {},
  			reverseQueryParams: {},
  			htmlTemplate: function(r) {
  				var a = r.address,
  					parts = [];
  				if (a.road || a.building) {
  					parts.push('{building} {road} {house_number}');
  				}
  
  				if (a.city || a.town || a.village) {
  					parts.push('<span class="' + (parts.length > 0 ? 'leaflet-control-geocoder-address-detail' : '') +
  						'">{postcode} {city}{town}{village}</span>');
  				}
  
  				if (a.state || a.country) {
  					parts.push('<span class="' + (parts.length > 0 ? 'leaflet-control-geocoder-address-context' : '') +
  						'">{state} {country}</span>');
  				}
  
  				return L.Control.Geocoder.template(parts.join('<br/>'), a, true);
  			}
  		},
  
  		initialize: function(options) {
  			L.Util.setOptions(this, options);
  		},
  
  		geocode: function(query, cb, context) {
  			L.Control.Geocoder.jsonp(this.options.serviceUrl + 'search/', L.extend({
  				q: query,
  				limit: 5,
  				format: 'json',
  				addressdetails: 1
  			}, this.options.geocodingQueryParams),
  			function(data) {
  				var results = [];
  				for (var i = data.length - 1; i >= 0; i--) {
  					var bbox = data[i].boundingbox;
  					for (var j = 0; j < 4; j++) bbox[j] = parseFloat(bbox[j]);
  					results[i] = {
  						icon: data[i].icon,
  						name: data[i].display_name,
  						html: this.options.htmlTemplate ?
  							this.options.htmlTemplate(data[i])
  							: undefined,
  						bbox: L.latLngBounds([bbox[0], bbox[2]], [bbox[1], bbox[3]]),
  						center: L.latLng(data[i].lat, data[i].lon),
  						properties: data[i]
  					};
  				}
  				cb.call(context, results);
  			}, this, 'json_callback');
  		},
  
  		reverse: function(location, scale, cb, context) {
  			L.Control.Geocoder.jsonp(this.options.serviceUrl + 'reverse/', L.extend({
  				lat: location.lat,
  				lon: location.lng,
  				zoom: Math.round(Math.log(scale / 256) / Math.log(2)),
  				addressdetails: 1,
  				format: 'json'
  			}, this.options.reverseQueryParams), function(data) {
  				var result = [],
  				    loc;
  
  				if (data && data.lat && data.lon) {
  					loc = L.latLng(data.lat, data.lon);
  					result.push({
  						name: data.display_name,
  						html: this.options.htmlTemplate ?
  							this.options.htmlTemplate(data)
  							: undefined,
  						center: loc,
  						bounds: L.latLngBounds(loc, loc),
  						properties: data
  					});
  				}
  
  				cb.call(context, result);
  			}, this, 'json_callback');
  		}
  	});
  
  	L.Control.Geocoder.nominatim = function(options) {
  		return new L.Control.Geocoder.Nominatim(options);
  	};
  
  	L.Control.Geocoder.Bing = L.Class.extend({
  		initialize: function(key) {
  			this.key = key;
  		},
  
  		geocode : function (query, cb, context) {
  			L.Control.Geocoder.jsonp('//dev.virtualearth.net/REST/v1/Locations', {
  				query: query,
  				key : this.key
  			}, function(data) {
  				var results = [];
  				for (var i = data.resourceSets[0].resources.length - 1; i >= 0; i--) {
  					var resource = data.resourceSets[0].resources[i],
  						bbox = resource.bbox;
  					results[i] = {
  						name: resource.name,
  						bbox: L.latLngBounds([bbox[0], bbox[1]], [bbox[2], bbox[3]]),
  						center: L.latLng(resource.point.coordinates)
  					};
  				}
  				cb.call(context, results);
  			}, this, 'jsonp');
  		},
  
  		reverse: function(location, scale, cb, context) {
  			L.Control.Geocoder.jsonp('//dev.virtualearth.net/REST/v1/Locations/' + location.lat + ',' + location.lng, {
  				key : this.key
  			}, function(data) {
  				var results = [];
  				for (var i = data.resourceSets[0].resources.length - 1; i >= 0; i--) {
  					var resource = data.resourceSets[0].resources[i],
  						bbox = resource.bbox;
  					results[i] = {
  						name: resource.name,
  						bbox: L.latLngBounds([bbox[0], bbox[1]], [bbox[2], bbox[3]]),
  						center: L.latLng(resource.point.coordinates)
  					};
  				}
  				cb.call(context, results);
  			}, this, 'jsonp');
  		}
  	});
  
  	L.Control.Geocoder.bing = function(key) {
  		return new L.Control.Geocoder.Bing(key);
  	};
  
  	L.Control.Geocoder.RaveGeo = L.Class.extend({
  		options: {
  			querySuffix: '',
  			deepSearch: true,
  			wordBased: false
  		},
  
  		jsonp: function(params, callback, context) {
  			var callbackId = '_l_geocoder_' + (L.Control.Geocoder.callbackId++),
  				paramParts = [];
  			params.prepend = callbackId + '(';
  			params.append = ')';
  			for (var p in params) {
  				paramParts.push(p + '=' + escape(params[p]));
  			}
  
  			window[callbackId] = L.Util.bind(callback, context);
  			var script = document.createElement('script');
  			script.type = 'text/javascript';
  			script.src = this._serviceUrl + '?' + paramParts.join('&');
  			script.id = callbackId;
  			document.getElementsByTagName('head')[0].appendChild(script);
  		},
  
  		initialize: function(serviceUrl, scheme, options) {
  			L.Util.setOptions(this, options);
  
  			this._serviceUrl = serviceUrl;
  			this._scheme = scheme;
  		},
  
  		geocode: function(query, cb, context) {
  			L.Control.Geocoder.jsonp(this._serviceUrl, {
  				address: query + this.options.querySuffix,
  				scheme: this._scheme,
  				outputFormat: 'jsonp',
  				deepSearch: this.options.deepSearch,
  				wordBased: this.options.wordBased
  			}, function(data) {
  				var results = [];
  				for (var i = data.length - 1; i >= 0; i--) {
  					var r = data[i],
  						c = L.latLng(r.y, r.x);
  					results[i] = {
  						name: r.address,
  						bbox: L.latLngBounds([c]),
  						center: c
  					};
  				}
  				cb.call(context, results);
  			}, this);
  		}
  	});
  
  	L.Control.Geocoder.raveGeo = function(serviceUrl, scheme, options) {
  		return new L.Control.Geocoder.RaveGeo(serviceUrl, scheme, options);
  	};
  
  	L.Control.Geocoder.MapQuest = L.Class.extend({
  		initialize: function(key) {
  			// MapQuest seems to provide URI encoded API keys,
  			// so to avoid encoding them twice, we decode them here
  			this._key = decodeURIComponent(key);
  		},
  
  		_formatName: function() {
  			var r = [],
  				i;
  			for (i = 0; i < arguments.length; i++) {
  				if (arguments[i]) {
  					r.push(arguments[i]);
  				}
  			}
  
  			return r.join(', ');
  		},
  
  		geocode: function(query, cb, context) {
  			L.Control.Geocoder.jsonp('//www.mapquestapi.com/geocoding/v1/address', {
  				key: this._key,
  				location: query,
  				limit: 5,
  				outFormat: 'json'
  			}, function(data) {
  				var results = [],
  					loc,
  					latLng;
  				if (data.results && data.results[0].locations) {
  					for (var i = data.results[0].locations.length - 1; i >= 0; i--) {
  						loc = data.results[0].locations[i];
  						latLng = L.latLng(loc.latLng);
  						results[i] = {
  							name: this._formatName(loc.street, loc.adminArea4, loc.adminArea3, loc.adminArea1),
  							bbox: L.latLngBounds(latLng, latLng),
  							center: latLng
  						};
  					}
  				}
  
  				cb.call(context, results);
  			}, this);
  		},
  
  		reverse: function(location, scale, cb, context) {
  			L.Control.Geocoder.jsonp('//www.mapquestapi.com/geocoding/v1/reverse', {
  				key: this._key,
  				location: location.lat + ',' + location.lng,
  				outputFormat: 'json'
  			}, function(data) {
  				var results = [],
  					loc,
  					latLng;
  				if (data.results && data.results[0].locations) {
  					for (var i = data.results[0].locations.length - 1; i >= 0; i--) {
  						loc = data.results[0].locations[i];
  						latLng = L.latLng(loc.latLng);
  						results[i] = {
  							name: this._formatName(loc.street, loc.adminArea4, loc.adminArea3, loc.adminArea1),
  							bbox: L.latLngBounds(latLng, latLng),
  							center: latLng
  						};
  					}
  				}
  
  				cb.call(context, results);
  			}, this);
  		}
  	});
  
  	L.Control.Geocoder.mapQuest = function(key) {
  		return new L.Control.Geocoder.MapQuest(key);
  	};
  
  	L.Control.Geocoder.Mapbox = L.Class.extend({
  		options: {
  			service_url: 'https://api.tiles.mapbox.com/v4/geocode/mapbox.places-v1/'
  		},
  
  		initialize: function(access_token) {
  			this._access_token = access_token;
  		},
  
  		geocode: function(query, cb, context) {
  			L.Control.Geocoder.getJSON(this.options.service_url + encodeURIComponent(query) + '.json', {
  				access_token: this._access_token,
  			}, function(data) {
  				var results = [],
  				loc,
  				latLng,
  				latLngBounds;
  				if (data.features && data.features.length) {
  					for (var i = 0; i <= data.features.length - 1; i++) {
  						loc = data.features[i];
  						latLng = L.latLng(loc.center.reverse());
  						if(loc.hasOwnProperty('bbox'))
  							{
  								latLngBounds = L.latLngBounds(L.latLng(loc.bbox.slice(0, 2).reverse()), L.latLng(loc.bbox.slice(2, 4).reverse()));
  							}
  							else
  							{
  								latLngBounds = L.latLngBounds(latLng, latLng);
  							}
  							results[i] = {
  								name: loc.place_name,
  								bbox: latLngBounds,
  								center: latLng
  							};
  						}
  					}
  
  					cb.call(context, results);
  			});
  		},
  
  		reverse: function(location, scale, cb, context) {
  			L.Control.Geocoder.getJSON(this.options.service_url + encodeURIComponent(location.lng) + ',' + encodeURIComponent(location.lat) + '.json', {
  				access_token: this._access_token,
  			}, function(data) {
  				var results = [],
  				loc,
  				latLng,
  				latLngBounds;
  				if (data.features && data.features.length) {
  					for (var i = 0; i <= data.features.length - 1; i++) {
  						loc = data.features[i];
  						latLng = L.latLng(loc.center.reverse());
  						if(loc.hasOwnProperty('bbox'))
  						{
  							latLngBounds = L.latLngBounds(L.latLng(loc.bbox.slice(0, 2).reverse()), L.latLng(loc.bbox.slice(2, 4).reverse()));
  						}
  						else
  						{
  							latLngBounds = L.latLngBounds(latLng, latLng);
  						}
  						results[i] = {
  							name: loc.place_name,
  							bbox: latLngBounds,
  							center: latLng
  						};
  					}
  				}
  
  				cb.call(context, results);
  			});
  		}
  	});
  
  	L.Control.Geocoder.mapbox = function(access_token) {
  			return new L.Control.Geocoder.Mapbox(access_token);
  	};
  
  	L.Control.Geocoder.Google = L.Class.extend({
  		options: {
  			service_url: 'https://maps.googleapis.com/maps/api/geocode/json'
  		},
  
  		initialize: function(key) {
  				this._key = key;
  		},
  
  		geocode: function(query, cb, context) {
  			var params = {
  				address: query,
  			};
  			if(this._key && this._key.length)
  			{
  				params['key'] = this._key
  			}
  
  			L.Control.Geocoder.getJSON(this.options.service_url, params, function(data) {
  					var results = [],
  							loc,
  							latLng,
  							latLngBounds;
  					if (data.results && data.results.length) {
  						for (var i = 0; i <= data.results.length - 1; i++) {
  							loc = data.results[i];
  							latLng = L.latLng(loc.geometry.location);
  							latLngBounds = L.latLngBounds(L.latLng(loc.geometry.viewport.northeast), L.latLng(loc.geometry.viewport.southwest));
  							results[i] = {
  									name: loc.formatted_address,
  									bbox: latLngBounds,
  									center: latLng
  							};
  						}
  					}
  
  					cb.call(context, results);
  			});
  		},
  
  		reverse: function(location, scale, cb, context) {
  			var params = {
  				latlng: encodeURIComponent(location.lat) + ',' + encodeURIComponent(location.lng)
  			};
  			if(this._key && this._key.length)
  			{
  				params['key'] = this._key
  			}
  			L.Control.Geocoder.getJSON(this.options.service_url, params, function(data) {
  				var results = [],
  						loc,
  						latLng,
  						latLngBounds;
  				if (data.results && data.results.length) {
  					for (var i = 0; i <= data.results.length - 1; i++) {
  						loc = data.results[i];
  						latLng = L.latLng(loc.geometry.location);
  						latLngBounds = L.latLngBounds(L.latLng(loc.geometry.viewport.northeast), L.latLng(loc.geometry.viewport.southwest));
  						results[i] = {
  							name: loc.formatted_address,
  							bbox: latLngBounds,
  							center: latLng
  						};
  					}
  				}
  
  				cb.call(context, results);
  			});
  		}
  	});
  
  	L.Control.Geocoder.google = function(key) {
  		return new L.Control.Geocoder.Google(key);
  	};
  	return L.Control.Geocoder;
  }));
  