| Current Path : /var/www/homesaver/www/bitrix/js/location/osm/dist/ |
| Current File : /var/www/homesaver/www/bitrix/js/location/osm/dist/osm.bundle.js |
/* eslint-disable */
this.BX = this.BX || {};
this.BX.Location = this.BX.Location || {};
(function (exports,ui_designTokens,main_core,location_core) {
'use strict';
function _classPrivateMethodInitSpec(obj, privateSet) { _checkPrivateRedeclaration(obj, privateSet); privateSet.add(obj); }
function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
var _sourceLanguageId = /*#__PURE__*/new WeakMap();
var _autocompleteResponseConverter = /*#__PURE__*/new WeakMap();
var _autocompleteReplacements = /*#__PURE__*/new WeakMap();
var _autocompletePromptsCount = /*#__PURE__*/new WeakMap();
var _processQuery = /*#__PURE__*/new WeakSet();
var AutocompleteService = /*#__PURE__*/function (_AutocompleteServiceB) {
babelHelpers.inherits(AutocompleteService, _AutocompleteServiceB);
function AutocompleteService(props) {
var _this;
babelHelpers.classCallCheck(this, AutocompleteService);
_this = babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(AutocompleteService).call(this, props));
_classPrivateMethodInitSpec(babelHelpers.assertThisInitialized(_this), _processQuery);
_classPrivateFieldInitSpec(babelHelpers.assertThisInitialized(_this), _sourceLanguageId, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec(babelHelpers.assertThisInitialized(_this), _autocompleteResponseConverter, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec(babelHelpers.assertThisInitialized(_this), _autocompleteReplacements, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec(babelHelpers.assertThisInitialized(_this), _autocompletePromptsCount, {
writable: true,
value: void 0
});
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _sourceLanguageId, props.sourceLanguageId);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _autocompleteResponseConverter, props.responseConverter);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _autocompleteReplacements, props.autocompleteReplacements);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _autocompletePromptsCount, props.autocompletePromptsCount);
return _this;
}
babelHelpers.createClass(AutocompleteService, [{
key: "autocomplete",
value: function autocomplete(text, autocompleteServiceParams) {
var _this2 = this;
if (text === '') {
return new Promise(function (resolve) {
resolve([]);
});
}
var params = {
q: _classPrivateMethodGet(this, _processQuery, _processQuery2).call(this, text),
limit: babelHelpers.classPrivateFieldGet(this, _autocompletePromptsCount),
lang: babelHelpers.classPrivateFieldGet(this, _sourceLanguageId)
};
if (autocompleteServiceParams.biasPoint) {
var lat = autocompleteServiceParams.biasPoint.latitude;
var lon = autocompleteServiceParams.biasPoint.longitude;
if (lat && lon) {
params.lat = lat;
params.lon = lon;
}
}
var cachedResult = location_core.AutocompleteCache.get(OSM.code, params);
if (cachedResult !== null) {
return Promise.resolve(babelHelpers.classPrivateFieldGet(this, _autocompleteResponseConverter).convertResponse(cachedResult.data.result, {
text: text,
autocompleteServiceParams: autocompleteServiceParams
}));
}
return BX.ajax.runAction('location.api.location.autocomplete', {
data: {
params: params
}
}).then(function (response) {
if (response) {
location_core.AutocompleteCache.set(OSM.code, params, {
result: response.data
});
}
return response ? babelHelpers.classPrivateFieldGet(_this2, _autocompleteResponseConverter).convertResponse(response.data, {
text: text,
autocompleteServiceParams: autocompleteServiceParams
}) : [];
})["catch"](function (response) {
console.error(response);
});
}
}]);
return AutocompleteService;
}(location_core.AutocompleteServiceBase);
function _processQuery2(query) {
var result = query;
for (var partToReplace in babelHelpers.classPrivateFieldGet(this, _autocompleteReplacements)) {
if (babelHelpers.classPrivateFieldGet(this, _autocompleteReplacements).hasOwnProperty(partToReplace)) {
result = result.replace(partToReplace, babelHelpers.classPrivateFieldGet(this, _autocompleteReplacements)[partToReplace]);
}
}
return result;
}
/* @preserve
* Leaflet 1.6.0+Detached: 0c81bdf904d864fd12a286e3d1979f47aba17991.0c81bdf, a JS library for interactive maps. http://leafletjs.com
* (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade
*/
/*
* @namespace Util
*
* Various utility functions, used by Leaflet internally.
*/
var freeze = Object.freeze;
Object.freeze = function (obj) {
return obj;
};
// @function extend(dest: Object, src?: Object): Object
// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
function extend(dest) {
var i, j, len, src;
for (j = 1, len = arguments.length; j < len; j++) {
src = arguments[j];
for (i in src) {
dest[i] = src[i];
}
}
return dest;
}
// @function create(proto: Object, properties?: Object): Object
// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
var create = Object.create || function () {
function F() {}
return function (proto) {
F.prototype = proto;
return new F();
};
}();
// @function bind(fn: Function, …): Function
// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
// Has a `L.bind()` shortcut.
function bind(fn, obj) {
var slice = Array.prototype.slice;
if (fn.bind) {
return fn.bind.apply(fn, slice.call(arguments, 1));
}
var args = slice.call(arguments, 2);
return function () {
return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
};
}
// @property lastId: Number
// Last unique ID used by [`stamp()`](#util-stamp)
var lastId = 0;
// @function stamp(obj: Object): Number
// Returns the unique ID of an object, assigning it one if it doesn't have it.
function stamp(obj) {
/*eslint-disable */
obj._leaflet_id = obj._leaflet_id || ++lastId;
return obj._leaflet_id;
/* eslint-enable */
}
// @function throttle(fn: Function, time: Number, context: Object): Function
// Returns a function which executes function `fn` with the given scope `context`
// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
// `fn` will be called no more than one time per given amount of `time`. The arguments
// received by the bound function will be any arguments passed when binding the
// function, followed by any arguments passed when invoking the bound function.
// Has an `L.throttle` shortcut.
function throttle(fn, time, context) {
var lock, args, wrapperFn, later;
later = function later() {
// reset lock and call if queued
lock = false;
if (args) {
wrapperFn.apply(context, args);
args = false;
}
};
wrapperFn = function wrapperFn() {
if (lock) {
// called too soon, queue to call later
args = arguments;
} else {
// call and lock until later
fn.apply(context, arguments);
setTimeout(later, time);
lock = true;
}
};
return wrapperFn;
}
// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
// Returns the number `num` modulo `range` in such a way so it lies within
// `range[0]` and `range[1]`. The returned value will be always smaller than
// `range[1]` unless `includeMax` is set to `true`.
function wrapNum(x, range, includeMax) {
var max = range[1],
min = range[0],
d = max - min;
return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
}
// @function falseFn(): Function
// Returns a function which always returns `false`.
function falseFn() {
return false;
}
// @function formatNum(num: Number, digits?: Number): Number
// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
function formatNum(num, digits) {
var pow = Math.pow(10, digits === undefined ? 6 : digits);
return Math.round(num * pow) / pow;
}
// @function trim(str: String): String
// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
function trim(str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}
// @function splitWords(str: String): String[]
// Trims and splits the string on whitespace and returns the array of parts.
function splitWords(str) {
return trim(str).split(/\s+/);
}
// @function setOptions(obj: Object, options: Object): Object
// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
function setOptions(obj, options) {
if (!obj.hasOwnProperty('options')) {
obj.options = obj.options ? create(obj.options) : {};
}
for (var i in options) {
obj.options[i] = options[i];
}
return obj.options;
}
// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
// be appended at the end. If `uppercase` is `true`, the parameter names will
// be uppercased (e.g. `'?A=foo&B=bar'`)
function getParamString(obj, existingUrl, uppercase) {
var params = [];
for (var i in obj) {
params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
}
return (!existingUrl || existingUrl.indexOf('?') === -1 ? '?' : '&') + params.join('&');
}
var templateRe = /\{ *([\w_-]+) *\}/g;
// @function template(str: String, data: Object): String
// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
// `('Hello foo, bar')`. You can also specify functions instead of strings for
// data values — they will be evaluated passing `data` as an argument.
function template(str, data) {
return str.replace(templateRe, function (str, key) {
var value = data[key];
if (value === undefined) {
throw new Error('No value provided for variable ' + str);
} else if (typeof value === 'function') {
value = value(data);
}
return value;
});
}
// @function isArray(obj): Boolean
// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
var isArray = Array.isArray || function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
// @function indexOf(array: Array, el: Object): Number
// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
function indexOf(array, el) {
for (var i = 0; i < array.length; i++) {
if (array[i] === el) {
return i;
}
}
return -1;
}
// @property emptyImageUrl: String
// Data URI string containing a base64-encoded empty GIF image.
// Used as a hack to free memory from unused images on WebKit-powered
// mobile devices (by setting image `src` to this string).
var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
function getPrefixed(name) {
return window['webkit' + name] || window['moz' + name] || window['ms' + name];
}
var lastTime = 0;
// fallback for IE 7-8
function timeoutDefer(fn) {
var time = +new Date(),
timeToCall = Math.max(0, 16 - (time - lastTime));
lastTime = time + timeToCall;
return window.setTimeout(fn, timeToCall);
}
var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || getPrefixed('CancelRequestAnimationFrame') || function (id) {
window.clearTimeout(id);
};
// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
// `context` if given. When `immediate` is set, `fn` is called immediately if
// the browser doesn't have native support for
// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
function requestAnimFrame(fn, context, immediate) {
if (immediate && requestFn === timeoutDefer) {
fn.call(context);
} else {
return requestFn.call(window, bind(fn, context));
}
}
// @function cancelAnimFrame(id: Number): undefined
// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
function cancelAnimFrame(id) {
if (id) {
cancelFn.call(window, id);
}
}
var Util = (Object.freeze || Object)({
freeze: freeze,
extend: extend,
create: create,
bind: bind,
lastId: lastId,
stamp: stamp,
throttle: throttle,
wrapNum: wrapNum,
falseFn: falseFn,
formatNum: formatNum,
trim: trim,
splitWords: splitWords,
setOptions: setOptions,
getParamString: getParamString,
template: template,
isArray: isArray,
indexOf: indexOf,
emptyImageUrl: emptyImageUrl,
requestFn: requestFn,
cancelFn: cancelFn,
requestAnimFrame: requestAnimFrame,
cancelAnimFrame: cancelAnimFrame
});
// @class Class
// @aka L.Class
// @section
// @uninheritable
// Thanks to John Resig and Dean Edwards for inspiration!
function Class() {}
Class.extend = function (props) {
// @function extend(props: Object): Function
// [Extends the current class](#class-inheritance) given the properties to be included.
// Returns a Javascript function that is a class constructor (to be called with `new`).
var NewClass = function NewClass() {
// call the constructor
if (this.initialize) {
this.initialize.apply(this, arguments);
}
// call all constructor hooks
this.callInitHooks();
};
var parentProto = NewClass.__super__ = this.prototype;
var proto = create(parentProto);
proto.constructor = NewClass;
NewClass.prototype = proto;
// inherit parent's statics
for (var i in this) {
if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
NewClass[i] = this[i];
}
}
// mix static properties into the class
if (props.statics) {
extend(NewClass, props.statics);
delete props.statics;
}
// mix includes into the prototype
if (props.includes) {
checkDeprecatedMixinEvents(props.includes);
extend.apply(null, [proto].concat(props.includes));
delete props.includes;
}
// merge options
if (proto.options) {
props.options = extend(create(proto.options), props.options);
}
// mix given properties into the prototype
extend(proto, props);
proto._initHooks = [];
// add method for calling all hooks
proto.callInitHooks = function () {
if (this._initHooksCalled) {
return;
}
if (parentProto.callInitHooks) {
parentProto.callInitHooks.call(this);
}
this._initHooksCalled = true;
for (var i = 0, len = proto._initHooks.length; i < len; i++) {
proto._initHooks[i].call(this);
}
};
return NewClass;
};
// @function include(properties: Object): this
// [Includes a mixin](#class-includes) into the current class.
Class.include = function (props) {
extend(this.prototype, props);
return this;
};
// @function mergeOptions(options: Object): this
// [Merges `options`](#class-options) into the defaults of the class.
Class.mergeOptions = function (options) {
extend(this.prototype.options, options);
return this;
};
// @function addInitHook(fn: Function): this
// Adds a [constructor hook](#class-constructor-hooks) to the class.
Class.addInitHook = function (fn) {
// (Function) || (String, args...)
var args = Array.prototype.slice.call(arguments, 1);
var init = typeof fn === 'function' ? fn : function () {
this[fn].apply(this, args);
};
this.prototype._initHooks = this.prototype._initHooks || [];
this.prototype._initHooks.push(init);
return this;
};
function checkDeprecatedMixinEvents(includes) {
if (typeof L === 'undefined' || !L || !L.Mixin) {
return;
}
includes = isArray(includes) ? includes : [includes];
for (var i = 0; i < includes.length; i++) {
if (includes[i] === L.Mixin.Events) {
console.warn('Deprecated include of L.Mixin.Events: ' + 'this property will be removed in future releases, ' + 'please inherit from L.Evented instead.', new Error().stack);
}
}
}
/*
* @class Evented
* @aka L.Evented
* @inherits Class
*
* A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
*
* @example
*
* ```js
* map.on('click', function(e) {
* alert(e.latlng);
* } );
* ```
*
* Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
*
* ```js
* function onClick(e) { ... }
*
* map.on('click', onClick);
* map.off('click', onClick);
* ```
*/
var Events = {
/* @method on(type: String, fn: Function, context?: Object): this
* Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
*
* @alternative
* @method on(eventMap: Object): this
* Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
*/
on: function on(types, fn, context) {
// types can be a map of types/handlers
if (babelHelpers["typeof"](types) === 'object') {
for (var type in types) {
// we don't process space-separated events here for performance;
// it's a hot path since Layer uses the on(obj) syntax
this._on(type, types[type], fn);
}
} else {
// types can be a string of space-separated words
types = splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
this._on(types[i], fn, context);
}
}
return this;
},
/* @method off(type: String, fn?: Function, context?: Object): this
* Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
*
* @alternative
* @method off(eventMap: Object): this
* Removes a set of type/listener pairs.
*
* @alternative
* @method off: this
* Removes all listeners to all events on the object. This includes implicitly attached events.
*/
off: function off(types, fn, context) {
if (!types) {
// clear all listeners if called without arguments
delete this._events;
} else if (babelHelpers["typeof"](types) === 'object') {
for (var type in types) {
this._off(type, types[type], fn);
}
} else {
types = splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
this._off(types[i], fn, context);
}
}
return this;
},
// attach listener (without syntactic sugar now)
_on: function _on(type, fn, context) {
this._events = this._events || {};
/* get/init listeners for type */
var typeListeners = this._events[type];
if (!typeListeners) {
typeListeners = [];
this._events[type] = typeListeners;
}
if (context === this) {
// Less memory footprint.
context = undefined;
}
var newListener = {
fn: fn,
ctx: context
},
listeners = typeListeners;
// check if fn already there
for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i].fn === fn && listeners[i].ctx === context) {
return;
}
}
listeners.push(newListener);
},
_off: function _off(type, fn, context) {
var listeners, i, len;
if (!this._events) {
return;
}
listeners = this._events[type];
if (!listeners) {
return;
}
if (!fn) {
// Set all removed listeners to noop so they are not called if remove happens in fire
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].fn = falseFn;
}
// clear all listeners for a type if function isn't specified
delete this._events[type];
return;
}
if (context === this) {
context = undefined;
}
if (listeners) {
// find fn and remove it
for (i = 0, len = listeners.length; i < len; i++) {
var l = listeners[i];
if (l.ctx !== context) {
continue;
}
if (l.fn === fn) {
// set the removed listener to noop so that's not called if remove happens in fire
l.fn = falseFn;
if (this._firingCount) {
/* copy array in case events are being fired */
this._events[type] = listeners = listeners.slice();
}
listeners.splice(i, 1);
return;
}
}
}
},
// @method fire(type: String, data?: Object, propagate?: Boolean): this
// Fires an event of the specified type. You can optionally provide an data
// object — the first argument of the listener function will contain its
// properties. The event can optionally be propagated to event parents.
fire: function fire(type, data, propagate) {
if (!this.listens(type, propagate)) {
return this;
}
var event = extend({}, data, {
type: type,
target: this,
sourceTarget: data && data.sourceTarget || this
});
if (this._events) {
var listeners = this._events[type];
if (listeners) {
this._firingCount = this._firingCount + 1 || 1;
for (var i = 0, len = listeners.length; i < len; i++) {
var l = listeners[i];
l.fn.call(l.ctx || this, event);
}
this._firingCount--;
}
}
if (propagate) {
// propagate the event to parents (set with addEventParent)
this._propagateEvent(event);
}
return this;
},
// @method listens(type: String): Boolean
// Returns `true` if a particular event type has any listeners attached to it.
listens: function listens(type, propagate) {
var listeners = this._events && this._events[type];
if (listeners && listeners.length) {
return true;
}
if (propagate) {
// also check parents for listeners if event propagates
for (var id in this._eventParents) {
if (this._eventParents[id].listens(type, propagate)) {
return true;
}
}
}
return false;
},
// @method once(…): this
// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
once: function once(types, fn, context) {
if (babelHelpers["typeof"](types) === 'object') {
for (var type in types) {
this.once(type, types[type], fn);
}
return this;
}
var handler = bind(function () {
this.off(types, fn, context).off(types, handler, context);
}, this);
// add a listener that's executed once and removed after that
return this.on(types, fn, context).on(types, handler, context);
},
// @method addEventParent(obj: Evented): this
// Adds an event parent - an `Evented` that will receive propagated events
addEventParent: function addEventParent(obj) {
this._eventParents = this._eventParents || {};
this._eventParents[stamp(obj)] = obj;
return this;
},
// @method removeEventParent(obj: Evented): this
// Removes an event parent, so it will stop receiving propagated events
removeEventParent: function removeEventParent(obj) {
if (this._eventParents) {
delete this._eventParents[stamp(obj)];
}
return this;
},
_propagateEvent: function _propagateEvent(e) {
for (var id in this._eventParents) {
this._eventParents[id].fire(e.type, extend({
layer: e.target,
propagatedFrom: e.target
}, e), true);
}
}
};
// aliases; we should ditch those eventually
// @method addEventListener(…): this
// Alias to [`on(…)`](#evented-on)
Events.addEventListener = Events.on;
// @method removeEventListener(…): this
// Alias to [`off(…)`](#evented-off)
// @method clearAllEventListeners(…): this
// Alias to [`off()`](#evented-off)
Events.removeEventListener = Events.clearAllEventListeners = Events.off;
// @method addOneTimeEventListener(…): this
// Alias to [`once(…)`](#evented-once)
Events.addOneTimeEventListener = Events.once;
// @method fireEvent(…): this
// Alias to [`fire(…)`](#evented-fire)
Events.fireEvent = Events.fire;
// @method hasEventListeners(…): Boolean
// Alias to [`listens(…)`](#evented-listens)
Events.hasEventListeners = Events.listens;
var Evented = Class.extend(Events);
/*
* @class Point
* @aka L.Point
*
* Represents a point with `x` and `y` coordinates in pixels.
*
* @example
*
* ```js
* var point = L.point(200, 300);
* ```
*
* All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
*
* ```js
* map.panBy([200, 300]);
* map.panBy(L.point(200, 300));
* ```
*
* Note that `Point` does not inherit from Leafet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
function Point(x, y, round) {
// @property x: Number; The `x` coordinate of the point
this.x = round ? Math.round(x) : x;
// @property y: Number; The `y` coordinate of the point
this.y = round ? Math.round(y) : y;
}
var trunc = Math.trunc || function (v) {
return v > 0 ? Math.floor(v) : Math.ceil(v);
};
Point.prototype = {
// @method clone(): Point
// Returns a copy of the current point.
clone: function clone() {
return new Point(this.x, this.y);
},
// @method add(otherPoint: Point): Point
// Returns the result of addition of the current and the given points.
add: function add(point) {
// non-destructive, returns a new point
return this.clone()._add(toPoint(point));
},
_add: function _add(point) {
// destructive, used directly for performance in situations where it's safe to modify existing point
this.x += point.x;
this.y += point.y;
return this;
},
// @method subtract(otherPoint: Point): Point
// Returns the result of subtraction of the given point from the current.
subtract: function subtract(point) {
return this.clone()._subtract(toPoint(point));
},
_subtract: function _subtract(point) {
this.x -= point.x;
this.y -= point.y;
return this;
},
// @method divideBy(num: Number): Point
// Returns the result of division of the current point by the given number.
divideBy: function divideBy(num) {
return this.clone()._divideBy(num);
},
_divideBy: function _divideBy(num) {
this.x /= num;
this.y /= num;
return this;
},
// @method multiplyBy(num: Number): Point
// Returns the result of multiplication of the current point by the given number.
multiplyBy: function multiplyBy(num) {
return this.clone()._multiplyBy(num);
},
_multiplyBy: function _multiplyBy(num) {
this.x *= num;
this.y *= num;
return this;
},
// @method scaleBy(scale: Point): Point
// Multiply each coordinate of the current point by each coordinate of
// `scale`. In linear algebra terms, multiply the point by the
// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
// defined by `scale`.
scaleBy: function scaleBy(point) {
return new Point(this.x * point.x, this.y * point.y);
},
// @method unscaleBy(scale: Point): Point
// Inverse of `scaleBy`. Divide each coordinate of the current point by
// each coordinate of `scale`.
unscaleBy: function unscaleBy(point) {
return new Point(this.x / point.x, this.y / point.y);
},
// @method round(): Point
// Returns a copy of the current point with rounded coordinates.
round: function round() {
return this.clone()._round();
},
_round: function _round() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
},
// @method floor(): Point
// Returns a copy of the current point with floored coordinates (rounded down).
floor: function floor() {
return this.clone()._floor();
},
_floor: function _floor() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
},
// @method ceil(): Point
// Returns a copy of the current point with ceiled coordinates (rounded up).
ceil: function ceil() {
return this.clone()._ceil();
},
_ceil: function _ceil() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
},
// @method trunc(): Point
// Returns a copy of the current point with truncated coordinates (rounded towards zero).
trunc: function trunc() {
return this.clone()._trunc();
},
_trunc: function _trunc() {
this.x = trunc(this.x);
this.y = trunc(this.y);
return this;
},
// @method distanceTo(otherPoint: Point): Number
// Returns the cartesian distance between the current and the given points.
distanceTo: function distanceTo(point) {
point = toPoint(point);
var x = point.x - this.x,
y = point.y - this.y;
return Math.sqrt(x * x + y * y);
},
// @method equals(otherPoint: Point): Boolean
// Returns `true` if the given point has the same coordinates.
equals: function equals(point) {
point = toPoint(point);
return point.x === this.x && point.y === this.y;
},
// @method contains(otherPoint: Point): Boolean
// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
contains: function contains(point) {
point = toPoint(point);
return Math.abs(point.x) <= Math.abs(this.x) && Math.abs(point.y) <= Math.abs(this.y);
},
// @method toString(): String
// Returns a string representation of the point for debugging purposes.
toString: function toString() {
return 'Point(' + formatNum(this.x) + ', ' + formatNum(this.y) + ')';
}
};
// @factory L.point(x: Number, y: Number, round?: Boolean)
// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
// @alternative
// @factory L.point(coords: Number[])
// Expects an array of the form `[x, y]` instead.
// @alternative
// @factory L.point(coords: Object)
// Expects a plain object of the form `{x: Number, y: Number}` instead.
function toPoint(x, y, round) {
if (x instanceof Point) {
return x;
}
if (isArray(x)) {
return new Point(x[0], x[1]);
}
if (x === undefined || x === null) {
return x;
}
if (babelHelpers["typeof"](x) === 'object' && 'x' in x && 'y' in x) {
return new Point(x.x, x.y);
}
return new Point(x, y, round);
}
/*
* @class Bounds
* @aka L.Bounds
*
* Represents a rectangular area in pixel coordinates.
*
* @example
*
* ```js
* var p1 = L.point(10, 10),
* p2 = L.point(40, 60),
* bounds = L.bounds(p1, p2);
* ```
*
* All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
*
* ```js
* otherBounds.intersects([[10, 10], [40, 60]]);
* ```
*
* Note that `Bounds` does not inherit from Leafet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
function Bounds(a, b) {
if (!a) {
return;
}
var points = b ? [a, b] : a;
for (var i = 0, len = points.length; i < len; i++) {
this.extend(points[i]);
}
}
Bounds.prototype = {
// @method extend(point: Point): this
// Extends the bounds to contain the given point.
extend: function extend(point) {
// (Point)
point = toPoint(point);
// @property min: Point
// The top left corner of the rectangle.
// @property max: Point
// The bottom right corner of the rectangle.
if (!this.min && !this.max) {
this.min = point.clone();
this.max = point.clone();
} else {
this.min.x = Math.min(point.x, this.min.x);
this.max.x = Math.max(point.x, this.max.x);
this.min.y = Math.min(point.y, this.min.y);
this.max.y = Math.max(point.y, this.max.y);
}
return this;
},
// @method getCenter(round?: Boolean): Point
// Returns the center point of the bounds.
getCenter: function getCenter(round) {
return new Point((this.min.x + this.max.x) / 2, (this.min.y + this.max.y) / 2, round);
},
// @method getBottomLeft(): Point
// Returns the bottom-left point of the bounds.
getBottomLeft: function getBottomLeft() {
return new Point(this.min.x, this.max.y);
},
// @method getTopRight(): Point
// Returns the top-right point of the bounds.
getTopRight: function getTopRight() {
// -> Point
return new Point(this.max.x, this.min.y);
},
// @method getTopLeft(): Point
// Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
getTopLeft: function getTopLeft() {
return this.min; // left, top
},
// @method getBottomRight(): Point
// Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
getBottomRight: function getBottomRight() {
return this.max; // right, bottom
},
// @method getSize(): Point
// Returns the size of the given bounds
getSize: function getSize() {
return this.max.subtract(this.min);
},
// @method contains(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle contains the given one.
// @alternative
// @method contains(point: Point): Boolean
// Returns `true` if the rectangle contains the given point.
contains: function contains(obj) {
var min, max;
if (typeof obj[0] === 'number' || obj instanceof Point) {
obj = toPoint(obj);
} else {
obj = toBounds(obj);
}
if (obj instanceof Bounds) {
min = obj.min;
max = obj.max;
} else {
min = max = obj;
}
return min.x >= this.min.x && max.x <= this.max.x && min.y >= this.min.y && max.y <= this.max.y;
},
// @method intersects(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle intersects the given bounds. Two bounds
// intersect if they have at least one point in common.
intersects: function intersects(bounds) {
// (Bounds) -> Boolean
bounds = toBounds(bounds);
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xIntersects = max2.x >= min.x && min2.x <= max.x,
yIntersects = max2.y >= min.y && min2.y <= max.y;
return xIntersects && yIntersects;
},
// @method overlaps(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle overlaps the given bounds. Two bounds
// overlap if their intersection is an area.
overlaps: function overlaps(bounds) {
// (Bounds) -> Boolean
bounds = toBounds(bounds);
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xOverlaps = max2.x > min.x && min2.x < max.x,
yOverlaps = max2.y > min.y && min2.y < max.y;
return xOverlaps && yOverlaps;
},
isValid: function isValid() {
return !!(this.min && this.max);
}
};
// @factory L.bounds(corner1: Point, corner2: Point)
// Creates a Bounds object from two corners coordinate pairs.
// @alternative
// @factory L.bounds(points: Point[])
// Creates a Bounds object from the given array of points.
function toBounds(a, b) {
if (!a || a instanceof Bounds) {
return a;
}
return new Bounds(a, b);
}
/*
* @class LatLngBounds
* @aka L.LatLngBounds
*
* Represents a rectangular geographical area on a map.
*
* @example
*
* ```js
* var corner1 = L.latLng(40.712, -74.227),
* corner2 = L.latLng(40.774, -74.125),
* bounds = L.latLngBounds(corner1, corner2);
* ```
*
* All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
*
* ```js
* map.fitBounds([
* [40.712, -74.227],
* [40.774, -74.125]
* ]);
* ```
*
* Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
*
* Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
function LatLngBounds(corner1, corner2) {
// (LatLng, LatLng) or (LatLng[])
if (!corner1) {
return;
}
var latlngs = corner2 ? [corner1, corner2] : corner1;
for (var i = 0, len = latlngs.length; i < len; i++) {
this.extend(latlngs[i]);
}
}
LatLngBounds.prototype = {
// @method extend(latlng: LatLng): this
// Extend the bounds to contain the given point
// @alternative
// @method extend(otherBounds: LatLngBounds): this
// Extend the bounds to contain the given bounds
extend: function extend(obj) {
var sw = this._southWest,
ne = this._northEast,
sw2,
ne2;
if (obj instanceof LatLng) {
sw2 = obj;
ne2 = obj;
} else if (obj instanceof LatLngBounds) {
sw2 = obj._southWest;
ne2 = obj._northEast;
if (!sw2 || !ne2) {
return this;
}
} else {
return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
}
if (!sw && !ne) {
this._southWest = new LatLng(sw2.lat, sw2.lng);
this._northEast = new LatLng(ne2.lat, ne2.lng);
} else {
sw.lat = Math.min(sw2.lat, sw.lat);
sw.lng = Math.min(sw2.lng, sw.lng);
ne.lat = Math.max(ne2.lat, ne.lat);
ne.lng = Math.max(ne2.lng, ne.lng);
}
return this;
},
// @method pad(bufferRatio: Number): LatLngBounds
// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
// Negative values will retract the bounds.
pad: function pad(bufferRatio) {
var sw = this._southWest,
ne = this._northEast,
heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
return new LatLngBounds(new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
},
// @method getCenter(): LatLng
// Returns the center point of the bounds.
getCenter: function getCenter() {
return new LatLng((this._southWest.lat + this._northEast.lat) / 2, (this._southWest.lng + this._northEast.lng) / 2);
},
// @method getSouthWest(): LatLng
// Returns the south-west point of the bounds.
getSouthWest: function getSouthWest() {
return this._southWest;
},
// @method getNorthEast(): LatLng
// Returns the north-east point of the bounds.
getNorthEast: function getNorthEast() {
return this._northEast;
},
// @method getNorthWest(): LatLng
// Returns the north-west point of the bounds.
getNorthWest: function getNorthWest() {
return new LatLng(this.getNorth(), this.getWest());
},
// @method getSouthEast(): LatLng
// Returns the south-east point of the bounds.
getSouthEast: function getSouthEast() {
return new LatLng(this.getSouth(), this.getEast());
},
// @method getWest(): Number
// Returns the west longitude of the bounds
getWest: function getWest() {
return this._southWest.lng;
},
// @method getSouth(): Number
// Returns the south latitude of the bounds
getSouth: function getSouth() {
return this._southWest.lat;
},
// @method getEast(): Number
// Returns the east longitude of the bounds
getEast: function getEast() {
return this._northEast.lng;
},
// @method getNorth(): Number
// Returns the north latitude of the bounds
getNorth: function getNorth() {
return this._northEast.lat;
},
// @method contains(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle contains the given one.
// @alternative
// @method contains (latlng: LatLng): Boolean
// Returns `true` if the rectangle contains the given point.
contains: function contains(obj) {
// (LatLngBounds) or (LatLng) -> Boolean
if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
obj = toLatLng(obj);
} else {
obj = toLatLngBounds(obj);
}
var sw = this._southWest,
ne = this._northEast,
sw2,
ne2;
if (obj instanceof LatLngBounds) {
sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast();
} else {
sw2 = ne2 = obj;
}
return sw2.lat >= sw.lat && ne2.lat <= ne.lat && sw2.lng >= sw.lng && ne2.lng <= ne.lng;
},
// @method intersects(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
intersects: function intersects(bounds) {
bounds = toLatLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latIntersects = ne2.lat >= sw.lat && sw2.lat <= ne.lat,
lngIntersects = ne2.lng >= sw.lng && sw2.lng <= ne.lng;
return latIntersects && lngIntersects;
},
// @method overlaps(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
overlaps: function overlaps(bounds) {
bounds = toLatLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latOverlaps = ne2.lat > sw.lat && sw2.lat < ne.lat,
lngOverlaps = ne2.lng > sw.lng && sw2.lng < ne.lng;
return latOverlaps && lngOverlaps;
},
// @method toBBoxString(): String
// Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
toBBoxString: function toBBoxString() {
return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
},
// @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
// Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
equals: function equals(bounds, maxMargin) {
if (!bounds) {
return false;
}
bounds = toLatLngBounds(bounds);
return this._southWest.equals(bounds.getSouthWest(), maxMargin) && this._northEast.equals(bounds.getNorthEast(), maxMargin);
},
// @method isValid(): Boolean
// Returns `true` if the bounds are properly initialized.
isValid: function isValid() {
return !!(this._southWest && this._northEast);
}
};
// TODO International date line?
// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
// @alternative
// @factory L.latLngBounds(latlngs: LatLng[])
// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
function toLatLngBounds(a, b) {
if (a instanceof LatLngBounds) {
return a;
}
return new LatLngBounds(a, b);
}
/* @class LatLng
* @aka L.LatLng
*
* Represents a geographical point with a certain latitude and longitude.
*
* @example
*
* ```
* var latlng = L.latLng(50.5, 30.5);
* ```
*
* All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
*
* ```
* map.panTo([50, 30]);
* map.panTo({lon: 30, lat: 50});
* map.panTo({lat: 50, lng: 30});
* map.panTo(L.latLng(50, 30));
* ```
*
* Note that `LatLng` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
function LatLng(lat, lng, alt) {
if (isNaN(lat) || isNaN(lng)) {
throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
}
// @property lat: Number
// Latitude in degrees
this.lat = +lat;
// @property lng: Number
// Longitude in degrees
this.lng = +lng;
// @property alt: Number
// Altitude in meters (optional)
if (alt !== undefined) {
this.alt = +alt;
}
}
LatLng.prototype = {
// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
// Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
equals: function equals(obj, maxMargin) {
if (!obj) {
return false;
}
obj = toLatLng(obj);
var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
},
// @method toString(): String
// Returns a string representation of the point (for debugging purposes).
toString: function toString(precision) {
return 'LatLng(' + formatNum(this.lat, precision) + ', ' + formatNum(this.lng, precision) + ')';
},
// @method distanceTo(otherLatLng: LatLng): Number
// Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
distanceTo: function distanceTo(other) {
return Earth.distance(this, toLatLng(other));
},
// @method wrap(): LatLng
// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
wrap: function wrap() {
return Earth.wrapLatLng(this);
},
// @method toBounds(sizeInMeters: Number): LatLngBounds
// Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
toBounds: function toBounds(sizeInMeters) {
var latAccuracy = 180 * sizeInMeters / 40075017,
lngAccuracy = latAccuracy / Math.cos(Math.PI / 180 * this.lat);
return toLatLngBounds([this.lat - latAccuracy, this.lng - lngAccuracy], [this.lat + latAccuracy, this.lng + lngAccuracy]);
},
clone: function clone() {
return new LatLng(this.lat, this.lng, this.alt);
}
};
// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
// @alternative
// @factory L.latLng(coords: Array): LatLng
// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
// @alternative
// @factory L.latLng(coords: Object): LatLng
// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
function toLatLng(a, b, c) {
if (a instanceof LatLng) {
return a;
}
if (isArray(a) && babelHelpers["typeof"](a[0]) !== 'object') {
if (a.length === 3) {
return new LatLng(a[0], a[1], a[2]);
}
if (a.length === 2) {
return new LatLng(a[0], a[1]);
}
return null;
}
if (a === undefined || a === null) {
return a;
}
if (babelHelpers["typeof"](a) === 'object' && 'lat' in a) {
return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
}
if (b === undefined) {
return null;
}
return new LatLng(a, b, c);
}
/*
* @namespace CRS
* @crs L.CRS.Base
* Object that defines coordinate reference systems for projecting
* geographical points into pixel (screen) coordinates and back (and to
* coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
* [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
*
* Leaflet defines the most usual CRSs by default. If you want to use a
* CRS not defined by default, take a look at the
* [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
*
* Note that the CRS instances do not inherit from Leafet's `Class` object,
* and can't be instantiated. Also, new classes can't inherit from them,
* and methods can't be added to them with the `include` function.
*/
var CRS = {
// @method latLngToPoint(latlng: LatLng, zoom: Number): Point
// Projects geographical coordinates into pixel coordinates for a given zoom.
latLngToPoint: function latLngToPoint(latlng, zoom) {
var projectedPoint = this.projection.project(latlng),
scale = this.scale(zoom);
return this.transformation._transform(projectedPoint, scale);
},
// @method pointToLatLng(point: Point, zoom: Number): LatLng
// The inverse of `latLngToPoint`. Projects pixel coordinates on a given
// zoom into geographical coordinates.
pointToLatLng: function pointToLatLng(point, zoom) {
var scale = this.scale(zoom),
untransformedPoint = this.transformation.untransform(point, scale);
return this.projection.unproject(untransformedPoint);
},
// @method project(latlng: LatLng): Point
// Projects geographical coordinates into coordinates in units accepted for
// this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
project: function project(latlng) {
return this.projection.project(latlng);
},
// @method unproject(point: Point): LatLng
// Given a projected coordinate returns the corresponding LatLng.
// The inverse of `project`.
unproject: function unproject(point) {
return this.projection.unproject(point);
},
// @method scale(zoom: Number): Number
// Returns the scale used when transforming projected coordinates into
// pixel coordinates for a particular zoom. For example, it returns
// `256 * 2^zoom` for Mercator-based CRS.
scale: function scale(zoom) {
return 256 * Math.pow(2, zoom);
},
// @method zoom(scale: Number): Number
// Inverse of `scale()`, returns the zoom level corresponding to a scale
// factor of `scale`.
zoom: function zoom(scale) {
return Math.log(scale / 256) / Math.LN2;
},
// @method getProjectedBounds(zoom: Number): Bounds
// Returns the projection's bounds scaled and transformed for the provided `zoom`.
getProjectedBounds: function getProjectedBounds(zoom) {
if (this.infinite) {
return null;
}
var b = this.projection.bounds,
s = this.scale(zoom),
min = this.transformation.transform(b.min, s),
max = this.transformation.transform(b.max, s);
return new Bounds(min, max);
},
// @method distance(latlng1: LatLng, latlng2: LatLng): Number
// Returns the distance between two geographical coordinates.
// @property code: String
// Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
//
// @property wrapLng: Number[]
// An array of two numbers defining whether the longitude (horizontal) coordinate
// axis wraps around a given range and how. Defaults to `[-180, 180]` in most
// geographical CRSs. If `undefined`, the longitude axis does not wrap around.
//
// @property wrapLat: Number[]
// Like `wrapLng`, but for the latitude (vertical) axis.
// wrapLng: [min, max],
// wrapLat: [min, max],
// @property infinite: Boolean
// If true, the coordinate space will be unbounded (infinite in both axes)
infinite: false,
// @method wrapLatLng(latlng: LatLng): LatLng
// Returns a `LatLng` where lat and lng has been wrapped according to the
// CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
wrapLatLng: function wrapLatLng(latlng) {
var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
alt = latlng.alt;
return new LatLng(lat, lng, alt);
},
// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
// Returns a `LatLngBounds` with the same size as the given one, ensuring
// that its center is within the CRS's bounds.
// Only accepts actual `L.LatLngBounds` instances, not arrays.
wrapLatLngBounds: function wrapLatLngBounds(bounds) {
var center = bounds.getCenter(),
newCenter = this.wrapLatLng(center),
latShift = center.lat - newCenter.lat,
lngShift = center.lng - newCenter.lng;
if (latShift === 0 && lngShift === 0) {
return bounds;
}
var sw = bounds.getSouthWest(),
ne = bounds.getNorthEast(),
newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
return new LatLngBounds(newSw, newNe);
}
};
/*
* @namespace CRS
* @crs L.CRS.Earth
*
* Serves as the base for CRS that are global such that they cover the earth.
* Can only be used as the base for other CRS and cannot be used directly,
* since it does not have a `code`, `projection` or `transformation`. `distance()` returns
* meters.
*/
var Earth = extend({}, CRS, {
wrapLng: [-180, 180],
// Mean Earth Radius, as recommended for use by
// the International Union of Geodesy and Geophysics,
// see http://rosettacode.org/wiki/Haversine_formula
R: 6371000,
// distance between two geographical points using spherical law of cosines approximation
distance: function distance(latlng1, latlng2) {
var rad = Math.PI / 180,
lat1 = latlng1.lat * rad,
lat2 = latlng2.lat * rad,
sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return this.R * c;
}
});
/*
* @namespace Projection
* @projection L.Projection.SphericalMercator
*
* Spherical Mercator projection — the most common projection for online maps,
* used by almost all free and commercial tile providers. Assumes that Earth is
* a sphere. Used by the `EPSG:3857` CRS.
*/
var earthRadius = 6378137;
var SphericalMercator = {
R: earthRadius,
MAX_LATITUDE: 85.0511287798,
project: function project(latlng) {
var d = Math.PI / 180,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
sin = Math.sin(lat * d);
return new Point(this.R * latlng.lng * d, this.R * Math.log((1 + sin) / (1 - sin)) / 2);
},
unproject: function unproject(point) {
var d = 180 / Math.PI;
return new LatLng((2 * Math.atan(Math.exp(point.y / this.R)) - Math.PI / 2) * d, point.x * d / this.R);
},
bounds: function () {
var d = earthRadius * Math.PI;
return new Bounds([-d, -d], [d, d]);
}()
};
/*
* @class Transformation
* @aka L.Transformation
*
* Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
* for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
* the reverse. Used by Leaflet in its projections code.
*
* @example
*
* ```js
* var transformation = L.transformation(2, 5, -1, 10),
* p = L.point(1, 2),
* p2 = transformation.transform(p), // L.point(7, 8)
* p3 = transformation.untransform(p2); // L.point(1, 2)
* ```
*/
// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
// Creates a `Transformation` object with the given coefficients.
function Transformation(a, b, c, d) {
if (isArray(a)) {
// use array properties
this._a = a[0];
this._b = a[1];
this._c = a[2];
this._d = a[3];
return;
}
this._a = a;
this._b = b;
this._c = c;
this._d = d;
}
Transformation.prototype = {
// @method transform(point: Point, scale?: Number): Point
// Returns a transformed point, optionally multiplied by the given scale.
// Only accepts actual `L.Point` instances, not arrays.
transform: function transform(point, scale) {
// (Point, Number) -> Point
return this._transform(point.clone(), scale);
},
// destructive transform (faster)
_transform: function _transform(point, scale) {
scale = scale || 1;
point.x = scale * (this._a * point.x + this._b);
point.y = scale * (this._c * point.y + this._d);
return point;
},
// @method untransform(point: Point, scale?: Number): Point
// Returns the reverse transformation of the given point, optionally divided
// by the given scale. Only accepts actual `L.Point` instances, not arrays.
untransform: function untransform(point, scale) {
scale = scale || 1;
return new Point((point.x / scale - this._b) / this._a, (point.y / scale - this._d) / this._c);
}
};
// factory L.transformation(a: Number, b: Number, c: Number, d: Number)
// @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
// Instantiates a Transformation object with the given coefficients.
// @alternative
// @factory L.transformation(coefficients: Array): Transformation
// Expects an coefficients array of the form
// `[a: Number, b: Number, c: Number, d: Number]`.
function toTransformation(a, b, c, d) {
return new Transformation(a, b, c, d);
}
/*
* @namespace CRS
* @crs L.CRS.EPSG3857
*
* The most common CRS for online maps, used by almost all free and commercial
* tile providers. Uses Spherical Mercator projection. Set in by default in
* Map's `crs` option.
*/
var EPSG3857 = extend({}, Earth, {
code: 'EPSG:3857',
projection: SphericalMercator,
transformation: function () {
var scale = 0.5 / (Math.PI * SphericalMercator.R);
return toTransformation(scale, 0.5, -scale, 0.5);
}()
});
var EPSG900913 = extend({}, EPSG3857, {
code: 'EPSG:900913'
});
// @namespace SVG; @section
// There are several static functions which can be called without instantiating L.SVG:
// @function create(name: String): SVGElement
// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
// corresponding to the class name passed. For example, using 'line' will return
// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
function svgCreate(name) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
}
// @function pointsToPath(rings: Point[], closed: Boolean): String
// Generates a SVG path string for multiple rings, with each ring turning
// into "M..L..L.." instructions
function pointsToPath(rings, closed) {
var str = '',
i,
j,
len,
len2,
points,
p;
for (i = 0, len = rings.length; i < len; i++) {
points = rings[i];
for (j = 0, len2 = points.length; j < len2; j++) {
p = points[j];
str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
}
// closes the ring for polygons; "x" is VML syntax
str += closed ? svg ? 'z' : 'x' : '';
}
// SVG complains about empty path strings
return str || 'M0 0';
}
/*
* @namespace Browser
* @aka L.Browser
*
* A namespace with static properties for browser/feature detection used by Leaflet internally.
*
* @example
*
* ```js
* if (L.Browser.ielt9) {
* alert('Upgrade your browser, dude!');
* }
* ```
*/
var style$1 = document.documentElement.style;
// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
var ie = ('ActiveXObject' in window);
// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
var ielt9 = ie && !document.addEventListener;
// @property edge: Boolean; `true` for the Edge web browser.
var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
// @property webkit: Boolean;
// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
var webkit = userAgentContains('webkit');
// @property android: Boolean
// `true` for any browser running on an Android platform.
var android = userAgentContains('android');
// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
var android23 = userAgentContains('android 2') || userAgentContains('android 3');
/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
// @property opera: Boolean; `true` for the Opera browser
var opera = !!window.opera;
// @property chrome: Boolean; `true` for the Chrome browser.
var chrome = userAgentContains('chrome');
// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
// @property safari: Boolean; `true` for the Safari browser.
var safari = !chrome && userAgentContains('safari');
var phantom = userAgentContains('phantom');
// @property opera12: Boolean
// `true` for the Opera browser supporting CSS transforms (version 12 or later).
var opera12 = ('OTransition' in style$1);
// @property win: Boolean; `true` when the browser is running in a Windows platform
var win = navigator.platform.indexOf('Win') === 0;
// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
var ie3d = ie && 'transition' in style$1;
// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
var webkit3d = 'WebKitCSSMatrix' in window && 'm11' in new window.WebKitCSSMatrix() && !android23;
// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
var gecko3d = ('MozPerspective' in style$1);
// @property any3d: Boolean
// `true` for all browsers supporting CSS transforms.
var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
// @property mobile: Boolean; `true` for all browsers running in a mobile device.
var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
var mobileWebkit = mobile && webkit;
// @property mobileWebkit3d: Boolean
// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
var mobileWebkit3d = mobile && webkit3d;
// @property msPointer: Boolean
// `true` for browsers implementing the Microsoft touch events model (notably IE10).
var msPointer = !window.PointerEvent && window.MSPointerEvent;
// @property pointer: Boolean
// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
var pointer = !webkit && !!(window.PointerEvent || msPointer);
// @property touch: Boolean
// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
// This does not necessarily mean that the browser is running in a computer with
// a touchscreen, it only means that the browser is capable of understanding
// touch events.
var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch);
// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
var mobileOpera = mobile && opera;
// @property mobileGecko: Boolean
// `true` for gecko-based browsers running in a mobile device.
var mobileGecko = mobile && gecko;
// @property retina: Boolean
// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
var retina = (window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI) > 1;
// @property passiveEvents: Boolean
// `true` for browsers that support passive events.
var passiveEvents = function passiveEvents() {
var supportsPassiveOption = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function get() {
supportsPassiveOption = true;
}
});
window.addEventListener('testPassiveEventSupport', falseFn, opts);
window.removeEventListener('testPassiveEventSupport', falseFn, opts);
} catch (e) {
// Errors can safely be ignored since this is only a browser support test.
}
return supportsPassiveOption;
};
// @property canvas: Boolean
// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
var canvas = function () {
return !!document.createElement('canvas').getContext;
}();
// @property svg: Boolean
// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
// @property vml: Boolean
// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
var vml = !svg && function () {
try {
var div = document.createElement('div');
div.innerHTML = '<v:shape adj="1"/>';
var shape = div.firstChild;
shape.style.behavior = 'url(#default#VML)';
return shape && babelHelpers["typeof"](shape.adj) === 'object';
} catch (e) {
return false;
}
}();
function userAgentContains(str) {
return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
}
var Browser = (Object.freeze || Object)({
ie: ie,
ielt9: ielt9,
edge: edge,
webkit: webkit,
android: android,
android23: android23,
androidStock: androidStock,
opera: opera,
chrome: chrome,
gecko: gecko,
safari: safari,
phantom: phantom,
opera12: opera12,
win: win,
ie3d: ie3d,
webkit3d: webkit3d,
gecko3d: gecko3d,
any3d: any3d,
mobile: mobile,
mobileWebkit: mobileWebkit,
mobileWebkit3d: mobileWebkit3d,
msPointer: msPointer,
pointer: pointer,
touch: touch,
mobileOpera: mobileOpera,
mobileGecko: mobileGecko,
retina: retina,
passiveEvents: passiveEvents,
canvas: canvas,
svg: svg,
vml: vml
});
/*
* Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
*/
var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
var _pointers = {};
var _pointerDocListener = false;
// DomEvent.DoubleTap needs to know about this
var _pointersCount = 0;
// Provides a touch events wrapper for (ms)pointer events.
// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
function addPointerListener(obj, type, handler, id) {
if (type === 'touchstart') {
_addPointerStart(obj, handler, id);
} else if (type === 'touchmove') {
_addPointerMove(obj, handler, id);
} else if (type === 'touchend') {
_addPointerEnd(obj, handler, id);
}
return this;
}
function removePointerListener(obj, type, id) {
var handler = obj['_leaflet_' + type + id];
if (type === 'touchstart') {
obj.removeEventListener(POINTER_DOWN, handler, false);
} else if (type === 'touchmove') {
obj.removeEventListener(POINTER_MOVE, handler, false);
} else if (type === 'touchend') {
obj.removeEventListener(POINTER_UP, handler, false);
obj.removeEventListener(POINTER_CANCEL, handler, false);
}
return this;
}
function _addPointerStart(obj, handler, id) {
var onDown = bind(function (e) {
if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
// In IE11, some touch events needs to fire for form controls, or
// the controls will stop working. We keep a whitelist of tag names that
// need these events. For other target tags, we prevent default on the event.
if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
preventDefault(e);
} else {
return;
}
}
_handlePointer(e, handler);
});
obj['_leaflet_touchstart' + id] = onDown;
obj.addEventListener(POINTER_DOWN, onDown, false);
// need to keep track of what pointers and how many are active to provide e.touches emulation
if (!_pointerDocListener) {
// we listen documentElement as any drags that end by moving the touch off the screen get fired there
document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
_pointerDocListener = true;
}
}
function _globalPointerDown(e) {
_pointers[e.pointerId] = e;
_pointersCount++;
}
function _globalPointerMove(e) {
if (_pointers[e.pointerId]) {
_pointers[e.pointerId] = e;
}
}
function _globalPointerUp(e) {
delete _pointers[e.pointerId];
_pointersCount--;
}
function _handlePointer(e, handler) {
e.touches = [];
for (var i in _pointers) {
e.touches.push(_pointers[i]);
}
e.changedTouches = [e];
handler(e);
}
function _addPointerMove(obj, handler, id) {
var onMove = function onMove(e) {
// don't fire touch moves when mouse isn't down
if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) {
return;
}
_handlePointer(e, handler);
};
obj['_leaflet_touchmove' + id] = onMove;
obj.addEventListener(POINTER_MOVE, onMove, false);
}
function _addPointerEnd(obj, handler, id) {
var onUp = function onUp(e) {
_handlePointer(e, handler);
};
obj['_leaflet_touchend' + id] = onUp;
obj.addEventListener(POINTER_UP, onUp, false);
obj.addEventListener(POINTER_CANCEL, onUp, false);
}
/*
* Extends the event handling code with double tap support for mobile browsers.
*/
var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
var _pre = '_leaflet_';
// inspired by Zepto touch code by Thomas Fuchs
function addDoubleTapListener(obj, handler, id) {
var last,
touch$$1,
doubleTap = false,
delay = 250;
function onTouchStart(e) {
var count;
if (pointer) {
if (!edge || e.pointerType === 'mouse') {
return;
}
count = _pointersCount;
} else {
count = e.touches.length;
}
if (count > 1) {
return;
}
var now = Date.now(),
delta = now - (last || now);
touch$$1 = e.touches ? e.touches[0] : e;
doubleTap = delta > 0 && delta <= delay;
last = now;
}
function onTouchEnd(e) {
if (doubleTap && !touch$$1.cancelBubble) {
if (pointer) {
if (!edge || e.pointerType === 'mouse') {
return;
}
// work around .type being readonly with MSPointer* events
var newTouch = {},
prop,
i;
for (i in touch$$1) {
prop = touch$$1[i];
newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
}
touch$$1 = newTouch;
}
touch$$1.type = 'dblclick';
touch$$1.button = 0;
handler(touch$$1);
last = null;
}
}
obj[_pre + _touchstart + id] = onTouchStart;
obj[_pre + _touchend + id] = onTouchEnd;
obj[_pre + 'dblclick' + id] = handler;
obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {
passive: false
} : false);
obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {
passive: false
} : false);
// On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
// the browser doesn't fire touchend/pointerup events but does fire
// native dblclicks. See #4127.
// Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
obj.addEventListener('dblclick', handler, false);
return this;
}
function removeDoubleTapListener(obj, id) {
var touchstart = obj[_pre + _touchstart + id],
touchend = obj[_pre + _touchend + id],
dblclick = obj[_pre + 'dblclick' + id];
obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {
passive: false
} : false);
obj.removeEventListener(_touchend, touchend, passiveEvents ? {
passive: false
} : false);
if (!edge) {
obj.removeEventListener('dblclick', dblclick, false);
}
return this;
}
/*
* @namespace DomUtil
*
* Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
* tree, used by Leaflet internally.
*
* Most functions expecting or returning a `HTMLElement` also work for
* SVG elements. The only difference is that classes refer to CSS classes
* in HTML and SVG classes in SVG.
*/
// @property TRANSFORM: String
// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
var TRANSFORM = testProp(['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
// webkitTransition comes first because some browser versions that drop vendor prefix don't do
// the same for the transitionend event, in particular the Android 4.1 stock browser
// @property TRANSITION: String
// Vendor-prefixed transition style name.
var TRANSITION = testProp(['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
// @property TRANSITION_END: String
// Vendor-prefixed transitionend event name.
var TRANSITION_END = TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
// @function get(id: String|HTMLElement): HTMLElement
// Returns an element given its DOM id, or returns the element itself
// if it was passed directly.
function get(id) {
return typeof id === 'string' ? document.getElementById(id) : id;
}
// @function getStyle(el: HTMLElement, styleAttrib: String): String
// Returns the value for a certain style attribute on an element,
// including computed values or values set through CSS.
function getStyle(el, style) {
var value = el.style[style] || el.currentStyle && el.currentStyle[style];
if ((!value || value === 'auto') && document.defaultView) {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css[style] : null;
}
return value === 'auto' ? null : value;
}
// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
function create$1(tagName, className, container) {
var el = document.createElement(tagName);
el.className = className || '';
if (container) {
container.appendChild(el);
}
return el;
}
// @function remove(el: HTMLElement)
// Removes `el` from its parent element
function _remove(el) {
var parent = el.parentNode;
if (parent) {
parent.removeChild(el);
}
}
// @function empty(el: HTMLElement)
// Removes all of `el`'s children elements from `el`
function empty(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
// @function toFront(el: HTMLElement)
// Makes `el` the last child of its parent, so it renders in front of the other children.
function toFront(el) {
var parent = el.parentNode;
if (parent && parent.lastChild !== el) {
parent.appendChild(el);
}
}
// @function toBack(el: HTMLElement)
// Makes `el` the first child of its parent, so it renders behind the other children.
function toBack(el) {
var parent = el.parentNode;
if (parent && parent.firstChild !== el) {
parent.insertBefore(el, parent.firstChild);
}
}
// @function hasClass(el: HTMLElement, name: String): Boolean
// Returns `true` if the element's class attribute contains `name`.
function hasClass(el, name) {
if (el.classList !== undefined) {
return el.classList.contains(name);
}
var className = getClass(el);
return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
}
// @function addClass(el: HTMLElement, name: String)
// Adds `name` to the element's class attribute.
function addClass(el, name) {
if (el.classList !== undefined) {
var classes = splitWords(name);
for (var i = 0, len = classes.length; i < len; i++) {
el.classList.add(classes[i]);
}
} else if (!hasClass(el, name)) {
var className = getClass(el);
setClass(el, (className ? className + ' ' : '') + name);
}
}
// @function removeClass(el: HTMLElement, name: String)
// Removes `name` from the element's class attribute.
function removeClass(el, name) {
if (el.classList !== undefined) {
el.classList.remove(name);
} else {
setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
}
}
// @function setClass(el: HTMLElement, name: String)
// Sets the element's class.
function setClass(el, name) {
if (el.className.baseVal === undefined) {
el.className = name;
} else {
// in case of SVG element
el.className.baseVal = name;
}
}
// @function getClass(el: HTMLElement): String
// Returns the element's class.
function getClass(el) {
// Check if the element is an SVGElementInstance and use the correspondingElement instead
// (Required for linked SVG elements in IE11.)
if (el.correspondingElement) {
el = el.correspondingElement;
}
return el.className.baseVal === undefined ? el.className : el.className.baseVal;
}
// @function setOpacity(el: HTMLElement, opacity: Number)
// Set the opacity of an element (including old IE support).
// `opacity` must be a number from `0` to `1`.
function _setOpacity(el, value) {
if ('opacity' in el.style) {
el.style.opacity = value;
} else if ('filter' in el.style) {
_setOpacityIE(el, value);
}
}
function _setOpacityIE(el, value) {
var filter = false,
filterName = 'DXImageTransform.Microsoft.Alpha';
// filters collection throws an error if we try to retrieve a filter that doesn't exist
try {
filter = el.filters.item(filterName);
} catch (e) {
// don't set opacity to 1 if we haven't already set an opacity,
// it isn't needed and breaks transparent pngs.
if (value === 1) {
return;
}
}
value = Math.round(value * 100);
if (filter) {
filter.Enabled = value !== 100;
filter.Opacity = value;
} else {
el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
}
}
// @function testProp(props: String[]): String|false
// Goes through the array of style names and returns the first name
// that is a valid style name for an element. If no such name is found,
// it returns false. Useful for vendor-prefixed styles like `transform`.
function testProp(props) {
var style = document.documentElement.style;
for (var i = 0; i < props.length; i++) {
if (props[i] in style) {
return props[i];
}
}
return false;
}
// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
// and optionally scaled by `scale`. Does not have an effect if the
// browser doesn't support 3D CSS transforms.
function setTransform(el, offset, scale) {
var pos = offset || new Point(0, 0);
el.style[TRANSFORM] = (ie3d ? 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + (scale ? ' scale(' + scale + ')' : '');
}
// @function setPosition(el: HTMLElement, position: Point)
// Sets the position of `el` to coordinates specified by `position`,
// using CSS translate or top/left positioning depending on the browser
// (used by Leaflet internally to position its layers).
function setPosition(el, point) {
/*eslint-disable */
el._leaflet_pos = point;
/* eslint-enable */
if (any3d) {
setTransform(el, point);
} else {
el.style.left = point.x + 'px';
el.style.top = point.y + 'px';
}
}
// @function getPosition(el: HTMLElement): Point
// Returns the coordinates of an element previously positioned with setPosition.
function getPosition(el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
return el._leaflet_pos || new Point(0, 0);
}
// @function disableTextSelection()
// Prevents the user from generating `selectstart` DOM events, usually generated
// when the user drags the mouse through a page with text. Used internally
// by Leaflet to override the behaviour of any click-and-drag interaction on
// the map. Affects drag interactions on the whole document.
// @function enableTextSelection()
// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
var disableTextSelection;
var enableTextSelection;
var _userSelect;
if ('onselectstart' in document) {
disableTextSelection = function disableTextSelection() {
on(window, 'selectstart', preventDefault);
};
enableTextSelection = function enableTextSelection() {
off(window, 'selectstart', preventDefault);
};
} else {
var userSelectProperty = testProp(['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
disableTextSelection = function disableTextSelection() {
if (userSelectProperty) {
var style = document.documentElement.style;
_userSelect = style[userSelectProperty];
style[userSelectProperty] = 'none';
}
};
enableTextSelection = function enableTextSelection() {
if (userSelectProperty) {
document.documentElement.style[userSelectProperty] = _userSelect;
_userSelect = undefined;
}
};
}
// @function disableImageDrag()
// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
// for `dragstart` DOM events, usually generated when the user drags an image.
function disableImageDrag() {
on(window, 'dragstart', preventDefault);
}
// @function enableImageDrag()
// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
function enableImageDrag() {
off(window, 'dragstart', preventDefault);
}
var _outlineElement;
var _outlineStyle;
// @function preventOutline(el: HTMLElement)
// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
// of the element `el` invisible. Used internally by Leaflet to prevent
// focusable elements from displaying an outline when the user performs a
// drag interaction on them.
function preventOutline(element) {
while (element.tabIndex === -1) {
element = element.parentNode;
}
if (!element.style) {
return;
}
restoreOutline();
_outlineElement = element;
_outlineStyle = element.style.outline;
element.style.outline = 'none';
on(window, 'keydown', restoreOutline);
}
// @function restoreOutline()
// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
function restoreOutline() {
if (!_outlineElement) {
return;
}
_outlineElement.style.outline = _outlineStyle;
_outlineElement = undefined;
_outlineStyle = undefined;
off(window, 'keydown', restoreOutline);
}
// @function getSizedParentNode(el: HTMLElement): HTMLElement
// Finds the closest parent node which size (width and height) is not null.
function getSizedParentNode(element) {
do {
element = element.parentNode;
} while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
return element;
}
// @function getScale(el: HTMLElement): Object
// Computes the CSS scale currently applied on the element.
// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
function getScale(element) {
var rect = element.getBoundingClientRect(); // Read-only in old browsers.
return {
x: rect.width / element.offsetWidth || 1,
y: rect.height / element.offsetHeight || 1,
boundingClientRect: rect
};
}
var DomUtil = (Object.freeze || Object)({
TRANSFORM: TRANSFORM,
TRANSITION: TRANSITION,
TRANSITION_END: TRANSITION_END,
get: get,
getStyle: getStyle,
create: create$1,
remove: _remove,
empty: empty,
toFront: toFront,
toBack: toBack,
hasClass: hasClass,
addClass: addClass,
removeClass: removeClass,
setClass: setClass,
getClass: getClass,
setOpacity: _setOpacity,
testProp: testProp,
setTransform: setTransform,
setPosition: setPosition,
getPosition: getPosition,
disableTextSelection: disableTextSelection,
enableTextSelection: enableTextSelection,
disableImageDrag: disableImageDrag,
enableImageDrag: enableImageDrag,
preventOutline: preventOutline,
restoreOutline: restoreOutline,
getSizedParentNode: getSizedParentNode,
getScale: getScale
});
/*
* @namespace DomEvent
* Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
*/
// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
// Adds a listener function (`fn`) to a particular DOM event type of the
// element `el`. You can optionally specify the context of the listener
// (object the `this` keyword will point to). You can also pass several
// space-separated types (e.g. `'click dblclick'`).
// @alternative
// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
function on(obj, types, fn, context) {
if (babelHelpers["typeof"](types) === 'object') {
for (var type in types) {
addOne(obj, type, types[type], fn);
}
} else {
types = splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
addOne(obj, types[i], fn, context);
}
}
return this;
}
var eventsKey = '_leaflet_events';
// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
// Removes a previously added listener function.
// Note that if you passed a custom context to on, you must pass the same
// context to `off` in order to remove the listener.
// @alternative
// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
function off(obj, types, fn, context) {
if (babelHelpers["typeof"](types) === 'object') {
for (var type in types) {
removeOne(obj, type, types[type], fn);
}
} else if (types) {
types = splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
removeOne(obj, types[i], fn, context);
}
} else {
for (var j in obj[eventsKey]) {
removeOne(obj, j, obj[eventsKey][j]);
}
delete obj[eventsKey];
}
return this;
}
function addOne(obj, type, fn, context) {
var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
if (obj[eventsKey] && obj[eventsKey][id]) {
return this;
}
var handler = function handler(e) {
return fn.call(context || obj, e || window.event);
};
var originalHandler = handler;
if (pointer && type.indexOf('touch') === 0) {
// Needs DomEvent.Pointer.js
addPointerListener(obj, type, handler, id);
} else if (touch && type === 'dblclick' && addDoubleTapListener && !(pointer && chrome)) {
// Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
// See #5180
addDoubleTapListener(obj, handler, id);
} else if ('addEventListener' in obj) {
if (type === 'mousewheel') {
obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {
passive: false
} : false);
} else if (type === 'mouseenter' || type === 'mouseleave') {
handler = function handler(e) {
e = e || window.event;
if (isExternalTarget(obj, e)) {
originalHandler(e);
}
};
obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
} else {
if (type === 'click' && android) {
handler = function handler(e) {
filterClick(e, originalHandler);
};
}
obj.addEventListener(type, handler, false);
}
} else if ('attachEvent' in obj) {
obj.attachEvent('on' + type, handler);
}
obj[eventsKey] = obj[eventsKey] || {};
obj[eventsKey][id] = handler;
}
function removeOne(obj, type, fn, context) {
var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
handler = obj[eventsKey] && obj[eventsKey][id];
if (!handler) {
return this;
}
if (pointer && type.indexOf('touch') === 0) {
removePointerListener(obj, type, id);
} else if (touch && type === 'dblclick' && removeDoubleTapListener && !(pointer && chrome)) {
removeDoubleTapListener(obj, id);
} else if ('removeEventListener' in obj) {
if (type === 'mousewheel') {
obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {
passive: false
} : false);
} else {
obj.removeEventListener(type === 'mouseenter' ? 'mouseover' : type === 'mouseleave' ? 'mouseout' : type, handler, false);
}
} else if ('detachEvent' in obj) {
obj.detachEvent('on' + type, handler);
}
obj[eventsKey][id] = null;
}
// @function stopPropagation(ev: DOMEvent): this
// Stop the given event from propagation to parent elements. Used inside the listener functions:
// ```js
// L.DomEvent.on(div, 'click', function (ev) {
// L.DomEvent.stopPropagation(ev);
// });
// ```
function stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();
} else if (e.originalEvent) {
// In case of Leaflet event.
e.originalEvent._stopped = true;
} else {
e.cancelBubble = true;
}
skipped(e);
return this;
}
// @function disableScrollPropagation(el: HTMLElement): this
// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
function disableScrollPropagation(el) {
addOne(el, 'mousewheel', stopPropagation);
return this;
}
// @function disableClickPropagation(el: HTMLElement): this
// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
// `'mousedown'` and `'touchstart'` events (plus browser variants).
function disableClickPropagation(el) {
on(el, 'mousedown touchstart dblclick', stopPropagation);
addOne(el, 'click', fakeStop);
return this;
}
// @function preventDefault(ev: DOMEvent): this
// Prevents the default action of the DOM Event `ev` from happening (such as
// following a link in the href of the a element, or doing a POST request
// with page reload when a `<form>` is submitted).
// Use it inside listener functions.
function preventDefault(e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
return this;
}
// @function stop(ev: DOMEvent): this
// Does `stopPropagation` and `preventDefault` at the same time.
function stop(e) {
preventDefault(e);
stopPropagation(e);
return this;
}
// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
// Gets normalized mouse position from a DOM event relative to the
// `container` (border excluded) or to the whole page if not specified.
function getMousePosition(e, container) {
if (!container) {
return new Point(e.clientX, e.clientY);
}
var scale = getScale(container),
offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
return new Point(
// offset.left/top values are in page scale (like clientX/Y),
// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
(e.clientX - offset.left) / scale.x - container.clientLeft, (e.clientY - offset.top) / scale.y - container.clientTop);
}
// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
// and Firefox scrolls device pixels, not CSS pixels
var wheelPxFactor = win && chrome ? 2 * window.devicePixelRatio : gecko ? window.devicePixelRatio : 1;
// @function getWheelDelta(ev: DOMEvent): Number
// Gets normalized wheel delta from a mousewheel DOM event, in vertical
// pixels scrolled (negative if scrolling down).
// Events from pointing devices without precise scrolling are mapped to
// a best guess of 60 pixels.
function getWheelDelta(e) {
return edge ? e.wheelDeltaY / 2 :
// Don't trust window-geometry-based delta
e.deltaY && e.deltaMode === 0 ? -e.deltaY / wheelPxFactor :
// Pixels
e.deltaY && e.deltaMode === 1 ? -e.deltaY * 20 :
// Lines
e.deltaY && e.deltaMode === 2 ? -e.deltaY * 60 :
// Pages
e.deltaX || e.deltaZ ? 0 :
// Skip horizontal/depth wheel events
e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 :
// Legacy IE pixels
e.detail && Math.abs(e.detail) < 32765 ? -e.detail * 20 :
// Legacy Moz lines
e.detail ? e.detail / -32765 * 60 :
// Legacy Moz pages
0;
}
var skipEvents = {};
function fakeStop(e) {
// fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
skipEvents[e.type] = true;
}
function skipped(e) {
var events = skipEvents[e.type];
// reset when checking, as it's only used in map container and propagates outside of the map
skipEvents[e.type] = false;
return events;
}
// check if element really left/entered the event target (for mouseenter/mouseleave)
function isExternalTarget(el, e) {
var related = e.relatedTarget;
if (!related) {
return true;
}
try {
while (related && related !== el) {
related = related.parentNode;
}
} catch (err) {
return false;
}
return related !== el;
}
var lastClick;
// this is a horrible workaround for a bug in Android where a single touch triggers two click events
function filterClick(e, handler) {
var timeStamp = e.timeStamp || e.originalEvent && e.originalEvent.timeStamp,
elapsed = lastClick && timeStamp - lastClick;
// are they closer together than 500ms yet more than 100ms?
// Android typically triggers them ~300ms apart while multiple listeners
// on the same event should be triggered far faster;
// or check if click is simulated on the element, and if it is, reject any non-simulated events
if (elapsed && elapsed > 100 && elapsed < 500 || e.target._simulatedClick && !e._simulated) {
stop(e);
return;
}
lastClick = timeStamp;
handler(e);
}
var DomEvent = (Object.freeze || Object)({
on: on,
off: off,
stopPropagation: stopPropagation,
disableScrollPropagation: disableScrollPropagation,
disableClickPropagation: disableClickPropagation,
preventDefault: preventDefault,
stop: stop,
getMousePosition: getMousePosition,
getWheelDelta: getWheelDelta,
fakeStop: fakeStop,
skipped: skipped,
isExternalTarget: isExternalTarget,
addListener: on,
removeListener: off
});
/*
* @class PosAnimation
* @aka L.PosAnimation
* @inherits Evented
* Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
*
* @example
* ```js
* var fx = new L.PosAnimation();
* fx.run(el, [300, 500], 0.5);
* ```
*
* @constructor L.PosAnimation()
* Creates a `PosAnimation` object.
*
*/
var PosAnimation = Evented.extend({
// @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
// Run an animation of a given element to a new position, optionally setting
// duration in seconds (`0.25` by default) and easing linearity factor (3rd
// argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
// `0.5` by default).
run: function run(el, newPos, duration, easeLinearity) {
this.stop();
this._el = el;
this._inProgress = true;
this._duration = duration || 0.25;
this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
this._startPos = getPosition(el);
this._offset = newPos.subtract(this._startPos);
this._startTime = +new Date();
// @event start: Event
// Fired when the animation starts
this.fire('start');
this._animate();
},
// @method stop()
// Stops the animation (if currently running).
stop: function stop() {
if (!this._inProgress) {
return;
}
this._step(true);
this._complete();
},
_animate: function _animate() {
// animation loop
this._animId = requestAnimFrame(this._animate, this);
this._step();
},
_step: function _step(round) {
var elapsed = +new Date() - this._startTime,
duration = this._duration * 1000;
if (elapsed < duration) {
this._runFrame(this._easeOut(elapsed / duration), round);
} else {
this._runFrame(1);
this._complete();
}
},
_runFrame: function _runFrame(progress, round) {
var pos = this._startPos.add(this._offset.multiplyBy(progress));
if (round) {
pos._round();
}
setPosition(this._el, pos);
// @event step: Event
// Fired continuously during the animation.
this.fire('step');
},
_complete: function _complete() {
cancelAnimFrame(this._animId);
this._inProgress = false;
// @event end: Event
// Fired when the animation ends.
this.fire('end');
},
_easeOut: function _easeOut(t) {
return 1 - Math.pow(1 - t, this._easeOutPower);
}
});
/*
* @class Map
* @aka L.Map
* @inherits Evented
*
* The central class of the API — it is used to create a map on a page and manipulate it.
*
* @example
*
* ```js
* // initialize the map on the "map" div with a given center and zoom
* var map = L.map('map', {
* center: [51.505, -0.09],
* zoom: 13
* });
* ```
*
*/
var Map = Evented.extend({
options: {
// @section Map State Options
// @option crs: CRS = L.CRS.EPSG3857
// The [Coordinate Reference System](#crs) to use. Don't change this if you're not
// sure what it means.
crs: EPSG3857,
// @option center: LatLng = undefined
// Initial geographic center of the map
center: undefined,
// @option zoom: Number = undefined
// Initial map zoom level
zoom: undefined,
// @option minZoom: Number = *
// Minimum zoom level of the map.
// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
// the lowest of their `minZoom` options will be used instead.
minZoom: undefined,
// @option maxZoom: Number = *
// Maximum zoom level of the map.
// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
// the highest of their `maxZoom` options will be used instead.
maxZoom: undefined,
// @option layers: Layer[] = []
// Array of layers that will be added to the map initially
layers: [],
// @option maxBounds: LatLngBounds = null
// When this option is set, the map restricts the view to the given
// geographical bounds, bouncing the user back if the user tries to pan
// outside the view. To set the restriction dynamically, use
// [`setMaxBounds`](#map-setmaxbounds) method.
maxBounds: undefined,
// @option renderer: Renderer = *
// The default method for drawing vector layers on the map. `L.SVG`
// or `L.Canvas` by default depending on browser support.
renderer: undefined,
// @section Animation Options
// @option zoomAnimation: Boolean = true
// Whether the map zoom animation is enabled. By default it's enabled
// in all browsers that support CSS3 Transitions except Android.
zoomAnimation: true,
// @option zoomAnimationThreshold: Number = 4
// Won't animate zoom if the zoom difference exceeds this value.
zoomAnimationThreshold: 4,
// @option fadeAnimation: Boolean = true
// Whether the tile fade animation is enabled. By default it's enabled
// in all browsers that support CSS3 Transitions except Android.
fadeAnimation: true,
// @option markerZoomAnimation: Boolean = true
// Whether markers animate their zoom with the zoom animation, if disabled
// they will disappear for the length of the animation. By default it's
// enabled in all browsers that support CSS3 Transitions except Android.
markerZoomAnimation: true,
// @option transform3DLimit: Number = 2^23
// Defines the maximum size of a CSS translation transform. The default
// value should not be changed unless a web browser positions layers in
// the wrong place after doing a large `panBy`.
transform3DLimit: 8388608,
// Precision limit of a 32-bit float
// @section Interaction Options
// @option zoomSnap: Number = 1
// Forces the map's zoom level to always be a multiple of this, particularly
// right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
// By default, the zoom level snaps to the nearest integer; lower values
// (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
// means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
zoomSnap: 1,
// @option zoomDelta: Number = 1
// Controls how much the map's zoom level will change after a
// [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
// or `-` on the keyboard, or using the [zoom controls](#control-zoom).
// Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
zoomDelta: 1,
// @option trackResize: Boolean = true
// Whether the map automatically handles browser window resize to update itself.
trackResize: true
},
initialize: function initialize(id, options) {
// (HTMLElement or String, Object)
options = setOptions(this, options);
// Make sure to assign internal flags at the beginning,
// to avoid inconsistent state in some edge cases.
this._handlers = [];
this._layers = {};
this._zoomBoundLayers = {};
this._sizeChanged = true;
this._initContainer(id);
this._initLayout();
// hack for https://github.com/Leaflet/Leaflet/issues/1980
this._onResize = bind(this._onResize, this);
this._initEvents();
if (options.maxBounds) {
this.setMaxBounds(options.maxBounds);
}
if (options.zoom !== undefined) {
this._zoom = this._limitZoom(options.zoom);
}
if (options.center && options.zoom !== undefined) {
this.setView(toLatLng(options.center), options.zoom, {
reset: true
});
}
this.callInitHooks();
// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
this._zoomAnimated = TRANSITION && any3d && !mobileOpera && this.options.zoomAnimation;
// zoom transitions run with the same duration for all layers, so if one of transitionend events
// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
if (this._zoomAnimated) {
this._createAnimProxy();
on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
}
this._addLayers(this.options.layers);
},
// @section Methods for modifying map state
// @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
// Sets the view of the map (geographical center and zoom) with the given
// animation options.
setView: function setView(center, zoom, options) {
zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
options = options || {};
this._stop();
if (this._loaded && !options.reset && options !== true) {
if (options.animate !== undefined) {
options.zoom = extend({
animate: options.animate
}, options.zoom);
options.pan = extend({
animate: options.animate,
duration: options.duration
}, options.pan);
}
// try animating pan or zoom
var moved = this._zoom !== zoom ? this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : this._tryAnimatedPan(center, options.pan);
if (moved) {
// prevent resize handler call, the view will refresh after animation anyway
clearTimeout(this._sizeTimer);
return this;
}
}
// animation didn't start, just reset the map view
this._resetView(center, zoom);
return this;
},
// @method setZoom(zoom: Number, options?: Zoom/pan options): this
// Sets the zoom of the map.
setZoom: function setZoom(zoom, options) {
if (!this._loaded) {
this._zoom = zoom;
return this;
}
return this.setView(this.getCenter(), zoom, {
zoom: options
});
},
// @method zoomIn(delta?: Number, options?: Zoom options): this
// Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
zoomIn: function zoomIn(delta, options) {
delta = delta || (any3d ? this.options.zoomDelta : 1);
return this.setZoom(this._zoom + delta, options);
},
// @method zoomOut(delta?: Number, options?: Zoom options): this
// Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
zoomOut: function zoomOut(delta, options) {
delta = delta || (any3d ? this.options.zoomDelta : 1);
return this.setZoom(this._zoom - delta, options);
},
// @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
// Zooms the map while keeping a specified geographical point on the map
// stationary (e.g. used internally for scroll zoom and double-click zoom).
// @alternative
// @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
// Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
setZoomAround: function setZoomAround(latlng, zoom, options) {
var scale = this.getZoomScale(zoom),
viewHalf = this.getSize().divideBy(2),
containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
return this.setView(newCenter, zoom, {
zoom: options
});
},
_getBoundsCenterZoom: function _getBoundsCenterZoom(bounds, options) {
options = options || {};
bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
zoom = typeof options.maxZoom === 'number' ? Math.min(options.maxZoom, zoom) : zoom;
if (zoom === Infinity) {
return {
center: bounds.getCenter(),
zoom: zoom
};
}
var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
swPoint = this.project(bounds.getSouthWest(), zoom),
nePoint = this.project(bounds.getNorthEast(), zoom),
center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
return {
center: center,
zoom: zoom
};
},
// @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
// Sets a map view that contains the given geographical bounds with the
// maximum zoom level possible.
fitBounds: function fitBounds(bounds, options) {
bounds = toLatLngBounds(bounds);
if (!bounds.isValid()) {
throw new Error('Bounds are not valid.');
}
var target = this._getBoundsCenterZoom(bounds, options);
return this.setView(target.center, target.zoom, options);
},
// @method fitWorld(options?: fitBounds options): this
// Sets a map view that mostly contains the whole world with the maximum
// zoom level possible.
fitWorld: function fitWorld(options) {
return this.fitBounds([[-90, -180], [90, 180]], options);
},
// @method panTo(latlng: LatLng, options?: Pan options): this
// Pans the map to a given center.
panTo: function panTo(center, options) {
// (LatLng)
return this.setView(center, this._zoom, {
pan: options
});
},
// @method panBy(offset: Point, options?: Pan options): this
// Pans the map by a given number of pixels (animated).
panBy: function panBy(offset, options) {
offset = toPoint(offset).round();
options = options || {};
if (!offset.x && !offset.y) {
return this.fire('moveend');
}
// If we pan too far, Chrome gets issues with tiles
// and makes them disappear or appear in the wrong place (slightly offset) #2602
if (options.animate !== true && !this.getSize().contains(offset)) {
this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
return this;
}
if (!this._panAnim) {
this._panAnim = new PosAnimation();
this._panAnim.on({
'step': this._onPanTransitionStep,
'end': this._onPanTransitionEnd
}, this);
}
// don't fire movestart if animating inertia
if (!options.noMoveStart) {
this.fire('movestart');
}
// animate pan unless animate: false specified
if (options.animate !== false) {
addClass(this._mapPane, 'leaflet-pan-anim');
var newPos = this._getMapPanePos().subtract(offset).round();
this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
} else {
this._rawPanBy(offset);
this.fire('move').fire('moveend');
}
return this;
},
// @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
// Sets the view of the map (geographical center and zoom) performing a smooth
// pan-zoom animation.
flyTo: function flyTo(targetCenter, targetZoom, options) {
options = options || {};
if (options.animate === false || !any3d) {
return this.setView(targetCenter, targetZoom, options);
}
this._stop();
var from = this.project(this.getCenter()),
to = this.project(targetCenter),
size = this.getSize(),
startZoom = this._zoom;
targetCenter = toLatLng(targetCenter);
targetZoom = targetZoom === undefined ? startZoom : targetZoom;
var w0 = Math.max(size.x, size.y),
w1 = w0 * this.getZoomScale(startZoom, targetZoom),
u1 = to.distanceTo(from) || 1,
rho = 1.42,
rho2 = rho * rho;
function r(i) {
var s1 = i ? -1 : 1,
s2 = i ? w1 : w0,
t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
b1 = 2 * s2 * rho2 * u1,
b = t1 / b1,
sq = Math.sqrt(b * b + 1) - b;
// workaround for floating point precision bug when sq = 0, log = -Infinite,
// thus triggering an infinite loop in flyTo
var log = sq < 0.000000001 ? -18 : Math.log(sq);
return log;
}
function sinh(n) {
return (Math.exp(n) - Math.exp(-n)) / 2;
}
function cosh(n) {
return (Math.exp(n) + Math.exp(-n)) / 2;
}
function tanh(n) {
return sinh(n) / cosh(n);
}
var r0 = r(0);
function w(s) {
return w0 * (cosh(r0) / cosh(r0 + rho * s));
}
function u(s) {
return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2;
}
function easeOut(t) {
return 1 - Math.pow(1 - t, 1.5);
}
var start = Date.now(),
S = (r(1) - r0) / rho,
duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
function frame() {
var t = (Date.now() - start) / duration,
s = easeOut(t) * S;
if (t <= 1) {
this._flyToFrame = requestAnimFrame(frame, this);
this._move(this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), this.getScaleZoom(w0 / w(s), startZoom), {
flyTo: true
});
} else {
this._move(targetCenter, targetZoom)._moveEnd(true);
}
}
this._moveStart(true, options.noMoveStart);
frame.call(this);
return this;
},
// @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
// Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
// but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
flyToBounds: function flyToBounds(bounds, options) {
var target = this._getBoundsCenterZoom(bounds, options);
return this.flyTo(target.center, target.zoom, options);
},
// @method setMaxBounds(bounds: Bounds): this
// Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
setMaxBounds: function setMaxBounds(bounds) {
bounds = toLatLngBounds(bounds);
if (!bounds.isValid()) {
this.options.maxBounds = null;
return this.off('moveend', this._panInsideMaxBounds);
} else if (this.options.maxBounds) {
this.off('moveend', this._panInsideMaxBounds);
}
this.options.maxBounds = bounds;
if (this._loaded) {
this._panInsideMaxBounds();
}
return this.on('moveend', this._panInsideMaxBounds);
},
// @method setMinZoom(zoom: Number): this
// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
setMinZoom: function setMinZoom(zoom) {
var oldZoom = this.options.minZoom;
this.options.minZoom = zoom;
if (this._loaded && oldZoom !== zoom) {
this.fire('zoomlevelschange');
if (this.getZoom() < this.options.minZoom) {
return this.setZoom(zoom);
}
}
return this;
},
// @method setMaxZoom(zoom: Number): this
// Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
setMaxZoom: function setMaxZoom(zoom) {
var oldZoom = this.options.maxZoom;
this.options.maxZoom = zoom;
if (this._loaded && oldZoom !== zoom) {
this.fire('zoomlevelschange');
if (this.getZoom() > this.options.maxZoom) {
return this.setZoom(zoom);
}
}
return this;
},
// @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
// Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
panInsideBounds: function panInsideBounds(bounds, options) {
this._enforcingBounds = true;
var center = this.getCenter(),
newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
if (!center.equals(newCenter)) {
this.panTo(newCenter, options);
}
this._enforcingBounds = false;
return this;
},
// @method panInside(latlng: LatLng, options?: options): this
// Pans the map the minimum amount to make the `latlng` visible. Use
// `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
// the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
// If `latlng` is already within the (optionally padded) display bounds,
// the map will not be panned.
panInside: function panInside(latlng, options) {
options = options || {};
var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
center = this.getCenter(),
pixelCenter = this.project(center),
pixelPoint = this.project(latlng),
pixelBounds = this.getPixelBounds(),
halfPixelBounds = pixelBounds.getSize().divideBy(2),
paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
if (!paddedBounds.contains(pixelPoint)) {
this._enforcingBounds = true;
var diff = pixelCenter.subtract(pixelPoint),
newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
newCenter.x = pixelCenter.x - diff.x;
if (diff.x > 0) {
newCenter.x += halfPixelBounds.x - paddingTL.x;
} else {
newCenter.x -= halfPixelBounds.x - paddingBR.x;
}
}
if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
newCenter.y = pixelCenter.y - diff.y;
if (diff.y > 0) {
newCenter.y += halfPixelBounds.y - paddingTL.y;
} else {
newCenter.y -= halfPixelBounds.y - paddingBR.y;
}
}
this.panTo(this.unproject(newCenter), options);
this._enforcingBounds = false;
}
return this;
},
// @method invalidateSize(options: Zoom/pan options): this
// Checks if the map container size changed and updates the map if so —
// call it after you've changed the map size dynamically, also animating
// pan by default. If `options.pan` is `false`, panning will not occur.
// If `options.debounceMoveend` is `true`, it will delay `moveend` event so
// that it doesn't happen often even if the method is called many
// times in a row.
// @alternative
// @method invalidateSize(animate: Boolean): this
// Checks if the map container size changed and updates the map if so —
// call it after you've changed the map size dynamically, also animating
// pan by default.
invalidateSize: function invalidateSize(options) {
if (!this._loaded) {
return this;
}
options = extend({
animate: false,
pan: true
}, options === true ? {
animate: true
} : options);
var oldSize = this.getSize();
this._sizeChanged = true;
this._lastCenter = null;
var newSize = this.getSize(),
oldCenter = oldSize.divideBy(2).round(),
newCenter = newSize.divideBy(2).round(),
offset = oldCenter.subtract(newCenter);
if (!offset.x && !offset.y) {
return this;
}
if (options.animate && options.pan) {
this.panBy(offset);
} else {
if (options.pan) {
this._rawPanBy(offset);
}
this.fire('move');
if (options.debounceMoveend) {
clearTimeout(this._sizeTimer);
this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
} else {
this.fire('moveend');
}
}
// @section Map state change events
// @event resize: ResizeEvent
// Fired when the map is resized.
return this.fire('resize', {
oldSize: oldSize,
newSize: newSize
});
},
// @section Methods for modifying map state
// @method stop(): this
// Stops the currently running `panTo` or `flyTo` animation, if any.
stop: function stop() {
this.setZoom(this._limitZoom(this._zoom));
if (!this.options.zoomSnap) {
this.fire('viewreset');
}
return this._stop();
},
// @section Geolocation methods
// @method locate(options?: Locate options): this
// Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
// event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
// and optionally sets the map view to the user's location with respect to
// detection accuracy (or to the world view if geolocation failed).
// Note that, if your page doesn't use HTTPS, this method will fail in
// modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
// See `Locate options` for more details.
locate: function locate(options) {
options = this._locateOptions = extend({
timeout: 10000,
watch: false
// setView: false
// maxZoom: <Number>
// maximumAge: 0
// enableHighAccuracy: false
}, options);
if (!('geolocation' in navigator)) {
this._handleGeolocationError({
code: 0,
message: 'Geolocation not supported.'
});
return this;
}
var onResponse = bind(this._handleGeolocationResponse, this),
onError = bind(this._handleGeolocationError, this);
if (options.watch) {
this._locationWatchId = navigator.geolocation.watchPosition(onResponse, onError, options);
} else {
navigator.geolocation.getCurrentPosition(onResponse, onError, options);
}
return this;
},
// @method stopLocate(): this
// Stops watching location previously initiated by `map.locate({watch: true})`
// and aborts resetting the map view if map.locate was called with
// `{setView: true}`.
stopLocate: function stopLocate() {
if (navigator.geolocation && navigator.geolocation.clearWatch) {
navigator.geolocation.clearWatch(this._locationWatchId);
}
if (this._locateOptions) {
this._locateOptions.setView = false;
}
return this;
},
_handleGeolocationError: function _handleGeolocationError(error) {
var c = error.code,
message = error.message || (c === 1 ? 'permission denied' : c === 2 ? 'position unavailable' : 'timeout');
if (this._locateOptions.setView && !this._loaded) {
this.fitWorld();
}
// @section Location events
// @event locationerror: ErrorEvent
// Fired when geolocation (using the [`locate`](#map-locate) method) failed.
this.fire('locationerror', {
code: c,
message: 'Geolocation error: ' + message + '.'
});
},
_handleGeolocationResponse: function _handleGeolocationResponse(pos) {
var lat = pos.coords.latitude,
lng = pos.coords.longitude,
latlng = new LatLng(lat, lng),
bounds = latlng.toBounds(pos.coords.accuracy * 2),
options = this._locateOptions;
if (options.setView) {
var zoom = this.getBoundsZoom(bounds);
this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
}
var data = {
latlng: latlng,
bounds: bounds,
timestamp: pos.timestamp
};
for (var i in pos.coords) {
if (typeof pos.coords[i] === 'number') {
data[i] = pos.coords[i];
}
}
// @event locationfound: LocationEvent
// Fired when geolocation (using the [`locate`](#map-locate) method)
// went successfully.
this.fire('locationfound', data);
},
// TODO Appropriate docs section?
// @section Other Methods
// @method addHandler(name: String, HandlerClass: Function): this
// Adds a new `Handler` to the map, given its name and constructor function.
addHandler: function addHandler(name, HandlerClass) {
if (!HandlerClass) {
return this;
}
var handler = this[name] = new HandlerClass(this);
this._handlers.push(handler);
if (this.options[name]) {
handler.enable();
}
return this;
},
// @method remove(): this
// Destroys the map and clears all related event listeners.
remove: function remove() {
this._initEvents(true);
if (this._containerId !== this._container._leaflet_id) {
throw new Error('Map container is being reused by another instance');
}
try {
// throws error in IE6-8
delete this._container._leaflet_id;
delete this._containerId;
} catch (e) {
/*eslint-disable */
this._container._leaflet_id = undefined;
/* eslint-enable */
this._containerId = undefined;
}
if (this._locationWatchId !== undefined) {
this.stopLocate();
}
this._stop();
_remove(this._mapPane);
if (this._clearControlPos) {
this._clearControlPos();
}
if (this._resizeRequest) {
cancelAnimFrame(this._resizeRequest);
this._resizeRequest = null;
}
this._clearHandlers();
if (this._loaded) {
// @section Map state change events
// @event unload: Event
// Fired when the map is destroyed with [remove](#map-remove) method.
this.fire('unload');
}
var i;
for (i in this._layers) {
this._layers[i].remove();
}
for (i in this._panes) {
_remove(this._panes[i]);
}
this._layers = [];
this._panes = [];
delete this._mapPane;
delete this._renderer;
return this;
},
// @section Other Methods
// @method createPane(name: String, container?: HTMLElement): HTMLElement
// Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
// then returns it. The pane is created as a child of `container`, or
// as a child of the main map pane if not set.
createPane: function createPane(name, container) {
var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
pane = create$1('div', className, container || this._mapPane);
if (name) {
this._panes[name] = pane;
}
return pane;
},
// @section Methods for Getting Map State
// @method getCenter(): LatLng
// Returns the geographical center of the map view
getCenter: function getCenter() {
this._checkIfLoaded();
if (this._lastCenter && !this._moved()) {
return this._lastCenter;
}
return this.layerPointToLatLng(this._getCenterLayerPoint());
},
// @method getZoom(): Number
// Returns the current zoom level of the map view
getZoom: function getZoom() {
return this._zoom;
},
// @method getBounds(): LatLngBounds
// Returns the geographical bounds visible in the current map view
getBounds: function getBounds() {
var bounds = this.getPixelBounds(),
sw = this.unproject(bounds.getBottomLeft()),
ne = this.unproject(bounds.getTopRight());
return new LatLngBounds(sw, ne);
},
// @method getMinZoom(): Number
// Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
getMinZoom: function getMinZoom() {
return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
},
// @method getMaxZoom(): Number
// Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
getMaxZoom: function getMaxZoom() {
return this.options.maxZoom === undefined ? this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom : this.options.maxZoom;
},
// @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
// Returns the maximum zoom level on which the given bounds fit to the map
// view in its entirety. If `inside` (optional) is set to `true`, the method
// instead returns the minimum zoom level on which the map view fits into
// the given bounds in its entirety.
getBoundsZoom: function getBoundsZoom(bounds, inside, padding) {
// (LatLngBounds[, Boolean, Point]) -> Number
bounds = toLatLngBounds(bounds);
padding = toPoint(padding || [0, 0]);
var zoom = this.getZoom() || 0,
min = this.getMinZoom(),
max = this.getMaxZoom(),
nw = bounds.getNorthWest(),
se = bounds.getSouthEast(),
size = this.getSize().subtract(padding),
boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
snap = any3d ? this.options.zoomSnap : 1,
scalex = size.x / boundsSize.x,
scaley = size.y / boundsSize.y,
scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
zoom = this.getScaleZoom(scale, zoom);
if (snap) {
zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
}
return Math.max(min, Math.min(max, zoom));
},
// @method getSize(): Point
// Returns the current size of the map container (in pixels).
getSize: function getSize() {
if (!this._size || this._sizeChanged) {
this._size = new Point(this._container.clientWidth || 0, this._container.clientHeight || 0);
this._sizeChanged = false;
}
return this._size.clone();
},
// @method getPixelBounds(): Bounds
// Returns the bounds of the current map view in projected pixel
// coordinates (sometimes useful in layer and overlay implementations).
getPixelBounds: function getPixelBounds(center, zoom) {
var topLeftPoint = this._getTopLeftPoint(center, zoom);
return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
},
// TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
// the map pane? "left point of the map layer" can be confusing, specially
// since there can be negative offsets.
// @method getPixelOrigin(): Point
// Returns the projected pixel coordinates of the top left point of
// the map layer (useful in custom layer and overlay implementations).
getPixelOrigin: function getPixelOrigin() {
this._checkIfLoaded();
return this._pixelOrigin;
},
// @method getPixelWorldBounds(zoom?: Number): Bounds
// Returns the world's bounds in pixel coordinates for zoom level `zoom`.
// If `zoom` is omitted, the map's current zoom level is used.
getPixelWorldBounds: function getPixelWorldBounds(zoom) {
return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
},
// @section Other Methods
// @method getPane(pane: String|HTMLElement): HTMLElement
// Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
getPane: function getPane(pane) {
return typeof pane === 'string' ? this._panes[pane] : pane;
},
// @method getPanes(): Object
// Returns a plain object containing the names of all [panes](#map-pane) as keys and
// the panes as values.
getPanes: function getPanes() {
return this._panes;
},
// @method getContainer: HTMLElement
// Returns the HTML element that contains the map.
getContainer: function getContainer() {
return this._container;
},
// @section Conversion Methods
// @method getZoomScale(toZoom: Number, fromZoom: Number): Number
// Returns the scale factor to be applied to a map transition from zoom level
// `fromZoom` to `toZoom`. Used internally to help with zoom animations.
getZoomScale: function getZoomScale(toZoom, fromZoom) {
// TODO replace with universal implementation after refactoring projections
var crs = this.options.crs;
fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
return crs.scale(toZoom) / crs.scale(fromZoom);
},
// @method getScaleZoom(scale: Number, fromZoom: Number): Number
// Returns the zoom level that the map would end up at, if it is at `fromZoom`
// level and everything is scaled by a factor of `scale`. Inverse of
// [`getZoomScale`](#map-getZoomScale).
getScaleZoom: function getScaleZoom(scale, fromZoom) {
var crs = this.options.crs;
fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
var zoom = crs.zoom(scale * crs.scale(fromZoom));
return isNaN(zoom) ? Infinity : zoom;
},
// @method project(latlng: LatLng, zoom: Number): Point
// Projects a geographical coordinate `LatLng` according to the projection
// of the map's CRS, then scales it according to `zoom` and the CRS's
// `Transformation`. The result is pixel coordinate relative to
// the CRS origin.
project: function project(latlng, zoom) {
zoom = zoom === undefined ? this._zoom : zoom;
return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
},
// @method unproject(point: Point, zoom: Number): LatLng
// Inverse of [`project`](#map-project).
unproject: function unproject(point, zoom) {
zoom = zoom === undefined ? this._zoom : zoom;
return this.options.crs.pointToLatLng(toPoint(point), zoom);
},
// @method layerPointToLatLng(point: Point): LatLng
// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
// returns the corresponding geographical coordinate (for the current zoom level).
layerPointToLatLng: function layerPointToLatLng(point) {
var projectedPoint = toPoint(point).add(this.getPixelOrigin());
return this.unproject(projectedPoint);
},
// @method latLngToLayerPoint(latlng: LatLng): Point
// Given a geographical coordinate, returns the corresponding pixel coordinate
// relative to the [origin pixel](#map-getpixelorigin).
latLngToLayerPoint: function latLngToLayerPoint(latlng) {
var projectedPoint = this.project(toLatLng(latlng))._round();
return projectedPoint._subtract(this.getPixelOrigin());
},
// @method wrapLatLng(latlng: LatLng): LatLng
// Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
// map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
// CRS's bounds.
// By default this means longitude is wrapped around the dateline so its
// value is between -180 and +180 degrees.
wrapLatLng: function wrapLatLng(latlng) {
return this.options.crs.wrapLatLng(toLatLng(latlng));
},
// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
// Returns a `LatLngBounds` with the same size as the given one, ensuring that
// its center is within the CRS's bounds.
// By default this means the center longitude is wrapped around the dateline so its
// value is between -180 and +180 degrees, and the majority of the bounds
// overlaps the CRS's bounds.
wrapLatLngBounds: function wrapLatLngBounds(latlng) {
return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
},
// @method distance(latlng1: LatLng, latlng2: LatLng): Number
// Returns the distance between two geographical coordinates according to
// the map's CRS. By default this measures distance in meters.
distance: function distance(latlng1, latlng2) {
return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
},
// @method containerPointToLayerPoint(point: Point): Point
// Given a pixel coordinate relative to the map container, returns the corresponding
// pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
containerPointToLayerPoint: function containerPointToLayerPoint(point) {
// (Point)
return toPoint(point).subtract(this._getMapPanePos());
},
// @method layerPointToContainerPoint(point: Point): Point
// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
// returns the corresponding pixel coordinate relative to the map container.
layerPointToContainerPoint: function layerPointToContainerPoint(point) {
// (Point)
return toPoint(point).add(this._getMapPanePos());
},
// @method containerPointToLatLng(point: Point): LatLng
// Given a pixel coordinate relative to the map container, returns
// the corresponding geographical coordinate (for the current zoom level).
containerPointToLatLng: function containerPointToLatLng(point) {
var layerPoint = this.containerPointToLayerPoint(toPoint(point));
return this.layerPointToLatLng(layerPoint);
},
// @method latLngToContainerPoint(latlng: LatLng): Point
// Given a geographical coordinate, returns the corresponding pixel coordinate
// relative to the map container.
latLngToContainerPoint: function latLngToContainerPoint(latlng) {
return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
},
// @method mouseEventToContainerPoint(ev: MouseEvent): Point
// Given a MouseEvent object, returns the pixel coordinate relative to the
// map container where the event took place.
mouseEventToContainerPoint: function mouseEventToContainerPoint(e) {
return getMousePosition(e, this._container);
},
// @method mouseEventToLayerPoint(ev: MouseEvent): Point
// Given a MouseEvent object, returns the pixel coordinate relative to
// the [origin pixel](#map-getpixelorigin) where the event took place.
mouseEventToLayerPoint: function mouseEventToLayerPoint(e) {
return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
},
// @method mouseEventToLatLng(ev: MouseEvent): LatLng
// Given a MouseEvent object, returns geographical coordinate where the
// event took place.
mouseEventToLatLng: function mouseEventToLatLng(e) {
// (MouseEvent)
return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
},
// map initialization methods
_initContainer: function _initContainer(id) {
var container = this._container = get(id);
if (!container) {
throw new Error('Map container not found.');
} else if (container._leaflet_id) {
throw new Error('Map container is already initialized.');
}
on(container, 'scroll', this._onScroll, this);
this._containerId = stamp(container);
},
_initLayout: function _initLayout() {
var container = this._container;
this._fadeAnimated = this.options.fadeAnimation && any3d;
addClass(container, 'leaflet-container' + (touch ? ' leaflet-touch' : '') + (retina ? ' leaflet-retina' : '') + (ielt9 ? ' leaflet-oldie' : '') + (safari ? ' leaflet-safari' : '') + (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
var position = getStyle(container, 'position');
if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
container.style.position = 'relative';
}
this._initPanes();
if (this._initControlPos) {
this._initControlPos();
}
},
_initPanes: function _initPanes() {
var panes = this._panes = {};
this._paneRenderers = {};
// @section
//
// Panes are DOM elements used to control the ordering of layers on the map. You
// can access panes with [`map.getPane`](#map-getpane) or
// [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
// [`map.createPane`](#map-createpane) method.
//
// Every map has the following default panes that differ only in zIndex.
//
// @pane mapPane: HTMLElement = 'auto'
// Pane that contains all other map panes
this._mapPane = this.createPane('mapPane', this._container);
setPosition(this._mapPane, new Point(0, 0));
// @pane tilePane: HTMLElement = 200
// Pane for `GridLayer`s and `TileLayer`s
this.createPane('tilePane');
// @pane overlayPane: HTMLElement = 400
// Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
this.createPane('shadowPane');
// @pane shadowPane: HTMLElement = 500
// Pane for overlay shadows (e.g. `Marker` shadows)
this.createPane('overlayPane');
// @pane markerPane: HTMLElement = 600
// Pane for `Icon`s of `Marker`s
this.createPane('markerPane');
// @pane tooltipPane: HTMLElement = 650
// Pane for `Tooltip`s.
this.createPane('tooltipPane');
// @pane popupPane: HTMLElement = 700
// Pane for `Popup`s.
this.createPane('popupPane');
if (!this.options.markerZoomAnimation) {
addClass(panes.markerPane, 'leaflet-zoom-hide');
addClass(panes.shadowPane, 'leaflet-zoom-hide');
}
},
// private methods that modify map state
// @section Map state change events
_resetView: function _resetView(center, zoom) {
setPosition(this._mapPane, new Point(0, 0));
var loading = !this._loaded;
this._loaded = true;
zoom = this._limitZoom(zoom);
this.fire('viewprereset');
var zoomChanged = this._zoom !== zoom;
this._moveStart(zoomChanged, false)._move(center, zoom)._moveEnd(zoomChanged);
// @event viewreset: Event
// Fired when the map needs to redraw its content (this usually happens
// on map zoom or load). Very useful for creating custom overlays.
this.fire('viewreset');
// @event load: Event
// Fired when the map is initialized (when its center and zoom are set
// for the first time).
if (loading) {
this.fire('load');
}
},
_moveStart: function _moveStart(zoomChanged, noMoveStart) {
// @event zoomstart: Event
// Fired when the map zoom is about to change (e.g. before zoom animation).
// @event movestart: Event
// Fired when the view of the map starts changing (e.g. user starts dragging the map).
if (zoomChanged) {
this.fire('zoomstart');
}
if (!noMoveStart) {
this.fire('movestart');
}
return this;
},
_move: function _move(center, zoom, data) {
if (zoom === undefined) {
zoom = this._zoom;
}
var zoomChanged = this._zoom !== zoom;
this._zoom = zoom;
this._lastCenter = center;
this._pixelOrigin = this._getNewPixelOrigin(center);
// @event zoom: Event
// Fired repeatedly during any change in zoom level, including zoom
// and fly animations.
if (zoomChanged || data && data.pinch) {
// Always fire 'zoom' if pinching because #3530
this.fire('zoom', data);
}
// @event move: Event
// Fired repeatedly during any movement of the map, including pan and
// fly animations.
return this.fire('move', data);
},
_moveEnd: function _moveEnd(zoomChanged) {
// @event zoomend: Event
// Fired when the map has changed, after any animations.
if (zoomChanged) {
this.fire('zoomend');
}
// @event moveend: Event
// Fired when the center of the map stops changing (e.g. user stopped
// dragging the map).
return this.fire('moveend');
},
_stop: function _stop() {
cancelAnimFrame(this._flyToFrame);
if (this._panAnim) {
this._panAnim.stop();
}
return this;
},
_rawPanBy: function _rawPanBy(offset) {
setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
},
_getZoomSpan: function _getZoomSpan() {
return this.getMaxZoom() - this.getMinZoom();
},
_panInsideMaxBounds: function _panInsideMaxBounds() {
if (!this._enforcingBounds) {
this.panInsideBounds(this.options.maxBounds);
}
},
_checkIfLoaded: function _checkIfLoaded() {
if (!this._loaded) {
throw new Error('Set map center and zoom first.');
}
},
// DOM event handling
// @section Interaction events
_initEvents: function _initEvents(remove$$1) {
this._targets = {};
this._targets[stamp(this._container)] = this;
var onOff = remove$$1 ? off : on;
// @event click: MouseEvent
// Fired when the user clicks (or taps) the map.
// @event dblclick: MouseEvent
// Fired when the user double-clicks (or double-taps) the map.
// @event mousedown: MouseEvent
// Fired when the user pushes the mouse button on the map.
// @event mouseup: MouseEvent
// Fired when the user releases the mouse button on the map.
// @event mouseover: MouseEvent
// Fired when the mouse enters the map.
// @event mouseout: MouseEvent
// Fired when the mouse leaves the map.
// @event mousemove: MouseEvent
// Fired while the mouse moves over the map.
// @event contextmenu: MouseEvent
// Fired when the user pushes the right mouse button on the map, prevents
// default browser context menu from showing if there are listeners on
// this event. Also fired on mobile when the user holds a single touch
// for a second (also called long press).
// @event keypress: KeyboardEvent
// Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
// @event keydown: KeyboardEvent
// Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
// the `keydown` event is fired for keys that produce a character value and for keys
// that do not produce a character value.
// @event keyup: KeyboardEvent
// Fired when the user releases a key from the keyboard while the map is focused.
onOff(this._container, 'click dblclick mousedown mouseup ' + 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
if (this.options.trackResize) {
onOff(window, 'resize', this._onResize, this);
}
if (any3d && this.options.transform3DLimit) {
(remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
}
},
_onResize: function _onResize() {
cancelAnimFrame(this._resizeRequest);
this._resizeRequest = requestAnimFrame(function () {
this.invalidateSize({
debounceMoveend: true
});
}, this);
},
_onScroll: function _onScroll() {
this._container.scrollTop = 0;
this._container.scrollLeft = 0;
},
_onMoveEnd: function _onMoveEnd() {
var pos = this._getMapPanePos();
if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
// https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
// a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
this._resetView(this.getCenter(), this.getZoom());
}
},
_findEventTargets: function _findEventTargets(e, type) {
var targets = [],
target,
isHover = type === 'mouseout' || type === 'mouseover',
src = e.target || e.srcElement,
dragging = false;
while (src) {
target = this._targets[stamp(src)];
if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
// Prevent firing click after you just dragged an object.
dragging = true;
break;
}
if (target && target.listens(type, true)) {
if (isHover && !isExternalTarget(src, e)) {
break;
}
targets.push(target);
if (isHover) {
break;
}
}
if (src === this._container) {
break;
}
src = src.parentNode;
}
if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
targets = [this];
}
return targets;
},
_handleDOMEvent: function _handleDOMEvent(e) {
if (!this._loaded || skipped(e)) {
return;
}
var type = e.type;
if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
// prevents outline when clicking on keyboard-focusable element
preventOutline(e.target || e.srcElement);
}
this._fireDOMEvent(e, type);
},
_mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
_fireDOMEvent: function _fireDOMEvent(e, type, targets) {
if (e.type === 'click') {
// Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
// @event preclick: MouseEvent
// Fired before mouse click on the map (sometimes useful when you
// want something to happen on click before any existing click
// handlers start running).
var synth = extend({}, e);
synth.type = 'preclick';
this._fireDOMEvent(synth, synth.type, targets);
}
if (e._stopped) {
return;
}
// Find the layer the event is propagating from and its parents.
targets = (targets || []).concat(this._findEventTargets(e, type));
if (!targets.length) {
return;
}
var target = targets[0];
if (type === 'contextmenu' && target.listens(type, true)) {
preventDefault(e);
}
var data = {
originalEvent: e
};
if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
data.containerPoint = isMarker ? this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
}
for (var i = 0; i < targets.length; i++) {
targets[i].fire(type, data, true);
if (data.originalEvent._stopped || targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1) {
return;
}
}
},
_draggableMoved: function _draggableMoved(obj) {
obj = obj.dragging && obj.dragging.enabled() ? obj : this;
return obj.dragging && obj.dragging.moved() || this.boxZoom && this.boxZoom.moved();
},
_clearHandlers: function _clearHandlers() {
for (var i = 0, len = this._handlers.length; i < len; i++) {
this._handlers[i].disable();
}
},
// @section Other Methods
// @method whenReady(fn: Function, context?: Object): this
// Runs the given function `fn` when the map gets initialized with
// a view (center and zoom) and at least one layer, or immediately
// if it's already initialized, optionally passing a function context.
whenReady: function whenReady(callback, context) {
if (this._loaded) {
callback.call(context || this, {
target: this
});
} else {
this.on('load', callback, context);
}
return this;
},
// private methods for getting map state
_getMapPanePos: function _getMapPanePos() {
return getPosition(this._mapPane) || new Point(0, 0);
},
_moved: function _moved() {
var pos = this._getMapPanePos();
return pos && !pos.equals([0, 0]);
},
_getTopLeftPoint: function _getTopLeftPoint(center, zoom) {
var pixelOrigin = center && zoom !== undefined ? this._getNewPixelOrigin(center, zoom) : this.getPixelOrigin();
return pixelOrigin.subtract(this._getMapPanePos());
},
_getNewPixelOrigin: function _getNewPixelOrigin(center, zoom) {
var viewHalf = this.getSize()._divideBy(2);
return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
},
_latLngToNewLayerPoint: function _latLngToNewLayerPoint(latlng, zoom, center) {
var topLeft = this._getNewPixelOrigin(center, zoom);
return this.project(latlng, zoom)._subtract(topLeft);
},
_latLngBoundsToNewLayerBounds: function _latLngBoundsToNewLayerBounds(latLngBounds, zoom, center) {
var topLeft = this._getNewPixelOrigin(center, zoom);
return toBounds([this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)]);
},
// layer point of the current center
_getCenterLayerPoint: function _getCenterLayerPoint() {
return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
},
// offset of the specified place to the current center in pixels
_getCenterOffset: function _getCenterOffset(latlng) {
return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
},
// adjust center for view to get inside bounds
_limitCenter: function _limitCenter(center, zoom, bounds) {
if (!bounds) {
return center;
}
var centerPoint = this.project(center, zoom),
viewHalf = this.getSize().divideBy(2),
viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
offset = this._getBoundsOffset(viewBounds, bounds, zoom);
// If offset is less than a pixel, ignore.
// This prevents unstable projections from getting into
// an infinite loop of tiny offsets.
if (offset.round().equals([0, 0])) {
return center;
}
return this.unproject(centerPoint.add(offset), zoom);
},
// adjust offset for view to get inside bounds
_limitOffset: function _limitOffset(offset, bounds) {
if (!bounds) {
return offset;
}
var viewBounds = this.getPixelBounds(),
newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
return offset.add(this._getBoundsOffset(newBounds, bounds));
},
// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
_getBoundsOffset: function _getBoundsOffset(pxBounds, maxBounds, zoom) {
var projectedMaxBounds = toBounds(this.project(maxBounds.getNorthEast(), zoom), this.project(maxBounds.getSouthWest(), zoom)),
minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
dx = this._rebound(minOffset.x, -maxOffset.x),
dy = this._rebound(minOffset.y, -maxOffset.y);
return new Point(dx, dy);
},
_rebound: function _rebound(left, right) {
return left + right > 0 ? Math.round(left - right) / 2 : Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
},
_limitZoom: function _limitZoom(zoom) {
var min = this.getMinZoom(),
max = this.getMaxZoom(),
snap = any3d ? this.options.zoomSnap : 1;
if (snap) {
zoom = Math.round(zoom / snap) * snap;
}
return Math.max(min, Math.min(max, zoom));
},
_onPanTransitionStep: function _onPanTransitionStep() {
this.fire('move');
},
_onPanTransitionEnd: function _onPanTransitionEnd() {
removeClass(this._mapPane, 'leaflet-pan-anim');
this.fire('moveend');
},
_tryAnimatedPan: function _tryAnimatedPan(center, options) {
// difference between the new and current centers in pixels
var offset = this._getCenterOffset(center)._trunc();
// don't animate too far unless animate: true specified in options
if ((options && options.animate) !== true && !this.getSize().contains(offset)) {
return false;
}
this.panBy(offset, options);
return true;
},
_createAnimProxy: function _createAnimProxy() {
var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
this._panes.mapPane.appendChild(proxy);
this.on('zoomanim', function (e) {
var prop = TRANSFORM,
transform = this._proxy.style[prop];
setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
// workaround for case when transform is the same and so transitionend event is not fired
if (transform === this._proxy.style[prop] && this._animatingZoom) {
this._onZoomTransitionEnd();
}
}, this);
this.on('load moveend', this._animMoveEnd, this);
this._on('unload', this._destroyAnimProxy, this);
},
_destroyAnimProxy: function _destroyAnimProxy() {
_remove(this._proxy);
this.off('load moveend', this._animMoveEnd, this);
delete this._proxy;
},
_animMoveEnd: function _animMoveEnd() {
var c = this.getCenter(),
z = this.getZoom();
setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
},
_catchTransitionEnd: function _catchTransitionEnd(e) {
if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
this._onZoomTransitionEnd();
}
},
_nothingToAnimate: function _nothingToAnimate() {
return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
},
_tryAnimatedZoom: function _tryAnimatedZoom(center, zoom, options) {
if (this._animatingZoom) {
return true;
}
options = options || {};
// don't animate if disabled, not supported or zoom difference is too large
if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) {
return false;
}
// offset is the pixel coords of the zoom origin relative to the current center
var scale = this.getZoomScale(zoom),
offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
// don't animate if the zoom origin isn't within one screen from the current center, unless forced
if (options.animate !== true && !this.getSize().contains(offset)) {
return false;
}
requestAnimFrame(function () {
this._moveStart(true, false)._animateZoom(center, zoom, true);
}, this);
return true;
},
_animateZoom: function _animateZoom(center, zoom, startAnim, noUpdate) {
if (!this._mapPane) {
return;
}
if (startAnim) {
this._animatingZoom = true;
// remember what center/zoom to set after animation
this._animateToCenter = center;
this._animateToZoom = zoom;
addClass(this._mapPane, 'leaflet-zoom-anim');
}
// @section Other Events
// @event zoomanim: ZoomAnimEvent
// Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
this.fire('zoomanim', {
center: center,
zoom: zoom,
noUpdate: noUpdate
});
// Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
setTimeout(bind(this._onZoomTransitionEnd, this), 250);
},
_onZoomTransitionEnd: function _onZoomTransitionEnd() {
if (!this._animatingZoom) {
return;
}
if (this._mapPane) {
removeClass(this._mapPane, 'leaflet-zoom-anim');
}
this._animatingZoom = false;
this._move(this._animateToCenter, this._animateToZoom);
// This anim frame should prevent an obscure iOS webkit tile loading race condition.
requestAnimFrame(function () {
this._moveEnd(true);
}, this);
}
});
// @section
// @factory L.map(id: String, options?: Map options)
// Instantiates a map object given the DOM ID of a `<div>` element
// and optionally an object literal with `Map options`.
//
// @alternative
// @factory L.map(el: HTMLElement, options?: Map options)
// Instantiates a map object given an instance of a `<div>` HTML element
// and optionally an object literal with `Map options`.
function createMap(id, options) {
return new Map(id, options);
}
/*
* @class Control
* @aka L.Control
* @inherits Class
*
* L.Control is a base class for implementing map controls. Handles positioning.
* All other controls extend from this class.
*/
var Control = Class.extend({
// @section
// @aka Control options
options: {
// @option position: String = 'topright'
// The position of the control (one of the map corners). Possible values are `'topleft'`,
// `'topright'`, `'bottomleft'` or `'bottomright'`
position: 'topright'
},
initialize: function initialize(options) {
setOptions(this, options);
},
/* @section
* Classes extending L.Control will inherit the following methods:
*
* @method getPosition: string
* Returns the position of the control.
*/
getPosition: function getPosition() {
return this.options.position;
},
// @method setPosition(position: string): this
// Sets the position of the control.
setPosition: function setPosition(position) {
var map = this._map;
if (map) {
map.removeControl(this);
}
this.options.position = position;
if (map) {
map.addControl(this);
}
return this;
},
// @method getContainer: HTMLElement
// Returns the HTMLElement that contains the control.
getContainer: function getContainer() {
return this._container;
},
// @method addTo(map: Map): this
// Adds the control to the given map.
addTo: function addTo(map) {
this.remove();
this._map = map;
var container = this._container = this.onAdd(map),
pos = this.getPosition(),
corner = map._controlCorners[pos];
addClass(container, 'leaflet-control');
if (pos.indexOf('bottom') !== -1) {
corner.insertBefore(container, corner.firstChild);
} else {
corner.appendChild(container);
}
this._map.on('unload', this.remove, this);
return this;
},
// @method remove: this
// Removes the control from the map it is currently active on.
remove: function remove() {
if (!this._map) {
return this;
}
_remove(this._container);
if (this.onRemove) {
this.onRemove(this._map);
}
this._map.off('unload', this.remove, this);
this._map = null;
return this;
},
_refocusOnMap: function _refocusOnMap(e) {
// if map exists and event is not a keyboard event
if (this._map && e && e.screenX > 0 && e.screenY > 0) {
this._map.getContainer().focus();
}
}
});
/* @section Extension methods
* @uninheritable
*
* Every control should extend from `L.Control` and (re-)implement the following methods.
*
* @method onAdd(map: Map): HTMLElement
* Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
*
* @method onRemove(map: Map)
* Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
*/
/* @namespace Map
* @section Methods for Layers and Controls
*/
Map.include({
// @method addControl(control: Control): this
// Adds the given control to the map
addControl: function addControl(control) {
control.addTo(this);
return this;
},
// @method removeControl(control: Control): this
// Removes the given control from the map
removeControl: function removeControl(control) {
control.remove();
return this;
},
_initControlPos: function _initControlPos() {
var corners = this._controlCorners = {},
l = 'leaflet-',
container = this._controlContainer = create$1('div', l + 'control-container', this._container);
function createCorner(vSide, hSide) {
var className = l + vSide + ' ' + l + hSide;
corners[vSide + hSide] = create$1('div', className, container);
}
createCorner('top', 'left');
createCorner('top', 'right');
createCorner('bottom', 'left');
createCorner('bottom', 'right');
},
_clearControlPos: function _clearControlPos() {
for (var i in this._controlCorners) {
_remove(this._controlCorners[i]);
}
_remove(this._controlContainer);
delete this._controlCorners;
delete this._controlContainer;
}
});
/*
* @class Control.Layers
* @aka L.Control.Layers
* @inherits Control
*
* The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`.
*
* @example
*
* ```js
* var baseLayers = {
* "Mapbox": mapbox,
* "OpenStreetMap": osm
* };
*
* var overlays = {
* "Marker": marker,
* "Roads": roadsLayer
* };
*
* L.control.layers(baseLayers, overlays).addTo(map);
* ```
*
* The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
*
* ```js
* {
* "<someName1>": layer1,
* "<someName2>": layer2
* }
* ```
*
* The layer names can contain HTML, which allows you to add additional styling to the items:
*
* ```js
* {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
* ```
*/
var Layers = Control.extend({
// @section
// @aka Control.Layers options
options: {
// @option collapsed: Boolean = true
// If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
collapsed: true,
position: 'topright',
// @option autoZIndex: Boolean = true
// If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
autoZIndex: true,
// @option hideSingleBase: Boolean = false
// If `true`, the base layers in the control will be hidden when there is only one.
hideSingleBase: false,
// @option sortLayers: Boolean = false
// Whether to sort the layers. When `false`, layers will keep the order
// in which they were added to the control.
sortLayers: false,
// @option sortFunction: Function = *
// A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
// that will be used for sorting the layers, when `sortLayers` is `true`.
// The function receives both the `L.Layer` instances and their names, as in
// `sortFunction(layerA, layerB, nameA, nameB)`.
// By default, it sorts layers alphabetically by their name.
sortFunction: function sortFunction(layerA, layerB, nameA, nameB) {
return nameA < nameB ? -1 : nameB < nameA ? 1 : 0;
}
},
initialize: function initialize(baseLayers, overlays, options) {
setOptions(this, options);
this._layerControlInputs = [];
this._layers = [];
this._lastZIndex = 0;
this._handlingClick = false;
for (var i in baseLayers) {
this._addLayer(baseLayers[i], i);
}
for (i in overlays) {
this._addLayer(overlays[i], i, true);
}
},
onAdd: function onAdd(map) {
this._initLayout();
this._update();
this._map = map;
map.on('zoomend', this._checkDisabledLayers, this);
for (var i = 0; i < this._layers.length; i++) {
this._layers[i].layer.on('add remove', this._onLayerChange, this);
}
return this._container;
},
addTo: function addTo(map) {
Control.prototype.addTo.call(this, map);
// Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
return this._expandIfNotCollapsed();
},
onRemove: function onRemove() {
this._map.off('zoomend', this._checkDisabledLayers, this);
for (var i = 0; i < this._layers.length; i++) {
this._layers[i].layer.off('add remove', this._onLayerChange, this);
}
},
// @method addBaseLayer(layer: Layer, name: String): this
// Adds a base layer (radio button entry) with the given name to the control.
addBaseLayer: function addBaseLayer(layer, name) {
this._addLayer(layer, name);
return this._map ? this._update() : this;
},
// @method addOverlay(layer: Layer, name: String): this
// Adds an overlay (checkbox entry) with the given name to the control.
addOverlay: function addOverlay(layer, name) {
this._addLayer(layer, name, true);
return this._map ? this._update() : this;
},
// @method removeLayer(layer: Layer): this
// Remove the given layer from the control.
removeLayer: function removeLayer(layer) {
layer.off('add remove', this._onLayerChange, this);
var obj = this._getLayer(stamp(layer));
if (obj) {
this._layers.splice(this._layers.indexOf(obj), 1);
}
return this._map ? this._update() : this;
},
// @method expand(): this
// Expand the control container if collapsed.
expand: function expand() {
addClass(this._container, 'leaflet-control-layers-expanded');
this._section.style.height = null;
var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
if (acceptableHeight < this._section.clientHeight) {
addClass(this._section, 'leaflet-control-layers-scrollbar');
this._section.style.height = acceptableHeight + 'px';
} else {
removeClass(this._section, 'leaflet-control-layers-scrollbar');
}
this._checkDisabledLayers();
return this;
},
// @method collapse(): this
// Collapse the control container if expanded.
collapse: function collapse() {
removeClass(this._container, 'leaflet-control-layers-expanded');
return this;
},
_initLayout: function _initLayout() {
var className = 'leaflet-control-layers',
container = this._container = create$1('div', className),
collapsed = this.options.collapsed;
// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
container.setAttribute('aria-haspopup', true);
disableClickPropagation(container);
disableScrollPropagation(container);
var section = this._section = create$1('section', className + '-list');
if (collapsed) {
this._map.on('click', this.collapse, this);
if (!android) {
on(container, {
mouseenter: this.expand,
mouseleave: this.collapse
}, this);
}
}
var link = this._layersLink = create$1('a', className + '-toggle', container);
link.href = '#';
link.title = 'Layers';
if (touch) {
on(link, 'click', stop);
on(link, 'click', this.expand, this);
} else {
on(link, 'focus', this.expand, this);
}
if (!collapsed) {
this.expand();
}
this._baseLayersList = create$1('div', className + '-base', section);
this._separator = create$1('div', className + '-separator', section);
this._overlaysList = create$1('div', className + '-overlays', section);
container.appendChild(section);
},
_getLayer: function _getLayer(id) {
for (var i = 0; i < this._layers.length; i++) {
if (this._layers[i] && stamp(this._layers[i].layer) === id) {
return this._layers[i];
}
}
},
_addLayer: function _addLayer(layer, name, overlay) {
if (this._map) {
layer.on('add remove', this._onLayerChange, this);
}
this._layers.push({
layer: layer,
name: name,
overlay: overlay
});
if (this.options.sortLayers) {
this._layers.sort(bind(function (a, b) {
return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
}, this));
}
if (this.options.autoZIndex && layer.setZIndex) {
this._lastZIndex++;
layer.setZIndex(this._lastZIndex);
}
this._expandIfNotCollapsed();
},
_update: function _update() {
if (!this._container) {
return this;
}
empty(this._baseLayersList);
empty(this._overlaysList);
this._layerControlInputs = [];
var baseLayersPresent,
overlaysPresent,
i,
obj,
baseLayersCount = 0;
for (i = 0; i < this._layers.length; i++) {
obj = this._layers[i];
this._addItem(obj);
overlaysPresent = overlaysPresent || obj.overlay;
baseLayersPresent = baseLayersPresent || !obj.overlay;
baseLayersCount += !obj.overlay ? 1 : 0;
}
// Hide base layers section if there's only one layer.
if (this.options.hideSingleBase) {
baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
}
this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
return this;
},
_onLayerChange: function _onLayerChange(e) {
if (!this._handlingClick) {
this._update();
}
var obj = this._getLayer(stamp(e.target));
// @namespace Map
// @section Layer events
// @event baselayerchange: LayersControlEvent
// Fired when the base layer is changed through the [layer control](#control-layers).
// @event overlayadd: LayersControlEvent
// Fired when an overlay is selected through the [layer control](#control-layers).
// @event overlayremove: LayersControlEvent
// Fired when an overlay is deselected through the [layer control](#control-layers).
// @namespace Control.Layers
var type = obj.overlay ? e.type === 'add' ? 'overlayadd' : 'overlayremove' : e.type === 'add' ? 'baselayerchange' : null;
if (type) {
this._map.fire(type, obj);
}
},
// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
_createRadioElement: function _createRadioElement(name, checked) {
var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"' + (checked ? ' checked="checked"' : '') + '/>';
var radioFragment = document.createElement('div');
radioFragment.innerHTML = radioHtml;
return radioFragment.firstChild;
},
_addItem: function _addItem(obj) {
var label = document.createElement('label'),
checked = this._map.hasLayer(obj.layer),
input;
if (obj.overlay) {
input = document.createElement('input');
input.type = 'checkbox';
input.className = 'leaflet-control-layers-selector';
input.defaultChecked = checked;
} else {
input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
}
this._layerControlInputs.push(input);
input.layerId = stamp(obj.layer);
on(input, 'click', this._onInputClick, this);
var name = document.createElement('span');
name.innerHTML = ' ' + obj.name;
// Helps from preventing layer control flicker when checkboxes are disabled
// https://github.com/Leaflet/Leaflet/issues/2771
var holder = document.createElement('div');
label.appendChild(holder);
holder.appendChild(input);
holder.appendChild(name);
var container = obj.overlay ? this._overlaysList : this._baseLayersList;
container.appendChild(label);
this._checkDisabledLayers();
return label;
},
_onInputClick: function _onInputClick() {
var inputs = this._layerControlInputs,
input,
layer;
var addedLayers = [],
removedLayers = [];
this._handlingClick = true;
for (var i = inputs.length - 1; i >= 0; i--) {
input = inputs[i];
layer = this._getLayer(input.layerId).layer;
if (input.checked) {
addedLayers.push(layer);
} else if (!input.checked) {
removedLayers.push(layer);
}
}
// Bugfix issue 2318: Should remove all old layers before readding new ones
for (i = 0; i < removedLayers.length; i++) {
if (this._map.hasLayer(removedLayers[i])) {
this._map.removeLayer(removedLayers[i]);
}
}
for (i = 0; i < addedLayers.length; i++) {
if (!this._map.hasLayer(addedLayers[i])) {
this._map.addLayer(addedLayers[i]);
}
}
this._handlingClick = false;
this._refocusOnMap();
},
_checkDisabledLayers: function _checkDisabledLayers() {
var inputs = this._layerControlInputs,
input,
layer,
zoom = this._map.getZoom();
for (var i = inputs.length - 1; i >= 0; i--) {
input = inputs[i];
layer = this._getLayer(input.layerId).layer;
input.disabled = layer.options.minZoom !== undefined && zoom < layer.options.minZoom || layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom;
}
},
_expandIfNotCollapsed: function _expandIfNotCollapsed() {
if (this._map && !this.options.collapsed) {
this.expand();
}
return this;
},
_expand: function _expand() {
// Backward compatibility, remove me in 1.1.
return this.expand();
},
_collapse: function _collapse() {
// Backward compatibility, remove me in 1.1.
return this.collapse();
}
});
/*
* @class Control.Zoom
* @aka L.Control.Zoom
* @inherits Control
*
* A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
*/
var Zoom = Control.extend({
// @section
// @aka Control.Zoom options
options: {
position: 'topleft',
// @option zoomInText: String = '+'
// The text set on the 'zoom in' button.
zoomInText: '+',
// @option zoomInTitle: String = 'Zoom in'
// The title set on the 'zoom in' button.
zoomInTitle: 'Zoom in',
// @option zoomOutText: String = '−'
// The text set on the 'zoom out' button.
zoomOutText: '−',
// @option zoomOutTitle: String = 'Zoom out'
// The title set on the 'zoom out' button.
zoomOutTitle: 'Zoom out'
},
onAdd: function onAdd(map) {
var zoomName = 'leaflet-control-zoom',
container = create$1('div', zoomName + ' leaflet-bar'),
options = this.options;
this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, zoomName + '-in', container, this._zoomIn);
this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, zoomName + '-out', container, this._zoomOut);
this._updateDisabled();
map.on('zoomend zoomlevelschange', this._updateDisabled, this);
return container;
},
onRemove: function onRemove(map) {
map.off('zoomend zoomlevelschange', this._updateDisabled, this);
},
disable: function disable() {
this._disabled = true;
this._updateDisabled();
return this;
},
enable: function enable() {
this._disabled = false;
this._updateDisabled();
return this;
},
_zoomIn: function _zoomIn(e) {
if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
}
},
_zoomOut: function _zoomOut(e) {
if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
}
},
_createButton: function _createButton(html, title, className, container, fn) {
var link = create$1('a', className, container);
link.innerHTML = html;
link.href = '#';
link.title = title;
/*
* Will force screen readers like VoiceOver to read this as "Zoom in - button"
*/
link.setAttribute('role', 'button');
link.setAttribute('aria-label', title);
disableClickPropagation(link);
on(link, 'click', stop);
on(link, 'click', fn, this);
on(link, 'click', this._refocusOnMap, this);
return link;
},
_updateDisabled: function _updateDisabled() {
var map = this._map,
className = 'leaflet-disabled';
removeClass(this._zoomInButton, className);
removeClass(this._zoomOutButton, className);
if (this._disabled || map._zoom === map.getMinZoom()) {
addClass(this._zoomOutButton, className);
}
if (this._disabled || map._zoom === map.getMaxZoom()) {
addClass(this._zoomInButton, className);
}
}
});
// @namespace Map
// @section Control options
// @option zoomControl: Boolean = true
// Whether a [zoom control](#control-zoom) is added to the map by default.
Map.mergeOptions({
zoomControl: true
});
Map.addInitHook(function () {
if (this.options.zoomControl) {
// @section Controls
// @property zoomControl: Control.Zoom
// The default zoom control (only available if the
// [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
this.zoomControl = new Zoom();
this.addControl(this.zoomControl);
}
});
/*
* @class Control.Scale
* @aka L.Control.Scale
* @inherits Control
*
* A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
*
* @example
*
* ```js
* L.control.scale().addTo(map);
* ```
*/
var Scale = Control.extend({
// @section
// @aka Control.Scale options
options: {
position: 'bottomleft',
// @option maxWidth: Number = 100
// Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
maxWidth: 100,
// @option metric: Boolean = True
// Whether to show the metric scale line (m/km).
metric: true,
// @option imperial: Boolean = True
// Whether to show the imperial scale line (mi/ft).
imperial: true
// @option updateWhenIdle: Boolean = false
// If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
},
onAdd: function onAdd(map) {
var className = 'leaflet-control-scale',
container = create$1('div', className),
options = this.options;
this._addScales(options, className + '-line', container);
map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
map.whenReady(this._update, this);
return container;
},
onRemove: function onRemove(map) {
map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
},
_addScales: function _addScales(options, className, container) {
if (options.metric) {
this._mScale = create$1('div', className, container);
}
if (options.imperial) {
this._iScale = create$1('div', className, container);
}
},
_update: function _update() {
var map = this._map,
y = map.getSize().y / 2;
var maxMeters = map.distance(map.containerPointToLatLng([0, y]), map.containerPointToLatLng([this.options.maxWidth, y]));
this._updateScales(maxMeters);
},
_updateScales: function _updateScales(maxMeters) {
if (this.options.metric && maxMeters) {
this._updateMetric(maxMeters);
}
if (this.options.imperial && maxMeters) {
this._updateImperial(maxMeters);
}
},
_updateMetric: function _updateMetric(maxMeters) {
var meters = this._getRoundNum(maxMeters),
label = meters < 1000 ? meters + ' m' : meters / 1000 + ' km';
this._updateScale(this._mScale, label, meters / maxMeters);
},
_updateImperial: function _updateImperial(maxMeters) {
var maxFeet = maxMeters * 3.2808399,
maxMiles,
miles,
feet;
if (maxFeet > 5280) {
maxMiles = maxFeet / 5280;
miles = this._getRoundNum(maxMiles);
this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
} else {
feet = this._getRoundNum(maxFeet);
this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
}
},
_updateScale: function _updateScale(scale, text, ratio) {
scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
scale.innerHTML = text;
},
_getRoundNum: function _getRoundNum(num) {
var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
d = num / pow10;
d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
return pow10 * d;
}
});
/*
* @class Control.Attribution
* @aka L.Control.Attribution
* @inherits Control
*
* The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
*/
var Attribution = Control.extend({
// @section
// @aka Control.Attribution options
options: {
position: 'bottomright',
// @option prefix: String = 'Leaflet'
// The HTML text shown before the attributions. Pass `false` to disable.
prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
},
initialize: function initialize(options) {
setOptions(this, options);
this._attributions = {};
},
onAdd: function onAdd(map) {
map.attributionControl = this;
this._container = create$1('div', 'leaflet-control-attribution');
disableClickPropagation(this._container);
// TODO ugly, refactor
for (var i in map._layers) {
if (map._layers[i].getAttribution) {
this.addAttribution(map._layers[i].getAttribution());
}
}
this._update();
return this._container;
},
// @method setPrefix(prefix: String): this
// Sets the text before the attributions.
setPrefix: function setPrefix(prefix) {
this.options.prefix = prefix;
this._update();
return this;
},
// @method addAttribution(text: String): this
// Adds an attribution text (e.g. `'Vector data © Mapbox'`).
addAttribution: function addAttribution(text) {
if (!text) {
return this;
}
if (!this._attributions[text]) {
this._attributions[text] = 0;
}
this._attributions[text]++;
this._update();
return this;
},
// @method removeAttribution(text: String): this
// Removes an attribution text.
removeAttribution: function removeAttribution(text) {
if (!text) {
return this;
}
if (this._attributions[text]) {
this._attributions[text]--;
this._update();
}
return this;
},
_update: function _update() {
if (!this._map) {
return;
}
var attribs = [];
for (var i in this._attributions) {
if (this._attributions[i]) {
attribs.push(i);
}
}
var prefixAndAttribs = [];
if (this.options.prefix) {
prefixAndAttribs.push(this.options.prefix);
}
if (attribs.length) {
prefixAndAttribs.push(attribs.join(', '));
}
this._container.innerHTML = prefixAndAttribs.join(' | ');
}
});
// @namespace Map
// @section Control options
// @option attributionControl: Boolean = true
// Whether a [attribution control](#control-attribution) is added to the map by default.
Map.mergeOptions({
attributionControl: true
});
Map.addInitHook(function () {
if (this.options.attributionControl) {
new Attribution().addTo(this);
}
});
Control.Layers = Layers;
Control.Zoom = Zoom;
Control.Scale = Scale;
Control.Attribution = Attribution;
/*
L.Handler is a base class for handler classes that are used internally to inject
interaction features like dragging to classes like Map and Marker.
*/
// @class Handler
// @aka L.Handler
// Abstract class for map interaction handlers
var Handler = Class.extend({
initialize: function initialize(map) {
this._map = map;
},
// @method enable(): this
// Enables the handler
enable: function enable() {
if (this._enabled) {
return this;
}
this._enabled = true;
this.addHooks();
return this;
},
// @method disable(): this
// Disables the handler
disable: function disable() {
if (!this._enabled) {
return this;
}
this._enabled = false;
this.removeHooks();
return this;
},
// @method enabled(): Boolean
// Returns `true` if the handler is enabled
enabled: function enabled() {
return !!this._enabled;
}
// @section Extension methods
// Classes inheriting from `Handler` must implement the two following methods:
// @method addHooks()
// Called when the handler is enabled, should add event hooks.
// @method removeHooks()
// Called when the handler is disabled, should remove the event hooks added previously.
});
// @section There is static function which can be called without instantiating L.Handler:
// @function addTo(map: Map, name: String): this
// Adds a new Handler to the given map with the given name.
Handler.addTo = function (map, name) {
map.addHandler(name, this);
return this;
};
/*
* @class Draggable
* @aka L.Draggable
* @inherits Evented
*
* A class for making DOM elements draggable (including touch support).
* Used internally for map and marker dragging. Only works for elements
* that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
*
* @example
* ```js
* var draggable = new L.Draggable(elementToDrag);
* draggable.enable();
* ```
*/
var START = touch ? 'touchstart mousedown' : 'mousedown';
var END = {
mousedown: 'mouseup',
touchstart: 'touchend',
pointerdown: 'touchend',
MSPointerDown: 'touchend'
};
var MOVE = {
mousedown: 'mousemove',
touchstart: 'touchmove',
pointerdown: 'touchmove',
MSPointerDown: 'touchmove'
};
var Draggable = Evented.extend({
options: {
// @section
// @aka Draggable options
// @option clickTolerance: Number = 3
// The max number of pixels a user can shift the mouse pointer during a click
// for it to be considered a valid click (as opposed to a mouse drag).
clickTolerance: 3
},
// @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
// Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
initialize: function initialize(element, dragStartTarget, preventOutline$$1, options) {
setOptions(this, options);
this._element = element;
this._dragStartTarget = dragStartTarget || element;
this._preventOutline = preventOutline$$1;
},
// @method enable()
// Enables the dragging ability
enable: function enable() {
if (this._enabled) {
return;
}
on(this._dragStartTarget, START, this._onDown, this);
this._enabled = true;
},
// @method disable()
// Disables the dragging ability
disable: function disable() {
if (!this._enabled) {
return;
}
// If we're currently dragging this draggable,
// disabling it counts as first ending the drag.
if (Draggable._dragging === this) {
this.finishDrag();
}
off(this._dragStartTarget, START, this._onDown, this);
this._enabled = false;
this._moved = false;
},
_onDown: function _onDown(e) {
// Ignore simulated events, since we handle both touch and
// mouse explicitly; otherwise we risk getting duplicates of
// touch events, see #4315.
// Also ignore the event if disabled; this happens in IE11
// under some circumstances, see #3666.
if (e._simulated || !this._enabled) {
return;
}
this._moved = false;
if (hasClass(this._element, 'leaflet-zoom-anim')) {
return;
}
if (Draggable._dragging || e.shiftKey || e.which !== 1 && e.button !== 1 && !e.touches) {
return;
}
Draggable._dragging = this; // Prevent dragging multiple objects at once.
if (this._preventOutline) {
preventOutline(this._element);
}
disableImageDrag();
disableTextSelection();
if (this._moving) {
return;
}
// @event down: Event
// Fired when a drag is about to start.
this.fire('down');
var first = e.touches ? e.touches[0] : e,
sizedParent = getSizedParentNode(this._element);
this._startPoint = new Point(first.clientX, first.clientY);
// Cache the scale, so that we can continuously compensate for it during drag (_onMove).
this._parentScale = getScale(sizedParent);
on(document, MOVE[e.type], this._onMove, this);
on(document, END[e.type], this._onUp, this);
},
_onMove: function _onMove(e) {
// Ignore simulated events, since we handle both touch and
// mouse explicitly; otherwise we risk getting duplicates of
// touch events, see #4315.
// Also ignore the event if disabled; this happens in IE11
// under some circumstances, see #3666.
if (e._simulated || !this._enabled) {
return;
}
if (e.touches && e.touches.length > 1) {
this._moved = true;
return;
}
var first = e.touches && e.touches.length === 1 ? e.touches[0] : e,
offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
if (!offset.x && !offset.y) {
return;
}
if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) {
return;
}
// We assume that the parent container's position, border and scale do not change for the duration of the drag.
// Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
// and we can use the cached value for the scale.
offset.x /= this._parentScale.x;
offset.y /= this._parentScale.y;
preventDefault(e);
if (!this._moved) {
// @event dragstart: Event
// Fired when a drag starts
this.fire('dragstart');
this._moved = true;
this._startPos = getPosition(this._element).subtract(offset);
addClass(document.body, 'leaflet-dragging');
this._lastTarget = e.target || e.srcElement;
// IE and Edge do not give the <use> element, so fetch it
// if necessary
if (window.SVGElementInstance && this._lastTarget instanceof SVGElementInstance) {
this._lastTarget = this._lastTarget.correspondingUseElement;
}
addClass(this._lastTarget, 'leaflet-drag-target');
}
this._newPos = this._startPos.add(offset);
this._moving = true;
cancelAnimFrame(this._animRequest);
this._lastEvent = e;
this._animRequest = requestAnimFrame(this._updatePosition, this, true);
},
_updatePosition: function _updatePosition() {
var e = {
originalEvent: this._lastEvent
};
// @event predrag: Event
// Fired continuously during dragging *before* each corresponding
// update of the element's position.
this.fire('predrag', e);
setPosition(this._element, this._newPos);
// @event drag: Event
// Fired continuously during dragging.
this.fire('drag', e);
},
_onUp: function _onUp(e) {
// Ignore simulated events, since we handle both touch and
// mouse explicitly; otherwise we risk getting duplicates of
// touch events, see #4315.
// Also ignore the event if disabled; this happens in IE11
// under some circumstances, see #3666.
if (e._simulated || !this._enabled) {
return;
}
this.finishDrag();
},
finishDrag: function finishDrag() {
removeClass(document.body, 'leaflet-dragging');
if (this._lastTarget) {
removeClass(this._lastTarget, 'leaflet-drag-target');
this._lastTarget = null;
}
for (var i in MOVE) {
off(document, MOVE[i], this._onMove, this);
off(document, END[i], this._onUp, this);
}
enableImageDrag();
enableTextSelection();
if (this._moved && this._moving) {
// ensure drag is not fired after dragend
cancelAnimFrame(this._animRequest);
// @event dragend: DragEndEvent
// Fired when the drag ends.
this.fire('dragend', {
distance: this._newPos.distanceTo(this._startPos)
});
}
this._moving = false;
Draggable._dragging = false;
}
});
/*
* @namespace LineUtil
*
* Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
*/
// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
// Improves rendering performance dramatically by lessening the number of points to draw.
// @function simplify(points: Point[], tolerance: Number): Point[]
// Dramatically reduces the number of points in a polyline while retaining
// its shape and returns a new array of simplified points, using the
// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
// Used for a huge performance boost when processing/displaying Leaflet polylines for
// each zoom level and also reducing visual noise. tolerance affects the amount of
// simplification (lesser value means higher quality but slower and with more points).
// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
function simplify(points, tolerance) {
if (!tolerance || !points.length) {
return points.slice();
}
var sqTolerance = tolerance * tolerance;
// stage 1: vertex reduction
points = _reducePoints(points, sqTolerance);
// stage 2: Douglas-Peucker simplification
points = _simplifyDP(points, sqTolerance);
return points;
}
// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
// Returns the distance between point `p` and segment `p1` to `p2`.
function pointToSegmentDistance(p, p1, p2) {
return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
}
// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
// Returns the closest point from a point `p` on a segment `p1` to `p2`.
function closestPointOnSegment(p, p1, p2) {
return _sqClosestPointOnSegment(p, p1, p2);
}
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
function _simplifyDP(points, sqTolerance) {
var len = points.length,
ArrayConstructor = (typeof Uint8Array === "undefined" ? "undefined" : babelHelpers["typeof"](Uint8Array)) !== undefined + '' ? Uint8Array : Array,
markers = new ArrayConstructor(len);
markers[0] = markers[len - 1] = 1;
_simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
var i,
newPoints = [];
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
return newPoints;
}
function _simplifyDPStep(points, markers, sqTolerance, first, last) {
var maxSqDist = 0,
index,
i,
sqDist;
for (i = first + 1; i <= last - 1; i++) {
sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
_simplifyDPStep(points, markers, sqTolerance, first, index);
_simplifyDPStep(points, markers, sqTolerance, index, last);
}
}
// reduce points that are too close to each other to a single point
function _reducePoints(points, sqTolerance) {
var reducedPoints = [points[0]];
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
if (_sqDist(points[i], points[prev]) > sqTolerance) {
reducedPoints.push(points[i]);
prev = i;
}
}
if (prev < len - 1) {
reducedPoints.push(points[len - 1]);
}
return reducedPoints;
}
var _lastCode;
// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
// Clips the segment a to b by rectangular bounds with the
// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
// (modifying the segment points directly!). Used by Leaflet to only show polyline
// points that are on the screen or near, increasing performance.
function clipSegment(a, b, bounds, useLastCode, round) {
var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
codeB = _getBitCode(b, bounds),
codeOut,
p,
newCode;
// save 2nd code to avoid calculating it on the next segment
_lastCode = codeB;
while (true) {
// if a,b is inside the clip window (trivial accept)
if (!(codeA | codeB)) {
return [a, b];
}
// if a,b is outside the clip window (trivial reject)
if (codeA & codeB) {
return false;
}
// other cases
codeOut = codeA || codeB;
p = _getEdgeIntersection(a, b, codeOut, bounds, round);
newCode = _getBitCode(p, bounds);
if (codeOut === codeA) {
a = p;
codeA = newCode;
} else {
b = p;
codeB = newCode;
}
}
}
function _getEdgeIntersection(a, b, code, bounds, round) {
var dx = b.x - a.x,
dy = b.y - a.y,
min = bounds.min,
max = bounds.max,
x,
y;
if (code & 8) {
// top
x = a.x + dx * (max.y - a.y) / dy;
y = max.y;
} else if (code & 4) {
// bottom
x = a.x + dx * (min.y - a.y) / dy;
y = min.y;
} else if (code & 2) {
// right
x = max.x;
y = a.y + dy * (max.x - a.x) / dx;
} else if (code & 1) {
// left
x = min.x;
y = a.y + dy * (min.x - a.x) / dx;
}
return new Point(x, y, round);
}
function _getBitCode(p, bounds) {
var code = 0;
if (p.x < bounds.min.x) {
// left
code |= 1;
} else if (p.x > bounds.max.x) {
// right
code |= 2;
}
if (p.y < bounds.min.y) {
// bottom
code |= 4;
} else if (p.y > bounds.max.y) {
// top
code |= 8;
}
return code;
}
// square distance (to avoid unnecessary Math.sqrt calls)
function _sqDist(p1, p2) {
var dx = p2.x - p1.x,
dy = p2.y - p1.y;
return dx * dx + dy * dy;
}
// return closest point on segment or distance to that point
function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y,
dot = dx * dx + dy * dy,
t;
if (dot > 0) {
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return sqDist ? dx * dx + dy * dy : new Point(x, y);
}
// @function isFlat(latlngs: LatLng[]): Boolean
// Returns true if `latlngs` is a flat array, false is nested.
function isFlat(latlngs) {
return !isArray(latlngs[0]) || babelHelpers["typeof"](latlngs[0][0]) !== 'object' && typeof latlngs[0][0] !== 'undefined';
}
function _flat(latlngs) {
console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
return isFlat(latlngs);
}
var LineUtil = (Object.freeze || Object)({
simplify: simplify,
pointToSegmentDistance: pointToSegmentDistance,
closestPointOnSegment: closestPointOnSegment,
clipSegment: clipSegment,
_getEdgeIntersection: _getEdgeIntersection,
_getBitCode: _getBitCode,
_sqClosestPointOnSegment: _sqClosestPointOnSegment,
isFlat: isFlat,
_flat: _flat
});
/*
* @namespace PolyUtil
* Various utility functions for polygon geometries.
*/
/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
* Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
* Used by Leaflet to only show polygon points that are on the screen or near, increasing
* performance. Note that polygon points needs different algorithm for clipping
* than polyline, so there's a separate method for it.
*/
function clipPolygon(points, bounds, round) {
var clippedPoints,
edges = [1, 4, 2, 8],
i,
j,
k,
a,
b,
len,
edge,
p;
for (i = 0, len = points.length; i < len; i++) {
points[i]._code = _getBitCode(points[i], bounds);
}
// for each edge (left, bottom, right, top)
for (k = 0; k < 4; k++) {
edge = edges[k];
clippedPoints = [];
for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
a = points[i];
b = points[j];
// if a is inside the clip window
if (!(a._code & edge)) {
// if b is outside the clip window (a->b goes out of screen)
if (b._code & edge) {
p = _getEdgeIntersection(b, a, edge, bounds, round);
p._code = _getBitCode(p, bounds);
clippedPoints.push(p);
}
clippedPoints.push(a);
// else if b is inside the clip window (a->b enters the screen)
} else if (!(b._code & edge)) {
p = _getEdgeIntersection(b, a, edge, bounds, round);
p._code = _getBitCode(p, bounds);
clippedPoints.push(p);
}
}
points = clippedPoints;
}
return points;
}
var PolyUtil = (Object.freeze || Object)({
clipPolygon: clipPolygon
});
/*
* @namespace Projection
* @section
* Leaflet comes with a set of already defined Projections out of the box:
*
* @projection L.Projection.LonLat
*
* Equirectangular, or Plate Carree projection — the most simple projection,
* mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
* latitude. Also suitable for flat worlds, e.g. game maps. Used by the
* `EPSG:4326` and `Simple` CRS.
*/
var LonLat = {
project: function project(latlng) {
return new Point(latlng.lng, latlng.lat);
},
unproject: function unproject(point) {
return new LatLng(point.y, point.x);
},
bounds: new Bounds([-180, -90], [180, 90])
};
/*
* @namespace Projection
* @projection L.Projection.Mercator
*
* Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
*/
var Mercator = {
R: 6378137,
R_MINOR: 6356752.314245179,
bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
project: function project(latlng) {
var d = Math.PI / 180,
r = this.R,
y = latlng.lat * d,
tmp = this.R_MINOR / r,
e = Math.sqrt(1 - tmp * tmp),
con = e * Math.sin(y);
var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
y = -r * Math.log(Math.max(ts, 1E-10));
return new Point(latlng.lng * d * r, y);
},
unproject: function unproject(point) {
var d = 180 / Math.PI,
r = this.R,
tmp = this.R_MINOR / r,
e = Math.sqrt(1 - tmp * tmp),
ts = Math.exp(-point.y / r),
phi = Math.PI / 2 - 2 * Math.atan(ts);
for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
con = e * Math.sin(phi);
con = Math.pow((1 - con) / (1 + con), e / 2);
dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
phi += dphi;
}
return new LatLng(phi * d, point.x * d / r);
}
};
/*
* @class Projection
* An object with methods for projecting geographical coordinates of the world onto
* a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
* @property bounds: Bounds
* The bounds (specified in CRS units) where the projection is valid
* @method project(latlng: LatLng): Point
* Projects geographical coordinates into a 2D point.
* Only accepts actual `L.LatLng` instances, not arrays.
* @method unproject(point: Point): LatLng
* The inverse of `project`. Projects a 2D point into a geographical location.
* Only accepts actual `L.Point` instances, not arrays.
* Note that the projection instances do not inherit from Leafet's `Class` object,
* and can't be instantiated. Also, new classes can't inherit from them,
* and methods can't be added to them with the `include` function.
*/
var index = (Object.freeze || Object)({
LonLat: LonLat,
Mercator: Mercator,
SphericalMercator: SphericalMercator
});
/*
* @namespace CRS
* @crs L.CRS.EPSG3395
*
* Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
*/
var EPSG3395 = extend({}, Earth, {
code: 'EPSG:3395',
projection: Mercator,
transformation: function () {
var scale = 0.5 / (Math.PI * Mercator.R);
return toTransformation(scale, 0.5, -scale, 0.5);
}()
});
/*
* @namespace CRS
* @crs L.CRS.EPSG4326
*
* A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
*
* Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
* which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
* with this CRS, ensure that there are two 256x256 pixel tiles covering the
* whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
* or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
*/
var EPSG4326 = extend({}, Earth, {
code: 'EPSG:4326',
projection: LonLat,
transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
});
/*
* @namespace CRS
* @crs L.CRS.Simple
*
* A simple CRS that maps longitude and latitude into `x` and `y` directly.
* May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
* axis should still be inverted (going from bottom to top). `distance()` returns
* simple euclidean distance.
*/
var Simple = extend({}, CRS, {
projection: LonLat,
transformation: toTransformation(1, 0, -1, 0),
scale: function scale(zoom) {
return Math.pow(2, zoom);
},
zoom: function zoom(scale) {
return Math.log(scale) / Math.LN2;
},
distance: function distance(latlng1, latlng2) {
var dx = latlng2.lng - latlng1.lng,
dy = latlng2.lat - latlng1.lat;
return Math.sqrt(dx * dx + dy * dy);
},
infinite: true
});
CRS.Earth = Earth;
CRS.EPSG3395 = EPSG3395;
CRS.EPSG3857 = EPSG3857;
CRS.EPSG900913 = EPSG900913;
CRS.EPSG4326 = EPSG4326;
CRS.Simple = Simple;
/*
* @class Layer
* @inherits Evented
* @aka L.Layer
* @aka ILayer
*
* A set of methods from the Layer base class that all Leaflet layers use.
* Inherits all methods, options and events from `L.Evented`.
*
* @example
*
* ```js
* var layer = L.marker(latlng).addTo(map);
* layer.addTo(map);
* layer.remove();
* ```
*
* @event add: Event
* Fired after the layer is added to a map
*
* @event remove: Event
* Fired after the layer is removed from a map
*/
var Layer = Evented.extend({
// Classes extending `L.Layer` will inherit the following options:
options: {
// @option pane: String = 'overlayPane'
// By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
pane: 'overlayPane',
// @option attribution: String = null
// String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
attribution: null,
bubblingMouseEvents: true
},
/* @section
* Classes extending `L.Layer` will inherit the following methods:
*
* @method addTo(map: Map|LayerGroup): this
* Adds the layer to the given map or layer group.
*/
addTo: function addTo(map) {
map.addLayer(this);
return this;
},
// @method remove: this
// Removes the layer from the map it is currently active on.
remove: function remove() {
return this.removeFrom(this._map || this._mapToAdd);
},
// @method removeFrom(map: Map): this
// Removes the layer from the given map
removeFrom: function removeFrom(obj) {
if (obj) {
obj.removeLayer(this);
}
return this;
},
// @method getPane(name? : String): HTMLElement
// Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
getPane: function getPane(name) {
return this._map.getPane(name ? this.options[name] || name : this.options.pane);
},
addInteractiveTarget: function addInteractiveTarget(targetEl) {
this._map._targets[stamp(targetEl)] = this;
return this;
},
removeInteractiveTarget: function removeInteractiveTarget(targetEl) {
delete this._map._targets[stamp(targetEl)];
return this;
},
// @method getAttribution: String
// Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
getAttribution: function getAttribution() {
return this.options.attribution;
},
_layerAdd: function _layerAdd(e) {
var map = e.target;
// check in case layer gets added and then removed before the map is ready
if (!map.hasLayer(this)) {
return;
}
this._map = map;
this._zoomAnimated = map._zoomAnimated;
if (this.getEvents) {
var events = this.getEvents();
map.on(events, this);
this.once('remove', function () {
map.off(events, this);
}, this);
}
this.onAdd(map);
if (this.getAttribution && map.attributionControl) {
map.attributionControl.addAttribution(this.getAttribution());
}
this.fire('add');
map.fire('layeradd', {
layer: this
});
}
});
/* @section Extension methods
* @uninheritable
*
* Every layer should extend from `L.Layer` and (re-)implement the following methods.
*
* @method onAdd(map: Map): this
* Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
*
* @method onRemove(map: Map): this
* Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
*
* @method getEvents(): Object
* This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
*
* @method getAttribution(): String
* This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
*
* @method beforeAdd(map: Map): this
* Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
*/
/* @namespace Map
* @section Layer events
*
* @event layeradd: LayerEvent
* Fired when a new layer is added to the map.
*
* @event layerremove: LayerEvent
* Fired when some layer is removed from the map
*
* @section Methods for Layers and Controls
*/
Map.include({
// @method addLayer(layer: Layer): this
// Adds the given layer to the map
addLayer: function addLayer(layer) {
if (!layer._layerAdd) {
throw new Error('The provided object is not a Layer.');
}
var id = stamp(layer);
if (this._layers[id]) {
return this;
}
this._layers[id] = layer;
layer._mapToAdd = this;
if (layer.beforeAdd) {
layer.beforeAdd(this);
}
this.whenReady(layer._layerAdd, layer);
return this;
},
// @method removeLayer(layer: Layer): this
// Removes the given layer from the map.
removeLayer: function removeLayer(layer) {
var id = stamp(layer);
if (!this._layers[id]) {
return this;
}
if (this._loaded) {
layer.onRemove(this);
}
if (layer.getAttribution && this.attributionControl) {
this.attributionControl.removeAttribution(layer.getAttribution());
}
delete this._layers[id];
if (this._loaded) {
this.fire('layerremove', {
layer: layer
});
layer.fire('remove');
}
layer._map = layer._mapToAdd = null;
return this;
},
// @method hasLayer(layer: Layer): Boolean
// Returns `true` if the given layer is currently added to the map
hasLayer: function hasLayer(layer) {
return !!layer && stamp(layer) in this._layers;
},
/* @method eachLayer(fn: Function, context?: Object): this
* Iterates over the layers of the map, optionally specifying context of the iterator function.
* ```
* map.eachLayer(function(layer){
* layer.bindPopup('Hello');
* });
* ```
*/
eachLayer: function eachLayer(method, context) {
for (var i in this._layers) {
method.call(context, this._layers[i]);
}
return this;
},
_addLayers: function _addLayers(layers) {
layers = layers ? isArray(layers) ? layers : [layers] : [];
for (var i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]);
}
},
_addZoomLimit: function _addZoomLimit(layer) {
if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
this._zoomBoundLayers[stamp(layer)] = layer;
this._updateZoomLevels();
}
},
_removeZoomLimit: function _removeZoomLimit(layer) {
var id = stamp(layer);
if (this._zoomBoundLayers[id]) {
delete this._zoomBoundLayers[id];
this._updateZoomLevels();
}
},
_updateZoomLevels: function _updateZoomLevels() {
var minZoom = Infinity,
maxZoom = -Infinity,
oldZoomSpan = this._getZoomSpan();
for (var i in this._zoomBoundLayers) {
var options = this._zoomBoundLayers[i].options;
minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
}
this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
// @section Map state change events
// @event zoomlevelschange: Event
// Fired when the number of zoomlevels on the map is changed due
// to adding or removing a layer.
if (oldZoomSpan !== this._getZoomSpan()) {
this.fire('zoomlevelschange');
}
if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
this.setZoom(this._layersMaxZoom);
}
if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
this.setZoom(this._layersMinZoom);
}
}
});
/*
* @class LayerGroup
* @aka L.LayerGroup
* @inherits Layer
*
* Used to group several layers and handle them as one. If you add it to the map,
* any layers added or removed from the group will be added/removed on the map as
* well. Extends `Layer`.
*
* @example
*
* ```js
* L.layerGroup([marker1, marker2])
* .addLayer(polyline)
* .addTo(map);
* ```
*/
var LayerGroup = Layer.extend({
initialize: function initialize(layers, options) {
setOptions(this, options);
this._layers = {};
var i, len;
if (layers) {
for (i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]);
}
}
},
// @method addLayer(layer: Layer): this
// Adds the given layer to the group.
addLayer: function addLayer(layer) {
var id = this.getLayerId(layer);
this._layers[id] = layer;
if (this._map) {
this._map.addLayer(layer);
}
return this;
},
// @method removeLayer(layer: Layer): this
// Removes the given layer from the group.
// @alternative
// @method removeLayer(id: Number): this
// Removes the layer with the given internal ID from the group.
removeLayer: function removeLayer(layer) {
var id = layer in this._layers ? layer : this.getLayerId(layer);
if (this._map && this._layers[id]) {
this._map.removeLayer(this._layers[id]);
}
delete this._layers[id];
return this;
},
// @method hasLayer(layer: Layer): Boolean
// Returns `true` if the given layer is currently added to the group.
// @alternative
// @method hasLayer(id: Number): Boolean
// Returns `true` if the given internal ID is currently added to the group.
hasLayer: function hasLayer(layer) {
return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
},
// @method clearLayers(): this
// Removes all the layers from the group.
clearLayers: function clearLayers() {
return this.eachLayer(this.removeLayer, this);
},
// @method invoke(methodName: String, …): this
// Calls `methodName` on every layer contained in this group, passing any
// additional parameters. Has no effect if the layers contained do not
// implement `methodName`.
invoke: function invoke(methodName) {
var args = Array.prototype.slice.call(arguments, 1),
i,
layer;
for (i in this._layers) {
layer = this._layers[i];
if (layer[methodName]) {
layer[methodName].apply(layer, args);
}
}
return this;
},
onAdd: function onAdd(map) {
this.eachLayer(map.addLayer, map);
},
onRemove: function onRemove(map) {
this.eachLayer(map.removeLayer, map);
},
// @method eachLayer(fn: Function, context?: Object): this
// Iterates over the layers of the group, optionally specifying context of the iterator function.
// ```js
// group.eachLayer(function (layer) {
// layer.bindPopup('Hello');
// });
// ```
eachLayer: function eachLayer(method, context) {
for (var i in this._layers) {
method.call(context, this._layers[i]);
}
return this;
},
// @method getLayer(id: Number): Layer
// Returns the layer with the given internal ID.
getLayer: function getLayer(id) {
return this._layers[id];
},
// @method getLayers(): Layer[]
// Returns an array of all the layers added to the group.
getLayers: function getLayers() {
var layers = [];
this.eachLayer(layers.push, layers);
return layers;
},
// @method setZIndex(zIndex: Number): this
// Calls `setZIndex` on every layer contained in this group, passing the z-index.
setZIndex: function setZIndex(zIndex) {
return this.invoke('setZIndex', zIndex);
},
// @method getLayerId(layer: Layer): Number
// Returns the internal ID for a layer
getLayerId: function getLayerId(layer) {
return stamp(layer);
}
});
/*
* @class FeatureGroup
* @aka L.FeatureGroup
* @inherits LayerGroup
*
* Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
* * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
* * Events are propagated to the `FeatureGroup`, so if the group has an event
* handler, it will handle events from any of the layers. This includes mouse events
* and custom events.
* * Has `layeradd` and `layerremove` events
*
* @example
*
* ```js
* L.featureGroup([marker1, marker2, polyline])
* .bindPopup('Hello world!')
* .on('click', function() { alert('Clicked on a member of the group!'); })
* .addTo(map);
* ```
*/
var FeatureGroup = LayerGroup.extend({
addLayer: function addLayer(layer) {
if (this.hasLayer(layer)) {
return this;
}
layer.addEventParent(this);
LayerGroup.prototype.addLayer.call(this, layer);
// @event layeradd: LayerEvent
// Fired when a layer is added to this `FeatureGroup`
return this.fire('layeradd', {
layer: layer
});
},
removeLayer: function removeLayer(layer) {
if (!this.hasLayer(layer)) {
return this;
}
if (layer in this._layers) {
layer = this._layers[layer];
}
layer.removeEventParent(this);
LayerGroup.prototype.removeLayer.call(this, layer);
// @event layerremove: LayerEvent
// Fired when a layer is removed from this `FeatureGroup`
return this.fire('layerremove', {
layer: layer
});
},
// @method setStyle(style: Path options): this
// Sets the given path options to each layer of the group that has a `setStyle` method.
setStyle: function setStyle(style) {
return this.invoke('setStyle', style);
},
// @method bringToFront(): this
// Brings the layer group to the top of all other layers
bringToFront: function bringToFront() {
return this.invoke('bringToFront');
},
// @method bringToBack(): this
// Brings the layer group to the back of all other layers
bringToBack: function bringToBack() {
return this.invoke('bringToBack');
},
// @method getBounds(): LatLngBounds
// Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
getBounds: function getBounds() {
var bounds = new LatLngBounds();
for (var id in this._layers) {
var layer = this._layers[id];
bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
}
return bounds;
}
});
/*
* @class Icon
* @aka L.Icon
*
* Represents an icon to provide when creating a marker.
*
* @example
*
* ```js
* var myIcon = L.icon({
* iconUrl: 'my-icon.png',
* iconRetinaUrl: 'my-icon@2x.png',
* iconSize: [38, 95],
* iconAnchor: [22, 94],
* popupAnchor: [-3, -76],
* shadowUrl: 'my-icon-shadow.png',
* shadowRetinaUrl: 'my-icon-shadow@2x.png',
* shadowSize: [68, 95],
* shadowAnchor: [22, 94]
* });
*
* L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
* ```
*
* `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
*
*/
var Icon = Class.extend({
/* @section
* @aka Icon options
*
* @option iconUrl: String = null
* **(required)** The URL to the icon image (absolute or relative to your script path).
*
* @option iconRetinaUrl: String = null
* The URL to a retina sized version of the icon image (absolute or relative to your
* script path). Used for Retina screen devices.
*
* @option iconSize: Point = null
* Size of the icon image in pixels.
*
* @option iconAnchor: Point = null
* The coordinates of the "tip" of the icon (relative to its top left corner). The icon
* will be aligned so that this point is at the marker's geographical location. Centered
* by default if size is specified, also can be set in CSS with negative margins.
*
* @option popupAnchor: Point = [0, 0]
* The coordinates of the point from which popups will "open", relative to the icon anchor.
*
* @option tooltipAnchor: Point = [0, 0]
* The coordinates of the point from which tooltips will "open", relative to the icon anchor.
*
* @option shadowUrl: String = null
* The URL to the icon shadow image. If not specified, no shadow image will be created.
*
* @option shadowRetinaUrl: String = null
*
* @option shadowSize: Point = null
* Size of the shadow image in pixels.
*
* @option shadowAnchor: Point = null
* The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
* as iconAnchor if not specified).
*
* @option className: String = ''
* A custom class name to assign to both icon and shadow images. Empty by default.
*/
options: {
popupAnchor: [0, 0],
tooltipAnchor: [0, 0]
},
initialize: function initialize(options) {
setOptions(this, options);
},
// @method createIcon(oldIcon?: HTMLElement): HTMLElement
// Called internally when the icon has to be shown, returns a `<img>` HTML element
// styled according to the options.
createIcon: function createIcon(oldIcon) {
return this._createIcon('icon', oldIcon);
},
// @method createShadow(oldIcon?: HTMLElement): HTMLElement
// As `createIcon`, but for the shadow beneath it.
createShadow: function createShadow(oldIcon) {
return this._createIcon('shadow', oldIcon);
},
_createIcon: function _createIcon(name, oldIcon) {
var src = this._getIconUrl(name);
if (!src) {
if (name === 'icon') {
throw new Error('iconUrl not set in Icon options (see the docs).');
}
return null;
}
var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
this._setIconStyles(img, name);
return img;
},
_setIconStyles: function _setIconStyles(img, name) {
var options = this.options;
var sizeOption = options[name + 'Size'];
if (typeof sizeOption === 'number') {
sizeOption = [sizeOption, sizeOption];
}
var size = toPoint(sizeOption),
anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || size && size.divideBy(2, true));
img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
if (anchor) {
img.style.marginLeft = -anchor.x + 'px';
img.style.marginTop = -anchor.y + 'px';
}
if (size) {
img.style.width = size.x + 'px';
img.style.height = size.y + 'px';
}
},
_createImg: function _createImg(src, el) {
el = el || document.createElement('img');
el.src = src;
return el;
},
_getIconUrl: function _getIconUrl(name) {
return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
}
});
// @factory L.icon(options: Icon options)
// Creates an icon instance with the given options.
function icon(options) {
return new Icon(options);
}
/*
* @miniclass Icon.Default (Icon)
* @aka L.Icon.Default
* @section
*
* A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
* no icon is specified. Points to the blue marker image distributed with Leaflet
* releases.
*
* In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
* (which is a set of `Icon options`).
*
* If you want to _completely_ replace the default icon, override the
* `L.Marker.prototype.options.icon` with your own icon instead.
*/
var IconDefault = Icon.extend({
options: {
iconUrl: 'marker-icon.png',
iconRetinaUrl: 'marker-icon-2x.png',
shadowUrl: 'marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowSize: [41, 41]
},
_getIconUrl: function _getIconUrl(name) {
if (!IconDefault.imagePath) {
// Deprecated, backwards-compatibility only
IconDefault.imagePath = this._detectIconPath();
}
// @option imagePath: String
// `Icon.Default` will try to auto-detect the location of the
// blue icon images. If you are placing these images in a non-standard
// way, set this option to point to the right path.
return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
},
_detectIconPath: function _detectIconPath() {
var el = create$1('div', 'leaflet-default-icon-path', document.body);
var path = getStyle(el, 'background-image') || getStyle(el, 'backgroundImage'); // IE8
document.body.removeChild(el);
if (path === null || path.indexOf('url') !== 0) {
path = '';
} else {
path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
}
return path;
}
});
/*
* L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
*/
/* @namespace Marker
* @section Interaction handlers
*
* Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
*
* ```js
* marker.dragging.disable();
* ```
*
* @property dragging: Handler
* Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
*/
var MarkerDrag = Handler.extend({
initialize: function initialize(marker) {
this._marker = marker;
},
addHooks: function addHooks() {
var icon = this._marker._icon;
if (!this._draggable) {
this._draggable = new Draggable(icon, icon, true);
}
this._draggable.on({
dragstart: this._onDragStart,
predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).enable();
addClass(icon, 'leaflet-marker-draggable');
},
removeHooks: function removeHooks() {
this._draggable.off({
dragstart: this._onDragStart,
predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).disable();
if (this._marker._icon) {
removeClass(this._marker._icon, 'leaflet-marker-draggable');
}
},
moved: function moved() {
return this._draggable && this._draggable._moved;
},
_adjustPan: function _adjustPan(e) {
var marker = this._marker,
map = marker._map,
speed = this._marker.options.autoPanSpeed,
padding = this._marker.options.autoPanPadding,
iconPos = getPosition(marker._icon),
bounds = map.getPixelBounds(),
origin = map.getPixelOrigin();
var panBounds = toBounds(bounds.min._subtract(origin).add(padding), bounds.max._subtract(origin).subtract(padding));
if (!panBounds.contains(iconPos)) {
// Compute incremental movement
var movement = toPoint((Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)).multiplyBy(speed);
map.panBy(movement, {
animate: false
});
this._draggable._newPos._add(movement);
this._draggable._startPos._add(movement);
setPosition(marker._icon, this._draggable._newPos);
this._onDrag(e);
this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
}
},
_onDragStart: function _onDragStart() {
// @section Dragging events
// @event dragstart: Event
// Fired when the user starts dragging the marker.
// @event movestart: Event
// Fired when the marker starts moving (because of dragging).
this._oldLatLng = this._marker.getLatLng();
this._marker.closePopup().fire('movestart').fire('dragstart');
},
_onPreDrag: function _onPreDrag(e) {
if (this._marker.options.autoPan) {
cancelAnimFrame(this._panRequest);
this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
}
},
_onDrag: function _onDrag(e) {
var marker = this._marker,
shadow = marker._shadow,
iconPos = getPosition(marker._icon),
latlng = marker._map.layerPointToLatLng(iconPos);
// update shadow position
if (shadow) {
setPosition(shadow, iconPos);
}
marker._latlng = latlng;
e.latlng = latlng;
e.oldLatLng = this._oldLatLng;
// @event drag: Event
// Fired repeatedly while the user drags the marker.
marker.fire('move', e).fire('drag', e);
},
_onDragEnd: function _onDragEnd(e) {
// @event dragend: DragEndEvent
// Fired when the user stops dragging the marker.
cancelAnimFrame(this._panRequest);
// @event moveend: Event
// Fired when the marker stops moving (because of dragging).
delete this._oldLatLng;
this._marker.fire('moveend').fire('dragend', e);
}
});
/*
* @class Marker
* @inherits Interactive layer
* @aka L.Marker
* L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
*
* @example
*
* ```js
* L.marker([50.5, 30.5]).addTo(map);
* ```
*/
var Marker = Layer.extend({
// @section
// @aka Marker options
options: {
// @option icon: Icon = *
// Icon instance to use for rendering the marker.
// See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
// If not specified, a common instance of `L.Icon.Default` is used.
icon: new IconDefault(),
// Option inherited from "Interactive layer" abstract class
interactive: true,
// @option keyboard: Boolean = true
// Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
keyboard: true,
// @option title: String = ''
// Text for the browser tooltip that appear on marker hover (no tooltip by default).
title: '',
// @option alt: String = ''
// Text for the `alt` attribute of the icon image (useful for accessibility).
alt: '',
// @option zIndexOffset: Number = 0
// By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
zIndexOffset: 0,
// @option opacity: Number = 1.0
// The opacity of the marker.
opacity: 1,
// @option riseOnHover: Boolean = false
// If `true`, the marker will get on top of others when you hover the mouse over it.
riseOnHover: false,
// @option riseOffset: Number = 250
// The z-index offset used for the `riseOnHover` feature.
riseOffset: 250,
// @option pane: String = 'markerPane'
// `Map pane` where the markers icon will be added.
pane: 'markerPane',
// @option pane: String = 'shadowPane'
// `Map pane` where the markers shadow will be added.
shadowPane: 'shadowPane',
// @option bubblingMouseEvents: Boolean = false
// When `true`, a mouse event on this marker will trigger the same event on the map
// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
bubblingMouseEvents: false,
// @section Draggable marker options
// @option draggable: Boolean = false
// Whether the marker is draggable with mouse/touch or not.
draggable: false,
// @option autoPan: Boolean = false
// Whether to pan the map when dragging this marker near its edge or not.
autoPan: false,
// @option autoPanPadding: Point = Point(50, 50)
// Distance (in pixels to the left/right and to the top/bottom) of the
// map edge to start panning the map.
autoPanPadding: [50, 50],
// @option autoPanSpeed: Number = 10
// Number of pixels the map should pan by.
autoPanSpeed: 10
},
/* @section
*
* In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
*/
initialize: function initialize(latlng, options) {
setOptions(this, options);
this._latlng = toLatLng(latlng);
},
onAdd: function onAdd(map) {
this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
if (this._zoomAnimated) {
map.on('zoomanim', this._animateZoom, this);
}
this._initIcon();
this.update();
},
onRemove: function onRemove(map) {
if (this.dragging && this.dragging.enabled()) {
this.options.draggable = true;
this.dragging.removeHooks();
}
delete this.dragging;
if (this._zoomAnimated) {
map.off('zoomanim', this._animateZoom, this);
}
this._removeIcon();
this._removeShadow();
},
getEvents: function getEvents() {
return {
zoom: this.update,
viewreset: this.update
};
},
// @method getLatLng: LatLng
// Returns the current geographical position of the marker.
getLatLng: function getLatLng() {
return this._latlng;
},
// @method setLatLng(latlng: LatLng): this
// Changes the marker position to the given point.
setLatLng: function setLatLng(latlng) {
var oldLatLng = this._latlng;
this._latlng = toLatLng(latlng);
this.update();
// @event move: Event
// Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
return this.fire('move', {
oldLatLng: oldLatLng,
latlng: this._latlng
});
},
// @method setZIndexOffset(offset: Number): this
// Changes the [zIndex offset](#marker-zindexoffset) of the marker.
setZIndexOffset: function setZIndexOffset(offset) {
this.options.zIndexOffset = offset;
return this.update();
},
// @method getIcon: Icon
// Returns the current icon used by the marker
getIcon: function getIcon() {
return this.options.icon;
},
// @method setIcon(icon: Icon): this
// Changes the marker icon.
setIcon: function setIcon(icon) {
this.options.icon = icon;
if (this._map) {
this._initIcon();
this.update();
}
if (this._popup) {
this.bindPopup(this._popup, this._popup.options);
}
return this;
},
getElement: function getElement() {
return this._icon;
},
update: function update() {
if (this._icon && this._map) {
var pos = this._map.latLngToLayerPoint(this._latlng).round();
this._setPos(pos);
}
return this;
},
_initIcon: function _initIcon() {
var options = this.options,
classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
var icon = options.icon.createIcon(this._icon),
addIcon = false;
// if we're not reusing the icon, remove the old one and init new one
if (icon !== this._icon) {
if (this._icon) {
this._removeIcon();
}
addIcon = true;
if (options.title) {
icon.title = options.title;
}
if (icon.tagName === 'IMG') {
icon.alt = options.alt || '';
}
}
addClass(icon, classToAdd);
if (options.keyboard) {
icon.tabIndex = '0';
}
this._icon = icon;
if (options.riseOnHover) {
this.on({
mouseover: this._bringToFront,
mouseout: this._resetZIndex
});
}
var newShadow = options.icon.createShadow(this._shadow),
addShadow = false;
if (newShadow !== this._shadow) {
this._removeShadow();
addShadow = true;
}
if (newShadow) {
addClass(newShadow, classToAdd);
newShadow.alt = '';
}
this._shadow = newShadow;
if (options.opacity < 1) {
this._updateOpacity();
}
if (addIcon) {
this.getPane().appendChild(this._icon);
}
this._initInteraction();
if (newShadow && addShadow) {
this.getPane(options.shadowPane).appendChild(this._shadow);
}
},
_removeIcon: function _removeIcon() {
if (this.options.riseOnHover) {
this.off({
mouseover: this._bringToFront,
mouseout: this._resetZIndex
});
}
_remove(this._icon);
this.removeInteractiveTarget(this._icon);
this._icon = null;
},
_removeShadow: function _removeShadow() {
if (this._shadow) {
_remove(this._shadow);
}
this._shadow = null;
},
_setPos: function _setPos(pos) {
if (this._icon) {
setPosition(this._icon, pos);
}
if (this._shadow) {
setPosition(this._shadow, pos);
}
this._zIndex = pos.y + this.options.zIndexOffset;
this._resetZIndex();
},
_updateZIndex: function _updateZIndex(offset) {
if (this._icon) {
this._icon.style.zIndex = this._zIndex + offset;
}
},
_animateZoom: function _animateZoom(opt) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
this._setPos(pos);
},
_initInteraction: function _initInteraction() {
if (!this.options.interactive) {
return;
}
addClass(this._icon, 'leaflet-interactive');
this.addInteractiveTarget(this._icon);
if (MarkerDrag) {
var draggable = this.options.draggable;
if (this.dragging) {
draggable = this.dragging.enabled();
this.dragging.disable();
}
this.dragging = new MarkerDrag(this);
if (draggable) {
this.dragging.enable();
}
}
},
// @method setOpacity(opacity: Number): this
// Changes the opacity of the marker.
setOpacity: function setOpacity(opacity) {
this.options.opacity = opacity;
if (this._map) {
this._updateOpacity();
}
return this;
},
_updateOpacity: function _updateOpacity() {
var opacity = this.options.opacity;
if (this._icon) {
_setOpacity(this._icon, opacity);
}
if (this._shadow) {
_setOpacity(this._shadow, opacity);
}
},
_bringToFront: function _bringToFront() {
this._updateZIndex(this.options.riseOffset);
},
_resetZIndex: function _resetZIndex() {
this._updateZIndex(0);
},
_getPopupAnchor: function _getPopupAnchor() {
return this.options.icon.options.popupAnchor;
},
_getTooltipAnchor: function _getTooltipAnchor() {
return this.options.icon.options.tooltipAnchor;
}
});
// factory L.marker(latlng: LatLng, options? : Marker options)
// @factory L.marker(latlng: LatLng, options? : Marker options)
// Instantiates a Marker object given a geographical point and optionally an options object.
function marker(latlng, options) {
return new Marker(latlng, options);
}
/*
* @class Path
* @aka L.Path
* @inherits Interactive layer
*
* An abstract class that contains options and constants shared between vector
* overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
*/
var Path = Layer.extend({
// @section
// @aka Path options
options: {
// @option stroke: Boolean = true
// Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
stroke: true,
// @option color: String = '#3388ff'
// Stroke color
color: '#3388ff',
// @option weight: Number = 3
// Stroke width in pixels
weight: 3,
// @option opacity: Number = 1.0
// Stroke opacity
opacity: 1,
// @option lineCap: String= 'round'
// A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
lineCap: 'round',
// @option lineJoin: String = 'round'
// A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
lineJoin: 'round',
// @option dashArray: String = null
// A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
dashArray: null,
// @option dashOffset: String = null
// A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
dashOffset: null,
// @option fill: Boolean = depends
// Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
fill: false,
// @option fillColor: String = *
// Fill color. Defaults to the value of the [`color`](#path-color) option
fillColor: null,
// @option fillOpacity: Number = 0.2
// Fill opacity.
fillOpacity: 0.2,
// @option fillRule: String = 'evenodd'
// A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
fillRule: 'evenodd',
// className: '',
// Option inherited from "Interactive layer" abstract class
interactive: true,
// @option bubblingMouseEvents: Boolean = true
// When `true`, a mouse event on this path will trigger the same event on the map
// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
bubblingMouseEvents: true
},
beforeAdd: function beforeAdd(map) {
// Renderer is set here because we need to call renderer.getEvents
// before this.getEvents.
this._renderer = map.getRenderer(this);
},
onAdd: function onAdd() {
this._renderer._initPath(this);
this._reset();
this._renderer._addPath(this);
},
onRemove: function onRemove() {
this._renderer._removePath(this);
},
// @method redraw(): this
// Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
redraw: function redraw() {
if (this._map) {
this._renderer._updatePath(this);
}
return this;
},
// @method setStyle(style: Path options): this
// Changes the appearance of a Path based on the options in the `Path options` object.
setStyle: function setStyle(style) {
setOptions(this, style);
if (this._renderer) {
this._renderer._updateStyle(this);
if (this.options.stroke && style && style.hasOwnProperty('weight')) {
this._updateBounds();
}
}
return this;
},
// @method bringToFront(): this
// Brings the layer to the top of all path layers.
bringToFront: function bringToFront() {
if (this._renderer) {
this._renderer._bringToFront(this);
}
return this;
},
// @method bringToBack(): this
// Brings the layer to the bottom of all path layers.
bringToBack: function bringToBack() {
if (this._renderer) {
this._renderer._bringToBack(this);
}
return this;
},
getElement: function getElement() {
return this._path;
},
_reset: function _reset() {
// defined in child classes
this._project();
this._update();
},
_clickTolerance: function _clickTolerance() {
// used when doing hit detection for Canvas layers
return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
}
});
/*
* @class CircleMarker
* @aka L.CircleMarker
* @inherits Path
*
* A circle of a fixed size with radius specified in pixels. Extends `Path`.
*/
var CircleMarker = Path.extend({
// @section
// @aka CircleMarker options
options: {
fill: true,
// @option radius: Number = 10
// Radius of the circle marker, in pixels
radius: 10
},
initialize: function initialize(latlng, options) {
setOptions(this, options);
this._latlng = toLatLng(latlng);
this._radius = this.options.radius;
},
// @method setLatLng(latLng: LatLng): this
// Sets the position of a circle marker to a new location.
setLatLng: function setLatLng(latlng) {
var oldLatLng = this._latlng;
this._latlng = toLatLng(latlng);
this.redraw();
// @event move: Event
// Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
return this.fire('move', {
oldLatLng: oldLatLng,
latlng: this._latlng
});
},
// @method getLatLng(): LatLng
// Returns the current geographical position of the circle marker
getLatLng: function getLatLng() {
return this._latlng;
},
// @method setRadius(radius: Number): this
// Sets the radius of a circle marker. Units are in pixels.
setRadius: function setRadius(radius) {
this.options.radius = this._radius = radius;
return this.redraw();
},
// @method getRadius(): Number
// Returns the current radius of the circle
getRadius: function getRadius() {
return this._radius;
},
setStyle: function setStyle(options) {
var radius = options && options.radius || this._radius;
Path.prototype.setStyle.call(this, options);
this.setRadius(radius);
return this;
},
_project: function _project() {
this._point = this._map.latLngToLayerPoint(this._latlng);
this._updateBounds();
},
_updateBounds: function _updateBounds() {
var r = this._radius,
r2 = this._radiusY || r,
w = this._clickTolerance(),
p = [r + w, r2 + w];
this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
},
_update: function _update() {
if (this._map) {
this._updatePath();
}
},
_updatePath: function _updatePath() {
this._renderer._updateCircle(this);
},
_empty: function _empty() {
return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
},
// Needed by the `Canvas` renderer for interactivity
_containsPoint: function _containsPoint(p) {
return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
}
});
/*
* @class Circle
* @aka L.Circle
* @inherits CircleMarker
*
* A class for drawing circle overlays on a map. Extends `CircleMarker`.
*
* It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
*
* @example
*
* ```js
* L.circle([50.5, 30.5], {radius: 200}).addTo(map);
* ```
*/
var Circle = CircleMarker.extend({
initialize: function initialize(latlng, options, legacyOptions) {
if (typeof options === 'number') {
// Backwards compatibility with 0.7.x factory (latlng, radius, options?)
options = extend({}, legacyOptions, {
radius: options
});
}
setOptions(this, options);
this._latlng = toLatLng(latlng);
if (isNaN(this.options.radius)) {
throw new Error('Circle radius cannot be NaN');
}
// @section
// @aka Circle options
// @option radius: Number; Radius of the circle, in meters.
this._mRadius = this.options.radius;
},
// @method setRadius(radius: Number): this
// Sets the radius of a circle. Units are in meters.
setRadius: function setRadius(radius) {
this._mRadius = radius;
return this.redraw();
},
// @method getRadius(): Number
// Returns the current radius of a circle. Units are in meters.
getRadius: function getRadius() {
return this._mRadius;
},
// @method getBounds(): LatLngBounds
// Returns the `LatLngBounds` of the path.
getBounds: function getBounds() {
var half = [this._radius, this._radiusY || this._radius];
return new LatLngBounds(this._map.layerPointToLatLng(this._point.subtract(half)), this._map.layerPointToLatLng(this._point.add(half)));
},
setStyle: Path.prototype.setStyle,
_project: function _project() {
var lng = this._latlng.lng,
lat = this._latlng.lat,
map = this._map,
crs = map.options.crs;
if (crs.distance === Earth.distance) {
var d = Math.PI / 180,
latR = this._mRadius / Earth.R / d,
top = map.project([lat + latR, lng]),
bottom = map.project([lat - latR, lng]),
p = top.add(bottom).divideBy(2),
lat2 = map.unproject(p).lat,
lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
if (isNaN(lngR) || lngR === 0) {
lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
}
this._point = p.subtract(map.getPixelOrigin());
this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
this._radiusY = p.y - top.y;
} else {
var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
this._point = map.latLngToLayerPoint(this._latlng);
this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
}
this._updateBounds();
}
});
/*
* @class Polyline
* @aka L.Polyline
* @inherits Path
*
* A class for drawing polyline overlays on a map. Extends `Path`.
*
* @example
*
* ```js
* // create a red polyline from an array of LatLng points
* var latlngs = [
* [45.51, -122.68],
* [37.77, -122.43],
* [34.04, -118.2]
* ];
*
* var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
*
* // zoom the map to the polyline
* map.fitBounds(polyline.getBounds());
* ```
*
* You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
*
* ```js
* // create a red polyline from an array of arrays of LatLng points
* var latlngs = [
* [[45.51, -122.68],
* [37.77, -122.43],
* [34.04, -118.2]],
* [[40.78, -73.91],
* [41.83, -87.62],
* [32.76, -96.72]]
* ];
* ```
*/
var Polyline = Path.extend({
// @section
// @aka Polyline options
options: {
// @option smoothFactor: Number = 1.0
// How much to simplify the polyline on each zoom level. More means
// better performance and smoother look, and less means more accurate representation.
smoothFactor: 1.0,
// @option noClip: Boolean = false
// Disable polyline clipping.
noClip: false
},
initialize: function initialize(latlngs, options) {
setOptions(this, options);
this._setLatLngs(latlngs);
},
// @method getLatLngs(): LatLng[]
// Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
getLatLngs: function getLatLngs() {
return this._latlngs;
},
// @method setLatLngs(latlngs: LatLng[]): this
// Replaces all the points in the polyline with the given array of geographical points.
setLatLngs: function setLatLngs(latlngs) {
this._setLatLngs(latlngs);
return this.redraw();
},
// @method isEmpty(): Boolean
// Returns `true` if the Polyline has no LatLngs.
isEmpty: function isEmpty() {
return !this._latlngs.length;
},
// @method closestLayerPoint(p: Point): Point
// Returns the point closest to `p` on the Polyline.
closestLayerPoint: function closestLayerPoint(p) {
var minDistance = Infinity,
minPoint = null,
closest = _sqClosestPointOnSegment,
p1,
p2;
for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
var points = this._parts[j];
for (var i = 1, len = points.length; i < len; i++) {
p1 = points[i - 1];
p2 = points[i];
var sqDist = closest(p, p1, p2, true);
if (sqDist < minDistance) {
minDistance = sqDist;
minPoint = closest(p, p1, p2);
}
}
}
if (minPoint) {
minPoint.distance = Math.sqrt(minDistance);
}
return minPoint;
},
// @method getCenter(): LatLng
// Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
getCenter: function getCenter() {
// throws error when not yet added to map as this center calculation requires projected coordinates
if (!this._map) {
throw new Error('Must add layer to map before using getCenter()');
}
var i,
halfDist,
segDist,
dist,
p1,
p2,
ratio,
points = this._rings[0],
len = points.length;
if (!len) {
return null;
}
// polyline centroid algorithm; only uses the first ring if there are multiple
for (i = 0, halfDist = 0; i < len - 1; i++) {
halfDist += points[i].distanceTo(points[i + 1]) / 2;
}
// The line is so small in the current view that all points are on the same pixel.
if (halfDist === 0) {
return this._map.layerPointToLatLng(points[0]);
}
for (i = 0, dist = 0; i < len - 1; i++) {
p1 = points[i];
p2 = points[i + 1];
segDist = p1.distanceTo(p2);
dist += segDist;
if (dist > halfDist) {
ratio = (dist - halfDist) / segDist;
return this._map.layerPointToLatLng([p2.x - ratio * (p2.x - p1.x), p2.y - ratio * (p2.y - p1.y)]);
}
}
},
// @method getBounds(): LatLngBounds
// Returns the `LatLngBounds` of the path.
getBounds: function getBounds() {
return this._bounds;
},
// @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
// Adds a given point to the polyline. By default, adds to the first ring of
// the polyline in case of a multi-polyline, but can be overridden by passing
// a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
addLatLng: function addLatLng(latlng, latlngs) {
latlngs = latlngs || this._defaultShape();
latlng = toLatLng(latlng);
latlngs.push(latlng);
this._bounds.extend(latlng);
return this.redraw();
},
_setLatLngs: function _setLatLngs(latlngs) {
this._bounds = new LatLngBounds();
this._latlngs = this._convertLatLngs(latlngs);
},
_defaultShape: function _defaultShape() {
return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
},
// recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
_convertLatLngs: function _convertLatLngs(latlngs) {
var result = [],
flat = isFlat(latlngs);
for (var i = 0, len = latlngs.length; i < len; i++) {
if (flat) {
result[i] = toLatLng(latlngs[i]);
this._bounds.extend(result[i]);
} else {
result[i] = this._convertLatLngs(latlngs[i]);
}
}
return result;
},
_project: function _project() {
var pxBounds = new Bounds();
this._rings = [];
this._projectLatlngs(this._latlngs, this._rings, pxBounds);
if (this._bounds.isValid() && pxBounds.isValid()) {
this._rawPxBounds = pxBounds;
this._updateBounds();
}
},
_updateBounds: function _updateBounds() {
var w = this._clickTolerance(),
p = new Point(w, w);
this._pxBounds = new Bounds([this._rawPxBounds.min.subtract(p), this._rawPxBounds.max.add(p)]);
},
// recursively turns latlngs into a set of rings with projected coordinates
_projectLatlngs: function _projectLatlngs(latlngs, result, projectedBounds) {
var flat = latlngs[0] instanceof LatLng,
len = latlngs.length,
i,
ring;
if (flat) {
ring = [];
for (i = 0; i < len; i++) {
ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
projectedBounds.extend(ring[i]);
}
result.push(ring);
} else {
for (i = 0; i < len; i++) {
this._projectLatlngs(latlngs[i], result, projectedBounds);
}
}
},
// clip polyline by renderer bounds so that we have less to render for performance
_clipPoints: function _clipPoints() {
var bounds = this._renderer._bounds;
this._parts = [];
if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
return;
}
if (this.options.noClip) {
this._parts = this._rings;
return;
}
var parts = this._parts,
i,
j,
k,
len,
len2,
segment,
points;
for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
points = this._rings[i];
for (j = 0, len2 = points.length; j < len2 - 1; j++) {
segment = clipSegment(points[j], points[j + 1], bounds, j, true);
if (!segment) {
continue;
}
parts[k] = parts[k] || [];
parts[k].push(segment[0]);
// if segment goes out of screen, or it's the last one, it's the end of the line part
if (segment[1] !== points[j + 1] || j === len2 - 2) {
parts[k].push(segment[1]);
k++;
}
}
}
},
// simplify each clipped part of the polyline for performance
_simplifyPoints: function _simplifyPoints() {
var parts = this._parts,
tolerance = this.options.smoothFactor;
for (var i = 0, len = parts.length; i < len; i++) {
parts[i] = simplify(parts[i], tolerance);
}
},
_update: function _update() {
if (!this._map) {
return;
}
this._clipPoints();
this._simplifyPoints();
this._updatePath();
},
_updatePath: function _updatePath() {
this._renderer._updatePoly(this);
},
// Needed by the `Canvas` renderer for interactivity
_containsPoint: function _containsPoint(p, closed) {
var i,
j,
k,
len,
len2,
part,
w = this._clickTolerance();
if (!this._pxBounds || !this._pxBounds.contains(p)) {
return false;
}
// hit detection for polylines
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
if (!closed && j === 0) {
continue;
}
if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
return true;
}
}
}
return false;
}
});
// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
Polyline._flat = _flat;
/*
* @class Polygon
* @aka L.Polygon
* @inherits Polyline
*
* A class for drawing polygon overlays on a map. Extends `Polyline`.
*
* Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
*
*
* @example
*
* ```js
* // create a red polygon from an array of LatLng points
* var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
*
* var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
*
* // zoom the map to the polygon
* map.fitBounds(polygon.getBounds());
* ```
*
* You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
*
* ```js
* var latlngs = [
* [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
* [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
* ];
* ```
*
* Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
*
* ```js
* var latlngs = [
* [ // first polygon
* [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
* [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
* ],
* [ // second polygon
* [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
* ]
* ];
* ```
*/
var Polygon = Polyline.extend({
options: {
fill: true
},
isEmpty: function isEmpty() {
return !this._latlngs.length || !this._latlngs[0].length;
},
getCenter: function getCenter() {
// throws error when not yet added to map as this center calculation requires projected coordinates
if (!this._map) {
throw new Error('Must add layer to map before using getCenter()');
}
var i,
j,
p1,
p2,
f,
area,
x,
y,
center,
points = this._rings[0],
len = points.length;
if (!len) {
return null;
}
// polygon centroid algorithm; only uses the first ring if there are multiple
area = x = y = 0;
for (i = 0, j = len - 1; i < len; j = i++) {
p1 = points[i];
p2 = points[j];
f = p1.y * p2.x - p2.y * p1.x;
x += (p1.x + p2.x) * f;
y += (p1.y + p2.y) * f;
area += f * 3;
}
if (area === 0) {
// Polygon is so small that all points are on same pixel.
center = points[0];
} else {
center = [x / area, y / area];
}
return this._map.layerPointToLatLng(center);
},
_convertLatLngs: function _convertLatLngs(latlngs) {
var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
len = result.length;
// remove last point if it equals first one
if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
result.pop();
}
return result;
},
_setLatLngs: function _setLatLngs(latlngs) {
Polyline.prototype._setLatLngs.call(this, latlngs);
if (isFlat(this._latlngs)) {
this._latlngs = [this._latlngs];
}
},
_defaultShape: function _defaultShape() {
return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
},
_clipPoints: function _clipPoints() {
// polygons need a different clipping algorithm so we redefine that
var bounds = this._renderer._bounds,
w = this.options.weight,
p = new Point(w, w);
// increase clip padding by stroke width to avoid stroke on clip edges
bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
this._parts = [];
if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
return;
}
if (this.options.noClip) {
this._parts = this._rings;
return;
}
for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
clipped = clipPolygon(this._rings[i], bounds, true);
if (clipped.length) {
this._parts.push(clipped);
}
}
},
_updatePath: function _updatePath() {
this._renderer._updatePoly(this, true);
},
// Needed by the `Canvas` renderer for interactivity
_containsPoint: function _containsPoint(p) {
var inside = false,
part,
p1,
p2,
i,
j,
k,
len,
len2;
if (!this._pxBounds || !this._pxBounds.contains(p)) {
return false;
}
// ray casting algorithm for detecting if point is in polygon
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
p1 = part[j];
p2 = part[k];
if (p1.y > p.y !== p2.y > p.y && p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x) {
inside = !inside;
}
}
}
// also check if it's on polygon stroke
return inside || Polyline.prototype._containsPoint.call(this, p, true);
}
});
/*
* @class GeoJSON
* @aka L.GeoJSON
* @inherits FeatureGroup
*
* Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
* GeoJSON data and display it on the map. Extends `FeatureGroup`.
*
* @example
*
* ```js
* L.geoJSON(data, {
* style: function (feature) {
* return {color: feature.properties.color};
* }
* }).bindPopup(function (layer) {
* return layer.feature.properties.description;
* }).addTo(map);
* ```
*/
var GeoJSON = FeatureGroup.extend({
/* @section
* @aka GeoJSON options
*
* @option pointToLayer: Function = *
* A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
* called when data is added, passing the GeoJSON point feature and its `LatLng`.
* The default is to spawn a default `Marker`:
* ```js
* function(geoJsonPoint, latlng) {
* return L.marker(latlng);
* }
* ```
*
* @option style: Function = *
* A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
* called internally when data is added.
* The default value is to not override any defaults:
* ```js
* function (geoJsonFeature) {
* return {}
* }
* ```
*
* @option onEachFeature: Function = *
* A `Function` that will be called once for each created `Feature`, after it has
* been created and styled. Useful for attaching events and popups to features.
* The default is to do nothing with the newly created layers:
* ```js
* function (feature, layer) {}
* ```
*
* @option filter: Function = *
* A `Function` that will be used to decide whether to include a feature or not.
* The default is to include all features:
* ```js
* function (geoJsonFeature) {
* return true;
* }
* ```
* Note: dynamically changing the `filter` option will have effect only on newly
* added data. It will _not_ re-evaluate already included features.
*
* @option coordsToLatLng: Function = *
* A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
* The default is the `coordsToLatLng` static method.
*
* @option markersInheritOptions: Boolean = false
* Whether default Markers for "Point" type Features inherit from group options.
*/
initialize: function initialize(geojson, options) {
setOptions(this, options);
this._layers = {};
if (geojson) {
this.addData(geojson);
}
},
// @method addData( <GeoJSON> data ): this
// Adds a GeoJSON object to the layer.
addData: function addData(geojson) {
var features = isArray(geojson) ? geojson : geojson.features,
i,
len,
feature;
if (features) {
for (i = 0, len = features.length; i < len; i++) {
// only add this if geometry or geometries are set and not null
feature = features[i];
if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
this.addData(feature);
}
}
return this;
}
var options = this.options;
if (options.filter && !options.filter(geojson)) {
return this;
}
var layer = geometryToLayer(geojson, options);
if (!layer) {
return this;
}
layer.feature = asFeature(geojson);
layer.defaultOptions = layer.options;
this.resetStyle(layer);
if (options.onEachFeature) {
options.onEachFeature(geojson, layer);
}
return this.addLayer(layer);
},
// @method resetStyle( <Path> layer? ): this
// Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
// If `layer` is omitted, the style of all features in the current layer is reset.
resetStyle: function resetStyle(layer) {
if (layer === undefined) {
return this.eachLayer(this.resetStyle, this);
}
// reset any custom styles
layer.options = extend({}, layer.defaultOptions);
this._setLayerStyle(layer, this.options.style);
return this;
},
// @method setStyle( <Function> style ): this
// Changes styles of GeoJSON vector layers with the given style function.
setStyle: function setStyle(style) {
return this.eachLayer(function (layer) {
this._setLayerStyle(layer, style);
}, this);
},
_setLayerStyle: function _setLayerStyle(layer, style) {
if (layer.setStyle) {
if (typeof style === 'function') {
style = style(layer.feature);
}
layer.setStyle(style);
}
}
});
// @section
// There are several static functions which can be called without instantiating L.GeoJSON:
// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
// Creates a `Layer` from a given GeoJSON feature. Can use a custom
// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
// functions if provided as options.
function geometryToLayer(geojson, options) {
var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
coords = geometry ? geometry.coordinates : null,
layers = [],
pointToLayer = options && options.pointToLayer,
_coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
latlng,
latlngs,
i,
len;
if (!coords && !geometry) {
return null;
}
switch (geometry.type) {
case 'Point':
latlng = _coordsToLatLng(coords);
return _pointToLayer(pointToLayer, geojson, latlng, options);
case 'MultiPoint':
for (i = 0, len = coords.length; i < len; i++) {
latlng = _coordsToLatLng(coords[i]);
layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
}
return new FeatureGroup(layers);
case 'LineString':
case 'MultiLineString':
latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
return new Polyline(latlngs, options);
case 'Polygon':
case 'MultiPolygon':
latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
return new Polygon(latlngs, options);
case 'GeometryCollection':
for (i = 0, len = geometry.geometries.length; i < len; i++) {
var layer = geometryToLayer({
geometry: geometry.geometries[i],
type: 'Feature',
properties: geojson.properties
}, options);
if (layer) {
layers.push(layer);
}
}
return new FeatureGroup(layers);
default:
throw new Error('Invalid GeoJSON object.');
}
}
function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
return pointToLayerFn ? pointToLayerFn(geojson, latlng) : new Marker(latlng, options && options.markersInheritOptions && options);
}
// @function coordsToLatLng(coords: Array): LatLng
// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
function coordsToLatLng(coords) {
return new LatLng(coords[1], coords[0], coords[2]);
}
// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
var latlngs = [];
for (var i = 0, len = coords.length, latlng; i < len; i++) {
latlng = levelsDeep ? coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : (_coordsToLatLng || coordsToLatLng)(coords[i]);
latlngs.push(latlng);
}
return latlngs;
}
// @function latLngToCoords(latlng: LatLng, precision?: Number): Array
// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
function latLngToCoords(latlng, precision) {
precision = typeof precision === 'number' ? precision : 6;
return latlng.alt !== undefined ? [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
}
// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
var coords = [];
for (var i = 0, len = latlngs.length; i < len; i++) {
coords.push(levelsDeep ? latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) : latLngToCoords(latlngs[i], precision));
}
if (!levelsDeep && closed) {
coords.push(coords[0]);
}
return coords;
}
function getFeature(layer, newGeometry) {
return layer.feature ? extend({}, layer.feature, {
geometry: newGeometry
}) : asFeature(newGeometry);
}
// @function asFeature(geojson: Object): Object
// Normalize GeoJSON geometries/features into GeoJSON features.
function asFeature(geojson) {
if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
return geojson;
}
return {
type: 'Feature',
properties: {},
geometry: geojson
};
}
var PointToGeoJSON = {
toGeoJSON: function toGeoJSON(precision) {
return getFeature(this, {
type: 'Point',
coordinates: latLngToCoords(this.getLatLng(), precision)
});
}
};
// @namespace Marker
// @section Other methods
// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
Marker.include(PointToGeoJSON);
// @namespace CircleMarker
// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
Circle.include(PointToGeoJSON);
CircleMarker.include(PointToGeoJSON);
// @namespace Polyline
// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
Polyline.include({
toGeoJSON: function toGeoJSON(precision) {
var multi = !isFlat(this._latlngs);
var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
return getFeature(this, {
type: (multi ? 'Multi' : '') + 'LineString',
coordinates: coords
});
}
});
// @namespace Polygon
// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
Polygon.include({
toGeoJSON: function toGeoJSON(precision) {
var holes = !isFlat(this._latlngs),
multi = holes && !isFlat(this._latlngs[0]);
var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
if (!holes) {
coords = [coords];
}
return getFeature(this, {
type: (multi ? 'Multi' : '') + 'Polygon',
coordinates: coords
});
}
});
// @namespace LayerGroup
LayerGroup.include({
toMultiPoint: function toMultiPoint(precision) {
var coords = [];
this.eachLayer(function (layer) {
coords.push(layer.toGeoJSON(precision).geometry.coordinates);
});
return getFeature(this, {
type: 'MultiPoint',
coordinates: coords
});
},
// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
toGeoJSON: function toGeoJSON(precision) {
var type = this.feature && this.feature.geometry && this.feature.geometry.type;
if (type === 'MultiPoint') {
return this.toMultiPoint(precision);
}
var isGeometryCollection = type === 'GeometryCollection',
jsons = [];
this.eachLayer(function (layer) {
if (layer.toGeoJSON) {
var json = layer.toGeoJSON(precision);
if (isGeometryCollection) {
jsons.push(json.geometry);
} else {
var feature = asFeature(json);
// Squash nested feature collections
if (feature.type === 'FeatureCollection') {
jsons.push.apply(jsons, feature.features);
} else {
jsons.push(feature);
}
}
}
});
if (isGeometryCollection) {
return getFeature(this, {
geometries: jsons,
type: 'GeometryCollection'
});
}
return {
type: 'FeatureCollection',
features: jsons
};
}
});
/*
* @class ImageOverlay
* @aka L.ImageOverlay
* @inherits Interactive layer
*
* Used to load and display a single image over specific bounds of the map. Extends `Layer`.
*
* @example
*
* ```js
* var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
* imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
* L.imageOverlay(imageUrl, imageBounds).addTo(map);
* ```
*/
var ImageOverlay = Layer.extend({
// @section
// @aka ImageOverlay options
options: {
// @option opacity: Number = 1.0
// The opacity of the image overlay.
opacity: 1,
// @option alt: String = ''
// Text for the `alt` attribute of the image (useful for accessibility).
alt: '',
// @option interactive: Boolean = false
// If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
interactive: false,
// @option crossOrigin: Boolean|String = false
// Whether the crossOrigin attribute will be added to the image.
// If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
crossOrigin: false,
// @option errorOverlayUrl: String = ''
// URL to the overlay image to show in place of the overlay that failed to load.
errorOverlayUrl: '',
// @option zIndex: Number = 1
// The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
zIndex: 1,
// @option className: String = ''
// A custom class name to assign to the image. Empty by default.
className: ''
},
initialize: function initialize(url, bounds, options) {
// (String, LatLngBounds, Object)
this._url = url;
this._bounds = toLatLngBounds(bounds);
setOptions(this, options);
},
onAdd: function onAdd() {
if (!this._image) {
this._initImage();
if (this.options.opacity < 1) {
this._updateOpacity();
}
}
if (this.options.interactive) {
addClass(this._image, 'leaflet-interactive');
this.addInteractiveTarget(this._image);
}
this.getPane().appendChild(this._image);
this._reset();
},
onRemove: function onRemove() {
_remove(this._image);
if (this.options.interactive) {
this.removeInteractiveTarget(this._image);
}
},
// @method setOpacity(opacity: Number): this
// Sets the opacity of the overlay.
setOpacity: function setOpacity(opacity) {
this.options.opacity = opacity;
if (this._image) {
this._updateOpacity();
}
return this;
},
setStyle: function setStyle(styleOpts) {
if (styleOpts.opacity) {
this.setOpacity(styleOpts.opacity);
}
return this;
},
// @method bringToFront(): this
// Brings the layer to the top of all overlays.
bringToFront: function bringToFront() {
if (this._map) {
toFront(this._image);
}
return this;
},
// @method bringToBack(): this
// Brings the layer to the bottom of all overlays.
bringToBack: function bringToBack() {
if (this._map) {
toBack(this._image);
}
return this;
},
// @method setUrl(url: String): this
// Changes the URL of the image.
setUrl: function setUrl(url) {
this._url = url;
if (this._image) {
this._image.src = url;
}
return this;
},
// @method setBounds(bounds: LatLngBounds): this
// Update the bounds that this ImageOverlay covers
setBounds: function setBounds(bounds) {
this._bounds = toLatLngBounds(bounds);
if (this._map) {
this._reset();
}
return this;
},
getEvents: function getEvents() {
var events = {
zoom: this._reset,
viewreset: this._reset
};
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
}
return events;
},
// @method setZIndex(value: Number): this
// Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
setZIndex: function setZIndex(value) {
this.options.zIndex = value;
this._updateZIndex();
return this;
},
// @method getBounds(): LatLngBounds
// Get the bounds that this ImageOverlay covers
getBounds: function getBounds() {
return this._bounds;
},
// @method getElement(): HTMLElement
// Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
// used by this overlay.
getElement: function getElement() {
return this._image;
},
_initImage: function _initImage() {
var wasElementSupplied = this._url.tagName === 'IMG';
var img = this._image = wasElementSupplied ? this._url : create$1('img');
addClass(img, 'leaflet-image-layer');
if (this._zoomAnimated) {
addClass(img, 'leaflet-zoom-animated');
}
if (this.options.className) {
addClass(img, this.options.className);
}
img.onselectstart = falseFn;
img.onmousemove = falseFn;
// @event load: Event
// Fired when the ImageOverlay layer has loaded its image
img.onload = bind(this.fire, this, 'load');
img.onerror = bind(this._overlayOnError, this, 'error');
if (this.options.crossOrigin || this.options.crossOrigin === '') {
img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
}
if (this.options.zIndex) {
this._updateZIndex();
}
if (wasElementSupplied) {
this._url = img.src;
return;
}
img.src = this._url;
img.alt = this.options.alt;
},
_animateZoom: function _animateZoom(e) {
var scale = this._map.getZoomScale(e.zoom),
offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
setTransform(this._image, offset, scale);
},
_reset: function _reset() {
var image = this._image,
bounds = new Bounds(this._map.latLngToLayerPoint(this._bounds.getNorthWest()), this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
size = bounds.getSize();
setPosition(image, bounds.min);
image.style.width = size.x + 'px';
image.style.height = size.y + 'px';
},
_updateOpacity: function _updateOpacity() {
_setOpacity(this._image, this.options.opacity);
},
_updateZIndex: function _updateZIndex() {
if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
this._image.style.zIndex = this.options.zIndex;
}
},
_overlayOnError: function _overlayOnError() {
// @event error: Event
// Fired when the ImageOverlay layer fails to load its image
this.fire('error');
var errorUrl = this.options.errorOverlayUrl;
if (errorUrl && this._url !== errorUrl) {
this._url = errorUrl;
this._image.src = errorUrl;
}
}
});
/*
* @class VideoOverlay
* @aka L.VideoOverlay
* @inherits ImageOverlay
*
* Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
*
* A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
* HTML5 element.
*
* @example
*
* ```js
* var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
* videoBounds = [[ 32, -130], [ 13, -100]];
* L.videoOverlay(videoUrl, videoBounds ).addTo(map);
* ```
*/
var VideoOverlay = ImageOverlay.extend({
// @section
// @aka VideoOverlay options
options: {
// @option autoplay: Boolean = true
// Whether the video starts playing automatically when loaded.
autoplay: true,
// @option loop: Boolean = true
// Whether the video will loop back to the beginning when played.
loop: true,
// @option keepAspectRatio: Boolean = true
// Whether the video will save aspect ratio after the projection.
// Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
keepAspectRatio: true
},
_initImage: function _initImage() {
var wasElementSupplied = this._url.tagName === 'VIDEO';
var vid = this._image = wasElementSupplied ? this._url : create$1('video');
addClass(vid, 'leaflet-image-layer');
if (this._zoomAnimated) {
addClass(vid, 'leaflet-zoom-animated');
}
if (this.options.className) {
addClass(vid, this.options.className);
}
vid.onselectstart = falseFn;
vid.onmousemove = falseFn;
// @event load: Event
// Fired when the video has finished loading the first frame
vid.onloadeddata = bind(this.fire, this, 'load');
if (wasElementSupplied) {
var sourceElements = vid.getElementsByTagName('source');
var sources = [];
for (var j = 0; j < sourceElements.length; j++) {
sources.push(sourceElements[j].src);
}
this._url = sourceElements.length > 0 ? sources : [vid.src];
return;
}
if (!isArray(this._url)) {
this._url = [this._url];
}
if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) {
vid.style['objectFit'] = 'fill';
}
vid.autoplay = !!this.options.autoplay;
vid.loop = !!this.options.loop;
for (var i = 0; i < this._url.length; i++) {
var source = create$1('source');
source.src = this._url[i];
vid.appendChild(source);
}
}
// @method getElement(): HTMLVideoElement
// Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
// used by this overlay.
});
/*
* @class SVGOverlay
* @aka L.SVGOverlay
* @inherits ImageOverlay
*
* Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
*
* An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
*
* @example
*
* ```js
* var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
* svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
* svgElement.setAttribute('viewBox', "0 0 200 200");
* svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
* var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
* L.svgOverlay(svgElement, svgElementBounds).addTo(map);
* ```
*/
var SVGOverlay = ImageOverlay.extend({
_initImage: function _initImage() {
var el = this._image = this._url;
addClass(el, 'leaflet-image-layer');
if (this._zoomAnimated) {
addClass(el, 'leaflet-zoom-animated');
}
if (this.options.className) {
addClass(el, this.options.className);
}
el.onselectstart = falseFn;
el.onmousemove = falseFn;
}
// @method getElement(): SVGElement
// Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
// used by this overlay.
});
/*
* @class DivOverlay
* @inherits Layer
* @aka L.DivOverlay
* Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
*/
// @namespace DivOverlay
var DivOverlay = Layer.extend({
// @section
// @aka DivOverlay options
options: {
// @option offset: Point = Point(0, 7)
// The offset of the popup position. Useful to control the anchor
// of the popup when opening it on some overlays.
offset: [0, 7],
// @option className: String = ''
// A custom CSS class name to assign to the popup.
className: '',
// @option pane: String = 'popupPane'
// `Map pane` where the popup will be added.
pane: 'popupPane'
},
initialize: function initialize(options, source) {
setOptions(this, options);
this._source = source;
},
onAdd: function onAdd(map) {
this._zoomAnimated = map._zoomAnimated;
if (!this._container) {
this._initLayout();
}
if (map._fadeAnimated) {
_setOpacity(this._container, 0);
}
clearTimeout(this._removeTimeout);
this.getPane().appendChild(this._container);
this.update();
if (map._fadeAnimated) {
_setOpacity(this._container, 1);
}
this.bringToFront();
},
onRemove: function onRemove(map) {
if (map._fadeAnimated) {
_setOpacity(this._container, 0);
this._removeTimeout = setTimeout(bind(_remove, undefined, this._container), 200);
} else {
_remove(this._container);
}
},
// @namespace Popup
// @method getLatLng: LatLng
// Returns the geographical point of popup.
getLatLng: function getLatLng() {
return this._latlng;
},
// @method setLatLng(latlng: LatLng): this
// Sets the geographical point where the popup will open.
setLatLng: function setLatLng(latlng) {
this._latlng = toLatLng(latlng);
if (this._map) {
this._updatePosition();
this._adjustPan();
}
return this;
},
// @method getContent: String|HTMLElement
// Returns the content of the popup.
getContent: function getContent() {
return this._content;
},
// @method setContent(htmlContent: String|HTMLElement|Function): this
// Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
setContent: function setContent(content) {
this._content = content;
this.update();
return this;
},
// @method getElement: String|HTMLElement
// Alias for [getContent()](#popup-getcontent)
getElement: function getElement() {
return this._container;
},
// @method update: null
// Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
update: function update() {
if (!this._map) {
return;
}
this._container.style.visibility = 'hidden';
this._updateContent();
this._updateLayout();
this._updatePosition();
this._container.style.visibility = '';
this._adjustPan();
},
getEvents: function getEvents() {
var events = {
zoom: this._updatePosition,
viewreset: this._updatePosition
};
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
}
return events;
},
// @method isOpen: Boolean
// Returns `true` when the popup is visible on the map.
isOpen: function isOpen() {
return !!this._map && this._map.hasLayer(this);
},
// @method bringToFront: this
// Brings this popup in front of other popups (in the same map pane).
bringToFront: function bringToFront() {
if (this._map) {
toFront(this._container);
}
return this;
},
// @method bringToBack: this
// Brings this popup to the back of other popups (in the same map pane).
bringToBack: function bringToBack() {
if (this._map) {
toBack(this._container);
}
return this;
},
_prepareOpen: function _prepareOpen(parent, layer, latlng) {
if (!(layer instanceof Layer)) {
latlng = layer;
layer = parent;
}
if (layer instanceof FeatureGroup) {
for (var id in parent._layers) {
layer = parent._layers[id];
break;
}
}
if (!latlng) {
if (layer.getCenter) {
latlng = layer.getCenter();
} else if (layer.getLatLng) {
latlng = layer.getLatLng();
} else {
throw new Error('Unable to get source layer LatLng.');
}
}
// set overlay source to this layer
this._source = layer;
// update the overlay (content, layout, ect...)
this.update();
return latlng;
},
_updateContent: function _updateContent() {
if (!this._content) {
return;
}
var node = this._contentNode;
var content = typeof this._content === 'function' ? this._content(this._source || this) : this._content;
if (typeof content === 'string') {
node.innerHTML = content;
} else {
while (node.hasChildNodes()) {
node.removeChild(node.firstChild);
}
node.appendChild(content);
}
this.fire('contentupdate');
},
_updatePosition: function _updatePosition() {
if (!this._map) {
return;
}
var pos = this._map.latLngToLayerPoint(this._latlng),
offset = toPoint(this.options.offset),
anchor = this._getAnchor();
if (this._zoomAnimated) {
setPosition(this._container, pos.add(anchor));
} else {
offset = offset.add(pos).add(anchor);
}
var bottom = this._containerBottom = -offset.y,
left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
// bottom position the popup in case the height of the popup changes (images loading etc)
this._container.style.bottom = bottom + 'px';
this._container.style.left = left + 'px';
},
_getAnchor: function _getAnchor() {
return [0, 0];
}
});
/*
* @class Popup
* @inherits DivOverlay
* @aka L.Popup
* Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
* open popups while making sure that only one popup is open at one time
* (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
*
* @example
*
* If you want to just bind a popup to marker click and then open it, it's really easy:
*
* ```js
* marker.bindPopup(popupContent).openPopup();
* ```
* Path overlays like polylines also have a `bindPopup` method.
* Here's a more complicated way to open a popup on a map:
*
* ```js
* var popup = L.popup()
* .setLatLng(latlng)
* .setContent('<p>Hello world!<br />This is a nice popup.</p>')
* .openOn(map);
* ```
*/
// @namespace Popup
var Popup = DivOverlay.extend({
// @section
// @aka Popup options
options: {
// @option maxWidth: Number = 300
// Max width of the popup, in pixels.
maxWidth: 300,
// @option minWidth: Number = 50
// Min width of the popup, in pixels.
minWidth: 50,
// @option maxHeight: Number = null
// If set, creates a scrollable container of the given height
// inside a popup if its content exceeds it.
maxHeight: null,
// @option autoPan: Boolean = true
// Set it to `false` if you don't want the map to do panning animation
// to fit the opened popup.
autoPan: true,
// @option autoPanPaddingTopLeft: Point = null
// The margin between the popup and the top left corner of the map
// view after autopanning was performed.
autoPanPaddingTopLeft: null,
// @option autoPanPaddingBottomRight: Point = null
// The margin between the popup and the bottom right corner of the map
// view after autopanning was performed.
autoPanPaddingBottomRight: null,
// @option autoPanPadding: Point = Point(5, 5)
// Equivalent of setting both top left and bottom right autopan padding to the same value.
autoPanPadding: [5, 5],
// @option keepInView: Boolean = false
// Set it to `true` if you want to prevent users from panning the popup
// off of the screen while it is open.
keepInView: false,
// @option closeButton: Boolean = true
// Controls the presence of a close button in the popup.
closeButton: true,
// @option autoClose: Boolean = true
// Set it to `false` if you want to override the default behavior of
// the popup closing when another popup is opened.
autoClose: true,
// @option closeOnEscapeKey: Boolean = true
// Set it to `false` if you want to override the default behavior of
// the ESC key for closing of the popup.
closeOnEscapeKey: true,
// @option closeOnClick: Boolean = *
// Set it if you want to override the default behavior of the popup closing when user clicks
// on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
// @option className: String = ''
// A custom CSS class name to assign to the popup.
className: ''
},
// @namespace Popup
// @method openOn(map: Map): this
// Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
openOn: function openOn(map) {
map.openPopup(this);
return this;
},
onAdd: function onAdd(map) {
DivOverlay.prototype.onAdd.call(this, map);
// @namespace Map
// @section Popup events
// @event popupopen: PopupEvent
// Fired when a popup is opened in the map
map.fire('popupopen', {
popup: this
});
if (this._source) {
// @namespace Layer
// @section Popup events
// @event popupopen: PopupEvent
// Fired when a popup bound to this layer is opened
this._source.fire('popupopen', {
popup: this
}, true);
// For non-path layers, we toggle the popup when clicking
// again the layer, so prevent the map to reopen it.
if (!(this._source instanceof Path)) {
this._source.on('preclick', stopPropagation);
}
}
},
onRemove: function onRemove(map) {
DivOverlay.prototype.onRemove.call(this, map);
// @namespace Map
// @section Popup events
// @event popupclose: PopupEvent
// Fired when a popup in the map is closed
map.fire('popupclose', {
popup: this
});
if (this._source) {
// @namespace Layer
// @section Popup events
// @event popupclose: PopupEvent
// Fired when a popup bound to this layer is closed
this._source.fire('popupclose', {
popup: this
}, true);
if (!(this._source instanceof Path)) {
this._source.off('preclick', stopPropagation);
}
}
},
getEvents: function getEvents() {
var events = DivOverlay.prototype.getEvents.call(this);
if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
events.preclick = this._close;
}
if (this.options.keepInView) {
events.moveend = this._adjustPan;
}
return events;
},
_close: function _close() {
if (this._map) {
this._map.closePopup(this);
}
},
_initLayout: function _initLayout() {
var prefix = 'leaflet-popup',
container = this._container = create$1('div', prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-animated');
var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
this._contentNode = create$1('div', prefix + '-content', wrapper);
disableClickPropagation(wrapper);
disableScrollPropagation(this._contentNode);
on(wrapper, 'contextmenu', stopPropagation);
this._tipContainer = create$1('div', prefix + '-tip-container', container);
this._tip = create$1('div', prefix + '-tip', this._tipContainer);
if (this.options.closeButton) {
var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
closeButton.href = '#close';
closeButton.innerHTML = '×';
on(closeButton, 'click', this._onCloseButtonClick, this);
}
},
_updateLayout: function _updateLayout() {
var container = this._contentNode,
style = container.style;
style.width = '';
style.whiteSpace = 'nowrap';
var width = container.offsetWidth;
width = Math.min(width, this.options.maxWidth);
width = Math.max(width, this.options.minWidth);
style.width = width + 1 + 'px';
style.whiteSpace = '';
style.height = '';
var height = container.offsetHeight,
maxHeight = this.options.maxHeight,
scrolledClass = 'leaflet-popup-scrolled';
if (maxHeight && height > maxHeight) {
style.height = maxHeight + 'px';
addClass(container, scrolledClass);
} else {
removeClass(container, scrolledClass);
}
this._containerWidth = this._container.offsetWidth;
},
_animateZoom: function _animateZoom(e) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
anchor = this._getAnchor();
setPosition(this._container, pos.add(anchor));
},
_adjustPan: function _adjustPan() {
if (!this.options.autoPan) {
return;
}
if (this._map._panAnim) {
this._map._panAnim.stop();
}
var map = this._map,
marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
containerHeight = this._container.offsetHeight + marginBottom,
containerWidth = this._containerWidth,
layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
layerPos._add(getPosition(this._container));
var containerPos = map.layerPointToContainerPoint(layerPos),
padding = toPoint(this.options.autoPanPadding),
paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
size = map.getSize(),
dx = 0,
dy = 0;
if (containerPos.x + containerWidth + paddingBR.x > size.x) {
// right
dx = containerPos.x + containerWidth - size.x + paddingBR.x;
}
if (containerPos.x - dx - paddingTL.x < 0) {
// left
dx = containerPos.x - paddingTL.x;
}
if (containerPos.y + containerHeight + paddingBR.y > size.y) {
// bottom
dy = containerPos.y + containerHeight - size.y + paddingBR.y;
}
if (containerPos.y - dy - paddingTL.y < 0) {
// top
dy = containerPos.y - paddingTL.y;
}
// @namespace Map
// @section Popup events
// @event autopanstart: Event
// Fired when the map starts autopanning when opening a popup.
if (dx || dy) {
map.fire('autopanstart').panBy([dx, dy]);
}
},
_onCloseButtonClick: function _onCloseButtonClick(e) {
this._close();
stop(e);
},
_getAnchor: function _getAnchor() {
// Where should we anchor the popup on the source layer?
return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
}
});
/* @namespace Map
* @section Interaction Options
* @option closePopupOnClick: Boolean = true
* Set it to `false` if you don't want popups to close when user clicks the map.
*/
Map.mergeOptions({
closePopupOnClick: true
});
// @namespace Map
// @section Methods for Layers and Controls
Map.include({
// @method openPopup(popup: Popup): this
// Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
// @alternative
// @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
// Creates a popup with the specified content and options and opens it in the given point on a map.
openPopup: function openPopup(popup, latlng, options) {
if (!(popup instanceof Popup)) {
popup = new Popup(options).setContent(popup);
}
if (latlng) {
popup.setLatLng(latlng);
}
if (this.hasLayer(popup)) {
return this;
}
if (this._popup && this._popup.options.autoClose) {
this.closePopup();
}
this._popup = popup;
return this.addLayer(popup);
},
// @method closePopup(popup?: Popup): this
// Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
closePopup: function closePopup(popup) {
if (!popup || popup === this._popup) {
popup = this._popup;
this._popup = null;
}
if (popup) {
this.removeLayer(popup);
}
return this;
}
});
/*
* @namespace Layer
* @section Popup methods example
*
* All layers share a set of methods convenient for binding popups to it.
*
* ```js
* var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
* layer.openPopup();
* layer.closePopup();
* ```
*
* Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
*/
// @section Popup methods
Layer.include({
// @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
// Binds a popup to the layer with the passed `content` and sets up the
// necessary event listeners. If a `Function` is passed it will receive
// the layer as the first argument and should return a `String` or `HTMLElement`.
bindPopup: function bindPopup(content, options) {
if (content instanceof Popup) {
setOptions(content, options);
this._popup = content;
content._source = this;
} else {
if (!this._popup || options) {
this._popup = new Popup(options, this);
}
this._popup.setContent(content);
}
if (!this._popupHandlersAdded) {
this.on({
click: this._openPopup,
keypress: this._onKeyPress,
remove: this.closePopup,
move: this._movePopup
});
this._popupHandlersAdded = true;
}
return this;
},
// @method unbindPopup(): this
// Removes the popup previously bound with `bindPopup`.
unbindPopup: function unbindPopup() {
if (this._popup) {
this.off({
click: this._openPopup,
keypress: this._onKeyPress,
remove: this.closePopup,
move: this._movePopup
});
this._popupHandlersAdded = false;
this._popup = null;
}
return this;
},
// @method openPopup(latlng?: LatLng): this
// Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
openPopup: function openPopup(layer, latlng) {
if (this._popup && this._map) {
latlng = this._popup._prepareOpen(this, layer, latlng);
// open the popup on the map
this._map.openPopup(this._popup, latlng);
}
return this;
},
// @method closePopup(): this
// Closes the popup bound to this layer if it is open.
closePopup: function closePopup() {
if (this._popup) {
this._popup._close();
}
return this;
},
// @method togglePopup(): this
// Opens or closes the popup bound to this layer depending on its current state.
togglePopup: function togglePopup(target) {
if (this._popup) {
if (this._popup._map) {
this.closePopup();
} else {
this.openPopup(target);
}
}
return this;
},
// @method isPopupOpen(): boolean
// Returns `true` if the popup bound to this layer is currently open.
isPopupOpen: function isPopupOpen() {
return this._popup ? this._popup.isOpen() : false;
},
// @method setPopupContent(content: String|HTMLElement|Popup): this
// Sets the content of the popup bound to this layer.
setPopupContent: function setPopupContent(content) {
if (this._popup) {
this._popup.setContent(content);
}
return this;
},
// @method getPopup(): Popup
// Returns the popup bound to this layer.
getPopup: function getPopup() {
return this._popup;
},
_openPopup: function _openPopup(e) {
var layer = e.layer || e.target;
if (!this._popup) {
return;
}
if (!this._map) {
return;
}
// prevent map click
stop(e);
// if this inherits from Path its a vector and we can just
// open the popup at the new location
if (layer instanceof Path) {
this.openPopup(e.layer || e.target, e.latlng);
return;
}
// otherwise treat it like a marker and figure out
// if we should toggle it open/closed
if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
this.closePopup();
} else {
this.openPopup(layer, e.latlng);
}
},
_movePopup: function _movePopup(e) {
this._popup.setLatLng(e.latlng);
},
_onKeyPress: function _onKeyPress(e) {
if (e.originalEvent.keyCode === 13) {
this._openPopup(e);
}
}
});
/*
* @class Tooltip
* @inherits DivOverlay
* @aka L.Tooltip
* Used to display small texts on top of map layers.
*
* @example
*
* ```js
* marker.bindTooltip("my tooltip text").openTooltip();
* ```
* Note about tooltip offset. Leaflet takes two options in consideration
* for computing tooltip offsetting:
* - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
* Add a positive x offset to move the tooltip to the right, and a positive y offset to
* move it to the bottom. Negatives will move to the left and top.
* - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
* should adapt this value if you use a custom icon.
*/
// @namespace Tooltip
var Tooltip = DivOverlay.extend({
// @section
// @aka Tooltip options
options: {
// @option pane: String = 'tooltipPane'
// `Map pane` where the tooltip will be added.
pane: 'tooltipPane',
// @option offset: Point = Point(0, 0)
// Optional offset of the tooltip position.
offset: [0, 0],
// @option direction: String = 'auto'
// Direction where to open the tooltip. Possible values are: `right`, `left`,
// `top`, `bottom`, `center`, `auto`.
// `auto` will dynamically switch between `right` and `left` according to the tooltip
// position on the map.
direction: 'auto',
// @option permanent: Boolean = false
// Whether to open the tooltip permanently or only on mouseover.
permanent: false,
// @option sticky: Boolean = false
// If true, the tooltip will follow the mouse instead of being fixed at the feature center.
sticky: false,
// @option interactive: Boolean = false
// If true, the tooltip will listen to the feature events.
interactive: false,
// @option opacity: Number = 0.9
// Tooltip container opacity.
opacity: 0.9
},
onAdd: function onAdd(map) {
DivOverlay.prototype.onAdd.call(this, map);
this.setOpacity(this.options.opacity);
// @namespace Map
// @section Tooltip events
// @event tooltipopen: TooltipEvent
// Fired when a tooltip is opened in the map.
map.fire('tooltipopen', {
tooltip: this
});
if (this._source) {
// @namespace Layer
// @section Tooltip events
// @event tooltipopen: TooltipEvent
// Fired when a tooltip bound to this layer is opened.
this._source.fire('tooltipopen', {
tooltip: this
}, true);
}
},
onRemove: function onRemove(map) {
DivOverlay.prototype.onRemove.call(this, map);
// @namespace Map
// @section Tooltip events
// @event tooltipclose: TooltipEvent
// Fired when a tooltip in the map is closed.
map.fire('tooltipclose', {
tooltip: this
});
if (this._source) {
// @namespace Layer
// @section Tooltip events
// @event tooltipclose: TooltipEvent
// Fired when a tooltip bound to this layer is closed.
this._source.fire('tooltipclose', {
tooltip: this
}, true);
}
},
getEvents: function getEvents() {
var events = DivOverlay.prototype.getEvents.call(this);
if (touch && !this.options.permanent) {
events.preclick = this._close;
}
return events;
},
_close: function _close() {
if (this._map) {
this._map.closeTooltip(this);
}
},
_initLayout: function _initLayout() {
var prefix = 'leaflet-tooltip',
className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
this._contentNode = this._container = create$1('div', className);
},
_updateLayout: function _updateLayout() {},
_adjustPan: function _adjustPan() {},
_setPosition: function _setPosition(pos) {
var map = this._map,
container = this._container,
centerPoint = map.latLngToContainerPoint(map.getCenter()),
tooltipPoint = map.layerPointToContainerPoint(pos),
direction = this.options.direction,
tooltipWidth = container.offsetWidth,
tooltipHeight = container.offsetHeight,
offset = toPoint(this.options.offset),
anchor = this._getAnchor();
if (direction === 'top') {
pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
} else if (direction === 'bottom') {
pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
} else if (direction === 'center') {
pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
} else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
direction = 'right';
pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
} else {
direction = 'left';
pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
}
removeClass(container, 'leaflet-tooltip-right');
removeClass(container, 'leaflet-tooltip-left');
removeClass(container, 'leaflet-tooltip-top');
removeClass(container, 'leaflet-tooltip-bottom');
addClass(container, 'leaflet-tooltip-' + direction);
setPosition(container, pos);
},
_updatePosition: function _updatePosition() {
var pos = this._map.latLngToLayerPoint(this._latlng);
this._setPosition(pos);
},
setOpacity: function setOpacity(opacity) {
this.options.opacity = opacity;
if (this._container) {
_setOpacity(this._container, opacity);
}
},
_animateZoom: function _animateZoom(e) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
this._setPosition(pos);
},
_getAnchor: function _getAnchor() {
// Where should we anchor the tooltip on the source layer?
return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
}
});
// @namespace Map
// @section Methods for Layers and Controls
Map.include({
// @method openTooltip(tooltip: Tooltip): this
// Opens the specified tooltip.
// @alternative
// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
// Creates a tooltip with the specified content and options and open it.
openTooltip: function openTooltip(tooltip, latlng, options) {
if (!(tooltip instanceof Tooltip)) {
tooltip = new Tooltip(options).setContent(tooltip);
}
if (latlng) {
tooltip.setLatLng(latlng);
}
if (this.hasLayer(tooltip)) {
return this;
}
return this.addLayer(tooltip);
},
// @method closeTooltip(tooltip?: Tooltip): this
// Closes the tooltip given as parameter.
closeTooltip: function closeTooltip(tooltip) {
if (tooltip) {
this.removeLayer(tooltip);
}
return this;
}
});
/*
* @namespace Layer
* @section Tooltip methods example
*
* All layers share a set of methods convenient for binding tooltips to it.
*
* ```js
* var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
* layer.openTooltip();
* layer.closeTooltip();
* ```
*/
// @section Tooltip methods
Layer.include({
// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
// Binds a tooltip to the layer with the passed `content` and sets up the
// necessary event listeners. If a `Function` is passed it will receive
// the layer as the first argument and should return a `String` or `HTMLElement`.
bindTooltip: function bindTooltip(content, options) {
if (content instanceof Tooltip) {
setOptions(content, options);
this._tooltip = content;
content._source = this;
} else {
if (!this._tooltip || options) {
this._tooltip = new Tooltip(options, this);
}
this._tooltip.setContent(content);
}
this._initTooltipInteractions();
if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
this.openTooltip();
}
return this;
},
// @method unbindTooltip(): this
// Removes the tooltip previously bound with `bindTooltip`.
unbindTooltip: function unbindTooltip() {
if (this._tooltip) {
this._initTooltipInteractions(true);
this.closeTooltip();
this._tooltip = null;
}
return this;
},
_initTooltipInteractions: function _initTooltipInteractions(remove$$1) {
if (!remove$$1 && this._tooltipHandlersAdded) {
return;
}
var onOff = remove$$1 ? 'off' : 'on',
events = {
remove: this.closeTooltip,
move: this._moveTooltip
};
if (!this._tooltip.options.permanent) {
events.mouseover = this._openTooltip;
events.mouseout = this.closeTooltip;
if (this._tooltip.options.sticky) {
events.mousemove = this._moveTooltip;
}
if (touch) {
events.click = this._openTooltip;
}
} else {
events.add = this._openTooltip;
}
this[onOff](events);
this._tooltipHandlersAdded = !remove$$1;
},
// @method openTooltip(latlng?: LatLng): this
// Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
openTooltip: function openTooltip(layer, latlng) {
if (this._tooltip && this._map) {
latlng = this._tooltip._prepareOpen(this, layer, latlng);
// open the tooltip on the map
this._map.openTooltip(this._tooltip, latlng);
// Tooltip container may not be defined if not permanent and never
// opened.
if (this._tooltip.options.interactive && this._tooltip._container) {
addClass(this._tooltip._container, 'leaflet-clickable');
this.addInteractiveTarget(this._tooltip._container);
}
}
return this;
},
// @method closeTooltip(): this
// Closes the tooltip bound to this layer if it is open.
closeTooltip: function closeTooltip() {
if (this._tooltip) {
this._tooltip._close();
if (this._tooltip.options.interactive && this._tooltip._container) {
removeClass(this._tooltip._container, 'leaflet-clickable');
this.removeInteractiveTarget(this._tooltip._container);
}
}
return this;
},
// @method toggleTooltip(): this
// Opens or closes the tooltip bound to this layer depending on its current state.
toggleTooltip: function toggleTooltip(target) {
if (this._tooltip) {
if (this._tooltip._map) {
this.closeTooltip();
} else {
this.openTooltip(target);
}
}
return this;
},
// @method isTooltipOpen(): boolean
// Returns `true` if the tooltip bound to this layer is currently open.
isTooltipOpen: function isTooltipOpen() {
return this._tooltip.isOpen();
},
// @method setTooltipContent(content: String|HTMLElement|Tooltip): this
// Sets the content of the tooltip bound to this layer.
setTooltipContent: function setTooltipContent(content) {
if (this._tooltip) {
this._tooltip.setContent(content);
}
return this;
},
// @method getTooltip(): Tooltip
// Returns the tooltip bound to this layer.
getTooltip: function getTooltip() {
return this._tooltip;
},
_openTooltip: function _openTooltip(e) {
var layer = e.layer || e.target;
if (!this._tooltip || !this._map) {
return;
}
this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
},
_moveTooltip: function _moveTooltip(e) {
var latlng = e.latlng,
containerPoint,
layerPoint;
if (this._tooltip.options.sticky && e.originalEvent) {
containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
layerPoint = this._map.containerPointToLayerPoint(containerPoint);
latlng = this._map.layerPointToLatLng(layerPoint);
}
this._tooltip.setLatLng(latlng);
}
});
/*
* @class DivIcon
* @aka L.DivIcon
* @inherits Icon
*
* Represents a lightweight icon for markers that uses a simple `<div>`
* element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
*
* @example
* ```js
* var myIcon = L.divIcon({className: 'my-div-icon'});
* // you can set .my-div-icon styles in CSS
*
* L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
* ```
*
* By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
*/
var DivIcon = Icon.extend({
options: {
// @section
// @aka DivIcon options
iconSize: [12, 12],
// also can be set through CSS
// iconAnchor: (Point),
// popupAnchor: (Point),
// @option html: String|HTMLElement = ''
// Custom HTML code to put inside the div element, empty by default. Alternatively,
// an instance of `HTMLElement`.
html: false,
// @option bgPos: Point = [0, 0]
// Optional relative position of the background, in pixels
bgPos: null,
className: 'leaflet-div-icon'
},
createIcon: function createIcon(oldIcon) {
var div = oldIcon && oldIcon.tagName === 'DIV' ? oldIcon : document.createElement('div'),
options = this.options;
if (options.html instanceof Element) {
empty(div);
div.appendChild(options.html);
} else {
div.innerHTML = options.html !== false ? options.html : '';
}
if (options.bgPos) {
var bgPos = toPoint(options.bgPos);
div.style.backgroundPosition = -bgPos.x + 'px ' + -bgPos.y + 'px';
}
this._setIconStyles(div, 'icon');
return div;
},
createShadow: function createShadow() {
return null;
}
});
Icon.Default = IconDefault;
/*
* @class GridLayer
* @inherits Layer
* @aka L.GridLayer
*
* Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
* GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
*
*
* @section Synchronous usage
* @example
*
* To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
*
* ```js
* var CanvasLayer = L.GridLayer.extend({
* createTile: function(coords){
* // create a <canvas> element for drawing
* var tile = L.DomUtil.create('canvas', 'leaflet-tile');
*
* // setup tile width and height according to the options
* var size = this.getTileSize();
* tile.width = size.x;
* tile.height = size.y;
*
* // get a canvas context and draw something on it using coords.x, coords.y and coords.z
* var ctx = tile.getContext('2d');
*
* // return the tile so it can be rendered on screen
* return tile;
* }
* });
* ```
*
* @section Asynchronous usage
* @example
*
* Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
*
* ```js
* var CanvasLayer = L.GridLayer.extend({
* createTile: function(coords, done){
* var error;
*
* // create a <canvas> element for drawing
* var tile = L.DomUtil.create('canvas', 'leaflet-tile');
*
* // setup tile width and height according to the options
* var size = this.getTileSize();
* tile.width = size.x;
* tile.height = size.y;
*
* // draw something asynchronously and pass the tile to the done() callback
* setTimeout(function() {
* done(error, tile);
* }, 1000);
*
* return tile;
* }
* });
* ```
*
* @section
*/
var GridLayer = Layer.extend({
// @section
// @aka GridLayer options
options: {
// @option tileSize: Number|Point = 256
// Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
tileSize: 256,
// @option opacity: Number = 1.0
// Opacity of the tiles. Can be used in the `createTile()` function.
opacity: 1,
// @option updateWhenIdle: Boolean = (depends)
// Load new tiles only when panning ends.
// `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
// `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
// [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
updateWhenIdle: mobile,
// @option updateWhenZooming: Boolean = true
// By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
updateWhenZooming: true,
// @option updateInterval: Number = 200
// Tiles will not update more than once every `updateInterval` milliseconds when panning.
updateInterval: 200,
// @option zIndex: Number = 1
// The explicit zIndex of the tile layer.
zIndex: 1,
// @option bounds: LatLngBounds = undefined
// If set, tiles will only be loaded inside the set `LatLngBounds`.
bounds: null,
// @option minZoom: Number = 0
// The minimum zoom level down to which this layer will be displayed (inclusive).
minZoom: 0,
// @option maxZoom: Number = undefined
// The maximum zoom level up to which this layer will be displayed (inclusive).
maxZoom: undefined,
// @option maxNativeZoom: Number = undefined
// Maximum zoom number the tile source has available. If it is specified,
// the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
// from `maxNativeZoom` level and auto-scaled.
maxNativeZoom: undefined,
// @option minNativeZoom: Number = undefined
// Minimum zoom number the tile source has available. If it is specified,
// the tiles on all zoom levels lower than `minNativeZoom` will be loaded
// from `minNativeZoom` level and auto-scaled.
minNativeZoom: undefined,
// @option noWrap: Boolean = false
// Whether the layer is wrapped around the antimeridian. If `true`, the
// GridLayer will only be displayed once at low zoom levels. Has no
// effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
// in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
// tiles outside the CRS limits.
noWrap: false,
// @option pane: String = 'tilePane'
// `Map pane` where the grid layer will be added.
pane: 'tilePane',
// @option className: String = ''
// A custom class name to assign to the tile layer. Empty by default.
className: '',
// @option keepBuffer: Number = 2
// When panning the map, keep this many rows and columns of tiles before unloading them.
keepBuffer: 2
},
initialize: function initialize(options) {
setOptions(this, options);
},
onAdd: function onAdd() {
this._initContainer();
this._levels = {};
this._tiles = {};
this._resetView();
this._update();
},
beforeAdd: function beforeAdd(map) {
map._addZoomLimit(this);
},
onRemove: function onRemove(map) {
this._removeAllTiles();
_remove(this._container);
map._removeZoomLimit(this);
this._container = null;
this._tileZoom = undefined;
},
// @method bringToFront: this
// Brings the tile layer to the top of all tile layers.
bringToFront: function bringToFront() {
if (this._map) {
toFront(this._container);
this._setAutoZIndex(Math.max);
}
return this;
},
// @method bringToBack: this
// Brings the tile layer to the bottom of all tile layers.
bringToBack: function bringToBack() {
if (this._map) {
toBack(this._container);
this._setAutoZIndex(Math.min);
}
return this;
},
// @method getContainer: HTMLElement
// Returns the HTML element that contains the tiles for this layer.
getContainer: function getContainer() {
return this._container;
},
// @method setOpacity(opacity: Number): this
// Changes the [opacity](#gridlayer-opacity) of the grid layer.
setOpacity: function setOpacity(opacity) {
this.options.opacity = opacity;
this._updateOpacity();
return this;
},
// @method setZIndex(zIndex: Number): this
// Changes the [zIndex](#gridlayer-zindex) of the grid layer.
setZIndex: function setZIndex(zIndex) {
this.options.zIndex = zIndex;
this._updateZIndex();
return this;
},
// @method isLoading: Boolean
// Returns `true` if any tile in the grid layer has not finished loading.
isLoading: function isLoading() {
return this._loading;
},
// @method redraw: this
// Causes the layer to clear all the tiles and request them again.
redraw: function redraw() {
if (this._map) {
this._removeAllTiles();
this._update();
}
return this;
},
getEvents: function getEvents() {
var events = {
viewprereset: this._invalidateAll,
viewreset: this._resetView,
zoom: this._resetView,
moveend: this._onMoveEnd
};
if (!this.options.updateWhenIdle) {
// update tiles on move, but not more often than once per given interval
if (!this._onMove) {
this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
}
events.move = this._onMove;
}
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
}
return events;
},
// @section Extension methods
// Layers extending `GridLayer` shall reimplement the following method.
// @method createTile(coords: Object, done?: Function): HTMLElement
// Called only internally, must be overridden by classes extending `GridLayer`.
// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
// is specified, it must be called when the tile has finished loading and drawing.
createTile: function createTile() {
return document.createElement('div');
},
// @section
// @method getTileSize: Point
// Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
getTileSize: function getTileSize() {
var s = this.options.tileSize;
return s instanceof Point ? s : new Point(s, s);
},
_updateZIndex: function _updateZIndex() {
if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
this._container.style.zIndex = this.options.zIndex;
}
},
_setAutoZIndex: function _setAutoZIndex(compare) {
// go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
var layers = this.getPane().children,
edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
for (var i = 0, len = layers.length, zIndex; i < len; i++) {
zIndex = layers[i].style.zIndex;
if (layers[i] !== this._container && zIndex) {
edgeZIndex = compare(edgeZIndex, +zIndex);
}
}
if (isFinite(edgeZIndex)) {
this.options.zIndex = edgeZIndex + compare(-1, 1);
this._updateZIndex();
}
},
_updateOpacity: function _updateOpacity() {
if (!this._map) {
return;
}
// IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
if (ielt9) {
return;
}
_setOpacity(this._container, this.options.opacity);
var now = +new Date(),
nextFrame = false,
willPrune = false;
for (var key in this._tiles) {
var tile = this._tiles[key];
if (!tile.current || !tile.loaded) {
continue;
}
var fade = Math.min(1, (now - tile.loaded) / 200);
_setOpacity(tile.el, fade);
if (fade < 1) {
nextFrame = true;
} else {
if (tile.active) {
willPrune = true;
} else {
this._onOpaqueTile(tile);
}
tile.active = true;
}
}
if (willPrune && !this._noPrune) {
this._pruneTiles();
}
if (nextFrame) {
cancelAnimFrame(this._fadeFrame);
this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
}
},
_onOpaqueTile: falseFn,
_initContainer: function _initContainer() {
if (this._container) {
return;
}
this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
this._updateZIndex();
if (this.options.opacity < 1) {
this._updateOpacity();
}
this.getPane().appendChild(this._container);
},
_updateLevels: function _updateLevels() {
var zoom = this._tileZoom,
maxZoom = this.options.maxZoom;
if (zoom === undefined) {
return undefined;
}
for (var z in this._levels) {
if (this._levels[z].el.children.length || z === zoom) {
this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
this._onUpdateLevel(z);
} else {
_remove(this._levels[z].el);
this._removeTilesAtZoom(z);
this._onRemoveLevel(z);
delete this._levels[z];
}
}
var level = this._levels[zoom],
map = this._map;
if (!level) {
level = this._levels[zoom] = {};
level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
level.el.style.zIndex = maxZoom;
level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
level.zoom = zoom;
this._setZoomTransform(level, map.getCenter(), map.getZoom());
// force the browser to consider the newly added element for transition
falseFn(level.el.offsetWidth);
this._onCreateLevel(level);
}
this._level = level;
return level;
},
_onUpdateLevel: falseFn,
_onRemoveLevel: falseFn,
_onCreateLevel: falseFn,
_pruneTiles: function _pruneTiles() {
if (!this._map) {
return;
}
var key, tile;
var zoom = this._map.getZoom();
if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
this._removeAllTiles();
return;
}
for (key in this._tiles) {
tile = this._tiles[key];
tile.retain = tile.current;
}
for (key in this._tiles) {
tile = this._tiles[key];
if (tile.current && !tile.active) {
var coords = tile.coords;
if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
}
}
}
for (key in this._tiles) {
if (!this._tiles[key].retain) {
this._removeTile(key);
}
}
},
_removeTilesAtZoom: function _removeTilesAtZoom(zoom) {
for (var key in this._tiles) {
if (this._tiles[key].coords.z !== zoom) {
continue;
}
this._removeTile(key);
}
},
_removeAllTiles: function _removeAllTiles() {
for (var key in this._tiles) {
this._removeTile(key);
}
},
_invalidateAll: function _invalidateAll() {
for (var z in this._levels) {
_remove(this._levels[z].el);
this._onRemoveLevel(z);
delete this._levels[z];
}
this._removeAllTiles();
this._tileZoom = undefined;
},
_retainParent: function _retainParent(x, y, z, minZoom) {
var x2 = Math.floor(x / 2),
y2 = Math.floor(y / 2),
z2 = z - 1,
coords2 = new Point(+x2, +y2);
coords2.z = +z2;
var key = this._tileCoordsToKey(coords2),
tile = this._tiles[key];
if (tile && tile.active) {
tile.retain = true;
return true;
} else if (tile && tile.loaded) {
tile.retain = true;
}
if (z2 > minZoom) {
return this._retainParent(x2, y2, z2, minZoom);
}
return false;
},
_retainChildren: function _retainChildren(x, y, z, maxZoom) {
for (var i = 2 * x; i < 2 * x + 2; i++) {
for (var j = 2 * y; j < 2 * y + 2; j++) {
var coords = new Point(i, j);
coords.z = z + 1;
var key = this._tileCoordsToKey(coords),
tile = this._tiles[key];
if (tile && tile.active) {
tile.retain = true;
continue;
} else if (tile && tile.loaded) {
tile.retain = true;
}
if (z + 1 < maxZoom) {
this._retainChildren(i, j, z + 1, maxZoom);
}
}
}
},
_resetView: function _resetView(e) {
var animating = e && (e.pinch || e.flyTo);
this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
},
_animateZoom: function _animateZoom(e) {
this._setView(e.center, e.zoom, true, e.noUpdate);
},
_clampZoom: function _clampZoom(zoom) {
var options = this.options;
if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
return options.minNativeZoom;
}
if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
return options.maxNativeZoom;
}
return zoom;
},
_setView: function _setView(center, zoom, noPrune, noUpdate) {
var tileZoom = this._clampZoom(Math.round(zoom));
if (this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom || this.options.minZoom !== undefined && tileZoom < this.options.minZoom) {
tileZoom = undefined;
}
var tileZoomChanged = this.options.updateWhenZooming && tileZoom !== this._tileZoom;
if (!noUpdate || tileZoomChanged) {
this._tileZoom = tileZoom;
if (this._abortLoading) {
this._abortLoading();
}
this._updateLevels();
this._resetGrid();
if (tileZoom !== undefined) {
this._update(center);
}
if (!noPrune) {
this._pruneTiles();
}
// Flag to prevent _updateOpacity from pruning tiles during
// a zoom anim or a pinch gesture
this._noPrune = !!noPrune;
}
this._setZoomTransforms(center, zoom);
},
_setZoomTransforms: function _setZoomTransforms(center, zoom) {
for (var i in this._levels) {
this._setZoomTransform(this._levels[i], center, zoom);
}
},
_setZoomTransform: function _setZoomTransform(level, center, zoom) {
var scale = this._map.getZoomScale(zoom, level.zoom),
translate = level.origin.multiplyBy(scale).subtract(this._map._getNewPixelOrigin(center, zoom)).round();
if (any3d) {
setTransform(level.el, translate, scale);
} else {
setPosition(level.el, translate);
}
},
_resetGrid: function _resetGrid() {
var map = this._map,
crs = map.options.crs,
tileSize = this._tileSize = this.getTileSize(),
tileZoom = this._tileZoom;
var bounds = this._map.getPixelWorldBounds(this._tileZoom);
if (bounds) {
this._globalTileRange = this._pxBoundsToTileRange(bounds);
}
this._wrapX = crs.wrapLng && !this.options.noWrap && [Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)];
this._wrapY = crs.wrapLat && !this.options.noWrap && [Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)];
},
_onMoveEnd: function _onMoveEnd() {
if (!this._map || this._map._animatingZoom) {
return;
}
this._update();
},
_getTiledPixelBounds: function _getTiledPixelBounds(center) {
var map = this._map,
mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
scale = map.getZoomScale(mapZoom, this._tileZoom),
pixelCenter = map.project(center, this._tileZoom).floor(),
halfSize = map.getSize().divideBy(scale * 2);
return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
},
// Private method to load tiles in the grid's active zoom level according to map bounds
_update: function _update(center) {
var map = this._map;
if (!map) {
return;
}
var zoom = this._clampZoom(map.getZoom());
if (center === undefined) {
center = map.getCenter();
}
if (this._tileZoom === undefined) {
return;
} // if out of minzoom/maxzoom
var pixelBounds = this._getTiledPixelBounds(center),
tileRange = this._pxBoundsToTileRange(pixelBounds),
tileCenter = tileRange.getCenter(),
queue = [],
margin = this.options.keepBuffer,
noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), tileRange.getTopRight().add([margin, -margin]));
// Sanity check: panic if the tile range contains Infinity somewhere.
if (!(isFinite(tileRange.min.x) && isFinite(tileRange.min.y) && isFinite(tileRange.max.x) && isFinite(tileRange.max.y))) {
throw new Error('Attempted to load an infinite number of tiles');
}
for (var key in this._tiles) {
var c = this._tiles[key].coords;
if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
this._tiles[key].current = false;
}
}
// _update just loads more tiles. If the tile zoom level differs too much
// from the map's, let _setView reset levels and prune old tiles.
if (Math.abs(zoom - this._tileZoom) > 1) {
this._setView(center, zoom);
return;
}
// create a queue of coordinates to load tiles from
for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
var coords = new Point(i, j);
coords.z = this._tileZoom;
if (!this._isValidTile(coords)) {
continue;
}
var tile = this._tiles[this._tileCoordsToKey(coords)];
if (tile) {
tile.current = true;
} else {
queue.push(coords);
}
}
}
// sort tile queue to load tiles in order of their distance to center
queue.sort(function (a, b) {
return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
});
if (queue.length !== 0) {
// if it's the first batch of tiles to load
if (!this._loading) {
this._loading = true;
// @event loading: Event
// Fired when the grid layer starts loading tiles.
this.fire('loading');
}
// create DOM fragment to append tiles in one batch
var fragment = document.createDocumentFragment();
for (i = 0; i < queue.length; i++) {
this._addTile(queue[i], fragment);
}
this._level.el.appendChild(fragment);
}
},
_isValidTile: function _isValidTile(coords) {
var crs = this._map.options.crs;
if (!crs.infinite) {
// don't load tile if it's out of bounds and not wrapped
var bounds = this._globalTileRange;
if (!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x) || !crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y)) {
return false;
}
}
if (!this.options.bounds) {
return true;
}
// don't load tile if it doesn't intersect the bounds in options
var tileBounds = this._tileCoordsToBounds(coords);
return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
},
_keyToBounds: function _keyToBounds(key) {
return this._tileCoordsToBounds(this._keyToTileCoords(key));
},
_tileCoordsToNwSe: function _tileCoordsToNwSe(coords) {
var map = this._map,
tileSize = this.getTileSize(),
nwPoint = coords.scaleBy(tileSize),
sePoint = nwPoint.add(tileSize),
nw = map.unproject(nwPoint, coords.z),
se = map.unproject(sePoint, coords.z);
return [nw, se];
},
// converts tile coordinates to its geographical bounds
_tileCoordsToBounds: function _tileCoordsToBounds(coords) {
var bp = this._tileCoordsToNwSe(coords),
bounds = new LatLngBounds(bp[0], bp[1]);
if (!this.options.noWrap) {
bounds = this._map.wrapLatLngBounds(bounds);
}
return bounds;
},
// converts tile coordinates to key for the tile cache
_tileCoordsToKey: function _tileCoordsToKey(coords) {
return coords.x + ':' + coords.y + ':' + coords.z;
},
// converts tile cache key to coordinates
_keyToTileCoords: function _keyToTileCoords(key) {
var k = key.split(':'),
coords = new Point(+k[0], +k[1]);
coords.z = +k[2];
return coords;
},
_removeTile: function _removeTile(key) {
var tile = this._tiles[key];
if (!tile) {
return;
}
_remove(tile.el);
delete this._tiles[key];
// @event tileunload: TileEvent
// Fired when a tile is removed (e.g. when a tile goes off the screen).
this.fire('tileunload', {
tile: tile.el,
coords: this._keyToTileCoords(key)
});
},
_initTile: function _initTile(tile) {
addClass(tile, 'leaflet-tile');
var tileSize = this.getTileSize();
tile.style.width = tileSize.x + 'px';
tile.style.height = tileSize.y + 'px';
tile.onselectstart = falseFn;
tile.onmousemove = falseFn;
// update opacity on tiles in IE7-8 because of filter inheritance problems
if (ielt9 && this.options.opacity < 1) {
_setOpacity(tile, this.options.opacity);
}
// without this hack, tiles disappear after zoom on Chrome for Android
// https://github.com/Leaflet/Leaflet/issues/2078
if (android && !android23) {
tile.style.WebkitBackfaceVisibility = 'hidden';
}
},
_addTile: function _addTile(coords, container) {
var tilePos = this._getTilePos(coords),
key = this._tileCoordsToKey(coords);
var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
this._initTile(tile);
// if createTile is defined with a second argument ("done" callback),
// we know that tile is async and will be ready later; otherwise
if (this.createTile.length < 2) {
// mark tile as ready, but delay one frame for opacity animation to happen
requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
}
setPosition(tile, tilePos);
// save tile in cache
this._tiles[key] = {
el: tile,
coords: coords,
current: true
};
container.appendChild(tile);
// @event tileloadstart: TileEvent
// Fired when a tile is requested and starts loading.
this.fire('tileloadstart', {
tile: tile,
coords: coords
});
},
_tileReady: function _tileReady(coords, err, tile) {
if (err) {
// @event tileerror: TileErrorEvent
// Fired when there is an error loading a tile.
this.fire('tileerror', {
error: err,
tile: tile,
coords: coords
});
}
var key = this._tileCoordsToKey(coords);
tile = this._tiles[key];
if (!tile) {
return;
}
tile.loaded = +new Date();
if (this._map._fadeAnimated) {
_setOpacity(tile.el, 0);
cancelAnimFrame(this._fadeFrame);
this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
} else {
tile.active = true;
this._pruneTiles();
}
if (!err) {
addClass(tile.el, 'leaflet-tile-loaded');
// @event tileload: TileEvent
// Fired when a tile loads.
this.fire('tileload', {
tile: tile.el,
coords: coords
});
}
if (this._noTilesToLoad()) {
this._loading = false;
// @event load: Event
// Fired when the grid layer loaded all visible tiles.
this.fire('load');
if (ielt9 || !this._map._fadeAnimated) {
requestAnimFrame(this._pruneTiles, this);
} else {
// Wait a bit more than 0.2 secs (the duration of the tile fade-in)
// to trigger a pruning.
setTimeout(bind(this._pruneTiles, this), 250);
}
}
},
_getTilePos: function _getTilePos(coords) {
return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
},
_wrapCoords: function _wrapCoords(coords) {
var newCoords = new Point(this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
newCoords.z = coords.z;
return newCoords;
},
_pxBoundsToTileRange: function _pxBoundsToTileRange(bounds) {
var tileSize = this.getTileSize();
return new Bounds(bounds.min.unscaleBy(tileSize).floor(), bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
},
_noTilesToLoad: function _noTilesToLoad() {
for (var key in this._tiles) {
if (!this._tiles[key].loaded) {
return false;
}
}
return true;
}
});
/*
* @class TileLayer
* @inherits GridLayer
* @aka L.TileLayer
* Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
*
* @example
*
* ```js
* L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
* ```
*
* @section URL template
* @example
*
* A string of the following form:
*
* ```
* 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
* ```
*
* `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add "@2x" to the URL to load retina tiles.
*
* You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
*
* ```
* L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
* ```
*/
var TileLayer = GridLayer.extend({
// @section
// @aka TileLayer options
options: {
// @option minZoom: Number = 0
// The minimum zoom level down to which this layer will be displayed (inclusive).
minZoom: 0,
// @option maxZoom: Number = 18
// The maximum zoom level up to which this layer will be displayed (inclusive).
maxZoom: 18,
// @option subdomains: String|String[] = 'abc'
// Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
subdomains: 'abc',
// @option errorTileUrl: String = ''
// URL to the tile image to show in place of the tile that failed to load.
errorTileUrl: '',
// @option zoomOffset: Number = 0
// The zoom number used in tile URLs will be offset with this value.
zoomOffset: 0,
// @option tms: Boolean = false
// If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
tms: false,
// @option zoomReverse: Boolean = false
// If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
zoomReverse: false,
// @option detectRetina: Boolean = false
// If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
detectRetina: false,
// @option crossOrigin: Boolean|String = false
// Whether the crossOrigin attribute will be added to the tiles.
// If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
crossOrigin: false
},
initialize: function initialize(url, options) {
this._url = url;
options = setOptions(this, options);
// detecting retina displays, adjusting tileSize and zoom levels
if (options.detectRetina && retina && options.maxZoom > 0) {
options.tileSize = Math.floor(options.tileSize / 2);
if (!options.zoomReverse) {
options.zoomOffset++;
options.maxZoom--;
} else {
options.zoomOffset--;
options.minZoom++;
}
options.minZoom = Math.max(0, options.minZoom);
}
if (typeof options.subdomains === 'string') {
options.subdomains = options.subdomains.split('');
}
// for https://github.com/Leaflet/Leaflet/issues/137
if (!android) {
this.on('tileunload', this._onTileRemove);
}
},
// @method setUrl(url: String, noRedraw?: Boolean): this
// Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
// If the URL does not change, the layer will not be redrawn unless
// the noRedraw parameter is set to false.
setUrl: function setUrl(url, noRedraw) {
if (this._url === url && noRedraw === undefined) {
noRedraw = true;
}
this._url = url;
if (!noRedraw) {
this.redraw();
}
return this;
},
// @method createTile(coords: Object, done?: Function): HTMLElement
// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
// to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
// callback is called when the tile has been loaded.
createTile: function createTile(coords, done) {
var tile = document.createElement('img');
on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
on(tile, 'error', bind(this._tileOnError, this, done, tile));
if (this.options.crossOrigin || this.options.crossOrigin === '') {
tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
}
/*
Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
http://www.w3.org/TR/WCAG20-TECHS/H67
*/
tile.alt = '';
/*
Set role="presentation" to force screen readers to ignore this
https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
*/
tile.setAttribute('role', 'presentation');
tile.src = this.getTileUrl(coords);
return tile;
},
// @section Extension methods
// @uninheritable
// Layers extending `TileLayer` might reimplement the following method.
// @method getTileUrl(coords: Object): String
// Called only internally, returns the URL for a tile given its coordinates.
// Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
getTileUrl: function getTileUrl(coords) {
var data = {
r: retina ? '@2x' : '',
s: this._getSubdomain(coords),
x: coords.x,
y: coords.y,
z: this._getZoomForUrl()
};
if (this._map && !this._map.options.crs.infinite) {
var invertedY = this._globalTileRange.max.y - coords.y;
if (this.options.tms) {
data['y'] = invertedY;
}
data['-y'] = invertedY;
}
return template(this._url, extend(data, this.options));
},
_tileOnLoad: function _tileOnLoad(done, tile) {
// For https://github.com/Leaflet/Leaflet/issues/3332
if (ielt9) {
setTimeout(bind(done, this, null, tile), 0);
} else {
done(null, tile);
}
},
_tileOnError: function _tileOnError(done, tile, e) {
var errorUrl = this.options.errorTileUrl;
if (errorUrl && tile.getAttribute('src') !== errorUrl) {
tile.src = errorUrl;
}
done(e, tile);
},
_onTileRemove: function _onTileRemove(e) {
e.tile.onload = null;
},
_getZoomForUrl: function _getZoomForUrl() {
var zoom = this._tileZoom,
maxZoom = this.options.maxZoom,
zoomReverse = this.options.zoomReverse,
zoomOffset = this.options.zoomOffset;
if (zoomReverse) {
zoom = maxZoom - zoom;
}
return zoom + zoomOffset;
},
_getSubdomain: function _getSubdomain(tilePoint) {
var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
return this.options.subdomains[index];
},
// stops loading all tiles in the background layer
_abortLoading: function _abortLoading() {
var i, tile;
for (i in this._tiles) {
if (this._tiles[i].coords.z !== this._tileZoom) {
tile = this._tiles[i].el;
tile.onload = falseFn;
tile.onerror = falseFn;
if (!tile.complete) {
tile.src = emptyImageUrl;
_remove(tile);
delete this._tiles[i];
}
}
}
},
_removeTile: function _removeTile(key) {
var tile = this._tiles[key];
if (!tile) {
return;
}
// Cancels any pending http requests associated with the tile
// unless we're on Android's stock browser,
// see https://github.com/Leaflet/Leaflet/issues/137
if (!androidStock) {
tile.el.setAttribute('src', emptyImageUrl);
}
return GridLayer.prototype._removeTile.call(this, key);
},
_tileReady: function _tileReady(coords, err, tile) {
if (!this._map || tile && tile.getAttribute('src') === emptyImageUrl) {
return;
}
return GridLayer.prototype._tileReady.call(this, coords, err, tile);
}
});
/*
* @class TileLayer.WMS
* @inherits TileLayer
* @aka L.TileLayer.WMS
* Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
*
* @example
*
* ```js
* var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
* layers: 'nexrad-n0r-900913',
* format: 'image/png',
* transparent: true,
* attribution: "Weather data © 2012 IEM Nexrad"
* });
* ```
*/
var TileLayerWMS = TileLayer.extend({
// @section
// @aka TileLayer.WMS options
// If any custom options not documented here are used, they will be sent to the
// WMS server as extra parameters in each request URL. This can be useful for
// [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
defaultWmsParams: {
service: 'WMS',
request: 'GetMap',
// @option layers: String = ''
// **(required)** Comma-separated list of WMS layers to show.
layers: '',
// @option styles: String = ''
// Comma-separated list of WMS styles.
styles: '',
// @option format: String = 'image/jpeg'
// WMS image format (use `'image/png'` for layers with transparency).
format: 'image/jpeg',
// @option transparent: Boolean = false
// If `true`, the WMS service will return images with transparency.
transparent: false,
// @option version: String = '1.1.1'
// Version of the WMS service to use
version: '1.1.1'
},
options: {
// @option crs: CRS = null
// Coordinate Reference System to use for the WMS requests, defaults to
// map CRS. Don't change this if you're not sure what it means.
crs: null,
// @option uppercase: Boolean = false
// If `true`, WMS request parameter keys will be uppercase.
uppercase: false
},
initialize: function initialize(url, options) {
this._url = url;
var wmsParams = extend({}, this.defaultWmsParams);
// all keys that are not TileLayer options go to WMS params
for (var i in options) {
if (!(i in this.options)) {
wmsParams[i] = options[i];
}
}
options = setOptions(this, options);
var realRetina = options.detectRetina && retina ? 2 : 1;
var tileSize = this.getTileSize();
wmsParams.width = tileSize.x * realRetina;
wmsParams.height = tileSize.y * realRetina;
this.wmsParams = wmsParams;
},
onAdd: function onAdd(map) {
this._crs = this.options.crs || map.options.crs;
this._wmsVersion = parseFloat(this.wmsParams.version);
var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
this.wmsParams[projectionKey] = this._crs.code;
TileLayer.prototype.onAdd.call(this, map);
},
getTileUrl: function getTileUrl(coords) {
var tileBounds = this._tileCoordsToNwSe(coords),
crs = this._crs,
bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
min = bounds.min,
max = bounds.max,
bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? [min.y, min.x, max.y, max.x] : [min.x, min.y, max.x, max.y]).join(','),
url = TileLayer.prototype.getTileUrl.call(this, coords);
return url + getParamString(this.wmsParams, url, this.options.uppercase) + (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
},
// @method setParams(params: Object, noRedraw?: Boolean): this
// Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
setParams: function setParams(params, noRedraw) {
extend(this.wmsParams, params);
if (!noRedraw) {
this.redraw();
}
return this;
}
});
TileLayer.WMS = TileLayerWMS;
/*
* @class Renderer
* @inherits Layer
* @aka L.Renderer
*
* Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
* DOM container of the renderer, its bounds, and its zoom animation.
*
* A `Renderer` works as an implicit layer group for all `Path`s - the renderer
* itself can be added or removed to the map. All paths use a renderer, which can
* be implicit (the map will decide the type of renderer and use it automatically)
* or explicit (using the [`renderer`](#path-renderer) option of the path).
*
* Do not use this class directly, use `SVG` and `Canvas` instead.
*
* @event update: Event
* Fired when the renderer updates its bounds, center and zoom, for example when
* its map has moved
*/
var Renderer = Layer.extend({
// @section
// @aka Renderer options
options: {
// @option padding: Number = 0.1
// How much to extend the clip area around the map view (relative to its size)
// e.g. 0.1 would be 10% of map view in each direction
padding: 0.1,
// @option tolerance: Number = 0
// How much to extend click tolerance round a path/object on the map
tolerance: 0
},
initialize: function initialize(options) {
setOptions(this, options);
stamp(this);
this._layers = this._layers || {};
},
onAdd: function onAdd() {
if (!this._container) {
this._initContainer(); // defined by renderer implementations
if (this._zoomAnimated) {
addClass(this._container, 'leaflet-zoom-animated');
}
}
this.getPane().appendChild(this._container);
this._update();
this.on('update', this._updatePaths, this);
},
onRemove: function onRemove() {
this.off('update', this._updatePaths, this);
this._destroyContainer();
},
getEvents: function getEvents() {
var events = {
viewreset: this._reset,
zoom: this._onZoom,
moveend: this._update,
zoomend: this._onZoomEnd
};
if (this._zoomAnimated) {
events.zoomanim = this._onAnimZoom;
}
return events;
},
_onAnimZoom: function _onAnimZoom(ev) {
this._updateTransform(ev.center, ev.zoom);
},
_onZoom: function _onZoom() {
this._updateTransform(this._map.getCenter(), this._map.getZoom());
},
_updateTransform: function _updateTransform(center, zoom) {
var scale = this._map.getZoomScale(zoom, this._zoom),
position = getPosition(this._container),
viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
currentCenterPoint = this._map.project(this._center, zoom),
destCenterPoint = this._map.project(center, zoom),
centerOffset = destCenterPoint.subtract(currentCenterPoint),
topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
if (any3d) {
setTransform(this._container, topLeftOffset, scale);
} else {
setPosition(this._container, topLeftOffset);
}
},
_reset: function _reset() {
this._update();
this._updateTransform(this._center, this._zoom);
for (var id in this._layers) {
this._layers[id]._reset();
}
},
_onZoomEnd: function _onZoomEnd() {
for (var id in this._layers) {
this._layers[id]._project();
}
},
_updatePaths: function _updatePaths() {
for (var id in this._layers) {
this._layers[id]._update();
}
},
_update: function _update() {
// Update pixel bounds of renderer container (for positioning/sizing/clipping later)
// Subclasses are responsible of firing the 'update' event.
var p = this.options.padding,
size = this._map.getSize(),
min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
this._center = this._map.getCenter();
this._zoom = this._map.getZoom();
}
});
/*
* @class Canvas
* @inherits Renderer
* @aka L.Canvas
*
* Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
* Inherits `Renderer`.
*
* Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
* available in all web browsers, notably IE8, and overlapping geometries might
* not display properly in some edge cases.
*
* @example
*
* Use Canvas by default for all paths in the map:
*
* ```js
* var map = L.map('map', {
* renderer: L.canvas()
* });
* ```
*
* Use a Canvas renderer with extra padding for specific vector geometries:
*
* ```js
* var map = L.map('map');
* var myRenderer = L.canvas({ padding: 0.5 });
* var line = L.polyline( coordinates, { renderer: myRenderer } );
* var circle = L.circle( center, { renderer: myRenderer } );
* ```
*/
var Canvas = Renderer.extend({
getEvents: function getEvents() {
var events = Renderer.prototype.getEvents.call(this);
events.viewprereset = this._onViewPreReset;
return events;
},
_onViewPreReset: function _onViewPreReset() {
// Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
this._postponeUpdatePaths = true;
},
onAdd: function onAdd() {
Renderer.prototype.onAdd.call(this);
// Redraw vectors since canvas is cleared upon removal,
// in case of removing the renderer itself from the map.
this._draw();
},
_initContainer: function _initContainer() {
var container = this._container = document.createElement('canvas');
on(container, 'mousemove', this._onMouseMove, this);
on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
on(container, 'mouseout', this._handleMouseOut, this);
this._ctx = container.getContext('2d');
},
_destroyContainer: function _destroyContainer() {
cancelAnimFrame(this._redrawRequest);
delete this._ctx;
_remove(this._container);
off(this._container);
delete this._container;
},
_updatePaths: function _updatePaths() {
if (this._postponeUpdatePaths) {
return;
}
var layer;
this._redrawBounds = null;
for (var id in this._layers) {
layer = this._layers[id];
layer._update();
}
this._redraw();
},
_update: function _update() {
if (this._map._animatingZoom && this._bounds) {
return;
}
Renderer.prototype._update.call(this);
var b = this._bounds,
container = this._container,
size = b.getSize(),
m = retina ? 2 : 1;
setPosition(container, b.min);
// set canvas size (also clearing it); use double size on retina
container.width = m * size.x;
container.height = m * size.y;
container.style.width = size.x + 'px';
container.style.height = size.y + 'px';
if (retina) {
this._ctx.scale(2, 2);
}
// translate so we use the same path coordinates after canvas element moves
this._ctx.translate(-b.min.x, -b.min.y);
// Tell paths to redraw themselves
this.fire('update');
},
_reset: function _reset() {
Renderer.prototype._reset.call(this);
if (this._postponeUpdatePaths) {
this._postponeUpdatePaths = false;
this._updatePaths();
}
},
_initPath: function _initPath(layer) {
this._updateDashArray(layer);
this._layers[stamp(layer)] = layer;
var order = layer._order = {
layer: layer,
prev: this._drawLast,
next: null
};
if (this._drawLast) {
this._drawLast.next = order;
}
this._drawLast = order;
this._drawFirst = this._drawFirst || this._drawLast;
},
_addPath: function _addPath(layer) {
this._requestRedraw(layer);
},
_removePath: function _removePath(layer) {
var order = layer._order;
var next = order.next;
var prev = order.prev;
if (next) {
next.prev = prev;
} else {
this._drawLast = prev;
}
if (prev) {
prev.next = next;
} else {
this._drawFirst = next;
}
delete layer._order;
delete this._layers[stamp(layer)];
this._requestRedraw(layer);
},
_updatePath: function _updatePath(layer) {
// Redraw the union of the layer's old pixel
// bounds and the new pixel bounds.
this._extendRedrawBounds(layer);
layer._project();
layer._update();
// The redraw will extend the redraw bounds
// with the new pixel bounds.
this._requestRedraw(layer);
},
_updateStyle: function _updateStyle(layer) {
this._updateDashArray(layer);
this._requestRedraw(layer);
},
_updateDashArray: function _updateDashArray(layer) {
if (typeof layer.options.dashArray === 'string') {
var parts = layer.options.dashArray.split(/[, ]+/),
dashArray = [],
dashValue,
i;
for (i = 0; i < parts.length; i++) {
dashValue = Number(parts[i]);
// Ignore dash array containing invalid lengths
if (isNaN(dashValue)) {
return;
}
dashArray.push(dashValue);
}
layer.options._dashArray = dashArray;
} else {
layer.options._dashArray = layer.options.dashArray;
}
},
_requestRedraw: function _requestRedraw(layer) {
if (!this._map) {
return;
}
this._extendRedrawBounds(layer);
this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
},
_extendRedrawBounds: function _extendRedrawBounds(layer) {
if (layer._pxBounds) {
var padding = (layer.options.weight || 0) + 1;
this._redrawBounds = this._redrawBounds || new Bounds();
this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
}
},
_redraw: function _redraw() {
this._redrawRequest = null;
if (this._redrawBounds) {
this._redrawBounds.min._floor();
this._redrawBounds.max._ceil();
}
this._clear(); // clear layers in redraw bounds
this._draw(); // draw layers
this._redrawBounds = null;
},
_clear: function _clear() {
var bounds = this._redrawBounds;
if (bounds) {
var size = bounds.getSize();
this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
} else {
this._ctx.clearRect(0, 0, this._container.width, this._container.height);
}
},
_draw: function _draw() {
var layer,
bounds = this._redrawBounds;
this._ctx.save();
if (bounds) {
var size = bounds.getSize();
this._ctx.beginPath();
this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
this._ctx.clip();
}
this._drawing = true;
for (var order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (!bounds || layer._pxBounds && layer._pxBounds.intersects(bounds)) {
layer._updatePath();
}
}
this._drawing = false;
this._ctx.restore(); // Restore state before clipping.
},
_updatePoly: function _updatePoly(layer, closed) {
if (!this._drawing) {
return;
}
var i,
j,
len2,
p,
parts = layer._parts,
len = parts.length,
ctx = this._ctx;
if (!len) {
return;
}
ctx.beginPath();
for (i = 0; i < len; i++) {
for (j = 0, len2 = parts[i].length; j < len2; j++) {
p = parts[i][j];
ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
}
if (closed) {
ctx.closePath();
}
}
this._fillStroke(ctx, layer);
// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
},
_updateCircle: function _updateCircle(layer) {
if (!this._drawing || layer._empty()) {
return;
}
var p = layer._point,
ctx = this._ctx,
r = Math.max(Math.round(layer._radius), 1),
s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
if (s !== 1) {
ctx.save();
ctx.scale(1, s);
}
ctx.beginPath();
ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
if (s !== 1) {
ctx.restore();
}
this._fillStroke(ctx, layer);
},
_fillStroke: function _fillStroke(ctx, layer) {
var options = layer.options;
if (options.fill) {
ctx.globalAlpha = options.fillOpacity;
ctx.fillStyle = options.fillColor || options.color;
ctx.fill(options.fillRule || 'evenodd');
}
if (options.stroke && options.weight !== 0) {
if (ctx.setLineDash) {
ctx.setLineDash(layer.options && layer.options._dashArray || []);
}
ctx.globalAlpha = options.opacity;
ctx.lineWidth = options.weight;
ctx.strokeStyle = options.color;
ctx.lineCap = options.lineCap;
ctx.lineJoin = options.lineJoin;
ctx.stroke();
}
},
// Canvas obviously doesn't have mouse events for individual drawn objects,
// so we emulate that by calculating what's under the mouse on mousemove/click manually
_onClick: function _onClick(e) {
var point = this._map.mouseEventToLayerPoint(e),
layer,
clickedLayer;
for (var order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
clickedLayer = layer;
}
}
if (clickedLayer) {
fakeStop(e);
this._fireEvent([clickedLayer], e);
}
},
_onMouseMove: function _onMouseMove(e) {
if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) {
return;
}
var point = this._map.mouseEventToLayerPoint(e);
this._handleMouseHover(e, point);
},
_handleMouseOut: function _handleMouseOut(e) {
var layer = this._hoveredLayer;
if (layer) {
// if we're leaving the layer, fire mouseout
removeClass(this._container, 'leaflet-interactive');
this._fireEvent([layer], e, 'mouseout');
this._hoveredLayer = null;
this._mouseHoverThrottled = false;
}
},
_handleMouseHover: function _handleMouseHover(e, point) {
if (this._mouseHoverThrottled) {
return;
}
var layer, candidateHoveredLayer;
for (var order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (layer.options.interactive && layer._containsPoint(point)) {
candidateHoveredLayer = layer;
}
}
if (candidateHoveredLayer !== this._hoveredLayer) {
this._handleMouseOut(e);
if (candidateHoveredLayer) {
addClass(this._container, 'leaflet-interactive'); // change cursor
this._fireEvent([candidateHoveredLayer], e, 'mouseover');
this._hoveredLayer = candidateHoveredLayer;
}
}
if (this._hoveredLayer) {
this._fireEvent([this._hoveredLayer], e);
}
this._mouseHoverThrottled = true;
setTimeout(L.bind(function () {
this._mouseHoverThrottled = false;
}, this), 32);
},
_fireEvent: function _fireEvent(layers, e, type) {
this._map._fireDOMEvent(e, type || e.type, layers);
},
_bringToFront: function _bringToFront(layer) {
var order = layer._order;
if (!order) {
return;
}
var next = order.next;
var prev = order.prev;
if (next) {
next.prev = prev;
} else {
// Already last
return;
}
if (prev) {
prev.next = next;
} else if (next) {
// Update first entry unless this is the
// single entry
this._drawFirst = next;
}
order.prev = this._drawLast;
this._drawLast.next = order;
order.next = null;
this._drawLast = order;
this._requestRedraw(layer);
},
_bringToBack: function _bringToBack(layer) {
var order = layer._order;
if (!order) {
return;
}
var next = order.next;
var prev = order.prev;
if (prev) {
prev.next = next;
} else {
// Already first
return;
}
if (next) {
next.prev = prev;
} else if (prev) {
// Update last entry unless this is the
// single entry
this._drawLast = prev;
}
order.prev = null;
order.next = this._drawFirst;
this._drawFirst.prev = order;
this._drawFirst = order;
this._requestRedraw(layer);
}
});
// @factory L.canvas(options?: Renderer options)
// Creates a Canvas renderer with the given options.
function canvas$1(options) {
return canvas ? new Canvas(options) : null;
}
/*
* Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
*/
var vmlCreate = function () {
try {
document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
return function (name) {
return document.createElement('<lvml:' + name + ' class="lvml">');
};
} catch (e) {
return function (name) {
return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
};
}
}();
/*
* @class SVG
*
*
* VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
* with old versions of Internet Explorer.
*/
// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
var vmlMixin = {
_initContainer: function _initContainer() {
this._container = create$1('div', 'leaflet-vml-container');
},
_update: function _update() {
if (this._map._animatingZoom) {
return;
}
Renderer.prototype._update.call(this);
this.fire('update');
},
_initPath: function _initPath(layer) {
var container = layer._container = vmlCreate('shape');
addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
container.coordsize = '1 1';
layer._path = vmlCreate('path');
container.appendChild(layer._path);
this._updateStyle(layer);
this._layers[stamp(layer)] = layer;
},
_addPath: function _addPath(layer) {
var container = layer._container;
this._container.appendChild(container);
if (layer.options.interactive) {
layer.addInteractiveTarget(container);
}
},
_removePath: function _removePath(layer) {
var container = layer._container;
_remove(container);
layer.removeInteractiveTarget(container);
delete this._layers[stamp(layer)];
},
_updateStyle: function _updateStyle(layer) {
var stroke = layer._stroke,
fill = layer._fill,
options = layer.options,
container = layer._container;
container.stroked = !!options.stroke;
container.filled = !!options.fill;
if (options.stroke) {
if (!stroke) {
stroke = layer._stroke = vmlCreate('stroke');
}
container.appendChild(stroke);
stroke.weight = options.weight + 'px';
stroke.color = options.color;
stroke.opacity = options.opacity;
if (options.dashArray) {
stroke.dashStyle = isArray(options.dashArray) ? options.dashArray.join(' ') : options.dashArray.replace(/( *, *)/g, ' ');
} else {
stroke.dashStyle = '';
}
stroke.endcap = options.lineCap.replace('butt', 'flat');
stroke.joinstyle = options.lineJoin;
} else if (stroke) {
container.removeChild(stroke);
layer._stroke = null;
}
if (options.fill) {
if (!fill) {
fill = layer._fill = vmlCreate('fill');
}
container.appendChild(fill);
fill.color = options.fillColor || options.color;
fill.opacity = options.fillOpacity;
} else if (fill) {
container.removeChild(fill);
layer._fill = null;
}
},
_updateCircle: function _updateCircle(layer) {
var p = layer._point.round(),
r = Math.round(layer._radius),
r2 = Math.round(layer._radiusY || r);
this._setPath(layer, layer._empty() ? 'M0 0' : 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + 65535 * 360);
},
_setPath: function _setPath(layer, path) {
layer._path.v = path;
},
_bringToFront: function _bringToFront(layer) {
toFront(layer._container);
},
_bringToBack: function _bringToBack(layer) {
toBack(layer._container);
}
};
var create$2 = vml ? vmlCreate : svgCreate;
/*
* @class SVG
* @inherits Renderer
* @aka L.SVG
*
* Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
* Inherits `Renderer`.
*
* Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
* available in all web browsers, notably Android 2.x and 3.x.
*
* Although SVG is not available on IE7 and IE8, these browsers support
* [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
* (a now deprecated technology), and the SVG renderer will fall back to VML in
* this case.
*
* @example
*
* Use SVG by default for all paths in the map:
*
* ```js
* var map = L.map('map', {
* renderer: L.svg()
* });
* ```
*
* Use a SVG renderer with extra padding for specific vector geometries:
*
* ```js
* var map = L.map('map');
* var myRenderer = L.svg({ padding: 0.5 });
* var line = L.polyline( coordinates, { renderer: myRenderer } );
* var circle = L.circle( center, { renderer: myRenderer } );
* ```
*/
var SVG = Renderer.extend({
getEvents: function getEvents() {
var events = Renderer.prototype.getEvents.call(this);
events.zoomstart = this._onZoomStart;
return events;
},
_initContainer: function _initContainer() {
this._container = create$2('svg');
// makes it possible to click through svg root; we'll reset it back in individual paths
this._container.setAttribute('pointer-events', 'none');
this._rootGroup = create$2('g');
this._container.appendChild(this._rootGroup);
},
_destroyContainer: function _destroyContainer() {
_remove(this._container);
off(this._container);
delete this._container;
delete this._rootGroup;
delete this._svgSize;
},
_onZoomStart: function _onZoomStart() {
// Drag-then-pinch interactions might mess up the center and zoom.
// In this case, the easiest way to prevent this is re-do the renderer
// bounds and padding when the zooming starts.
this._update();
},
_update: function _update() {
if (this._map._animatingZoom && this._bounds) {
return;
}
Renderer.prototype._update.call(this);
var b = this._bounds,
size = b.getSize(),
container = this._container;
// set size of svg-container if changed
if (!this._svgSize || !this._svgSize.equals(size)) {
this._svgSize = size;
container.setAttribute('width', size.x);
container.setAttribute('height', size.y);
}
// movement: update container viewBox so that we don't have to change coordinates of individual layers
setPosition(container, b.min);
container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
this.fire('update');
},
// methods below are called by vector layers implementations
_initPath: function _initPath(layer) {
var path = layer._path = create$2('path');
// @namespace Path
// @option className: String = null
// Custom class name set on an element. Only for SVG renderer.
if (layer.options.className) {
addClass(path, layer.options.className);
}
if (layer.options.interactive) {
addClass(path, 'leaflet-interactive');
}
this._updateStyle(layer);
this._layers[stamp(layer)] = layer;
},
_addPath: function _addPath(layer) {
if (!this._rootGroup) {
this._initContainer();
}
this._rootGroup.appendChild(layer._path);
layer.addInteractiveTarget(layer._path);
},
_removePath: function _removePath(layer) {
_remove(layer._path);
layer.removeInteractiveTarget(layer._path);
delete this._layers[stamp(layer)];
},
_updatePath: function _updatePath(layer) {
layer._project();
layer._update();
},
_updateStyle: function _updateStyle(layer) {
var path = layer._path,
options = layer.options;
if (!path) {
return;
}
if (options.stroke) {
path.setAttribute('stroke', options.color);
path.setAttribute('stroke-opacity', options.opacity);
path.setAttribute('stroke-width', options.weight);
path.setAttribute('stroke-linecap', options.lineCap);
path.setAttribute('stroke-linejoin', options.lineJoin);
if (options.dashArray) {
path.setAttribute('stroke-dasharray', options.dashArray);
} else {
path.removeAttribute('stroke-dasharray');
}
if (options.dashOffset) {
path.setAttribute('stroke-dashoffset', options.dashOffset);
} else {
path.removeAttribute('stroke-dashoffset');
}
} else {
path.setAttribute('stroke', 'none');
}
if (options.fill) {
path.setAttribute('fill', options.fillColor || options.color);
path.setAttribute('fill-opacity', options.fillOpacity);
path.setAttribute('fill-rule', options.fillRule || 'evenodd');
} else {
path.setAttribute('fill', 'none');
}
},
_updatePoly: function _updatePoly(layer, closed) {
this._setPath(layer, pointsToPath(layer._parts, closed));
},
_updateCircle: function _updateCircle(layer) {
var p = layer._point,
r = Math.max(Math.round(layer._radius), 1),
r2 = Math.max(Math.round(layer._radiusY), 1) || r,
arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
// drawing a circle with two half-arcs
var d = layer._empty() ? 'M0 0' : 'M' + (p.x - r) + ',' + p.y + arc + r * 2 + ',0 ' + arc + -r * 2 + ',0 ';
this._setPath(layer, d);
},
_setPath: function _setPath(layer, path) {
layer._path.setAttribute('d', path);
},
// SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
_bringToFront: function _bringToFront(layer) {
toFront(layer._path);
},
_bringToBack: function _bringToBack(layer) {
toBack(layer._path);
}
});
if (vml) {
SVG.include(vmlMixin);
}
// @namespace SVG
// @factory L.svg(options?: Renderer options)
// Creates a SVG renderer with the given options.
function svg$1(options) {
return svg || vml ? new SVG(options) : null;
}
Map.include({
// @namespace Map; @method getRenderer(layer: Path): Renderer
// Returns the instance of `Renderer` that should be used to render the given
// `Path`. It will ensure that the `renderer` options of the map and paths
// are respected, and that the renderers do exist on the map.
getRenderer: function getRenderer(layer) {
// @namespace Path; @option renderer: Renderer
// Use this specific instance of `Renderer` for this path. Takes
// precedence over the map's [default renderer](#map-renderer).
var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
if (!renderer) {
renderer = this._renderer = this._createRenderer();
}
if (!this.hasLayer(renderer)) {
this.addLayer(renderer);
}
return renderer;
},
_getPaneRenderer: function _getPaneRenderer(name) {
if (name === 'overlayPane' || name === undefined) {
return false;
}
var renderer = this._paneRenderers[name];
if (renderer === undefined) {
renderer = this._createRenderer({
pane: name
});
this._paneRenderers[name] = renderer;
}
return renderer;
},
_createRenderer: function _createRenderer(options) {
// @namespace Map; @option preferCanvas: Boolean = false
// Whether `Path`s should be rendered on a `Canvas` renderer.
// By default, all `Path`s are rendered in a `SVG` renderer.
return this.options.preferCanvas && canvas$1(options) || svg$1(options);
}
});
/*
* L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
*/
/*
* @class Rectangle
* @aka L.Rectangle
* @inherits Polygon
*
* A class for drawing rectangle overlays on a map. Extends `Polygon`.
*
* @example
*
* ```js
* // define rectangle geographical bounds
* var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
*
* // create an orange rectangle
* L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
*
* // zoom the map to the rectangle bounds
* map.fitBounds(bounds);
* ```
*
*/
var Rectangle = Polygon.extend({
initialize: function initialize(latLngBounds, options) {
Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
},
// @method setBounds(latLngBounds: LatLngBounds): this
// Redraws the rectangle with the passed bounds.
setBounds: function setBounds(latLngBounds) {
return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
},
_boundsToLatLngs: function _boundsToLatLngs(latLngBounds) {
latLngBounds = toLatLngBounds(latLngBounds);
return [latLngBounds.getSouthWest(), latLngBounds.getNorthWest(), latLngBounds.getNorthEast(), latLngBounds.getSouthEast()];
}
});
SVG.create = create$2;
SVG.pointsToPath = pointsToPath;
GeoJSON.geometryToLayer = geometryToLayer;
GeoJSON.coordsToLatLng = coordsToLatLng;
GeoJSON.coordsToLatLngs = coordsToLatLngs;
GeoJSON.latLngToCoords = latLngToCoords;
GeoJSON.latLngsToCoords = latLngsToCoords;
GeoJSON.getFeature = getFeature;
GeoJSON.asFeature = asFeature;
/*
* L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
* (zoom to a selected bounding box), enabled by default.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @option boxZoom: Boolean = true
// Whether the map can be zoomed to a rectangular area specified by
// dragging the mouse while pressing the shift key.
boxZoom: true
});
var BoxZoom = Handler.extend({
initialize: function initialize(map) {
this._map = map;
this._container = map._container;
this._pane = map._panes.overlayPane;
this._resetStateTimeout = 0;
map.on('unload', this._destroy, this);
},
addHooks: function addHooks() {
on(this._container, 'mousedown', this._onMouseDown, this);
},
removeHooks: function removeHooks() {
off(this._container, 'mousedown', this._onMouseDown, this);
},
moved: function moved() {
return this._moved;
},
_destroy: function _destroy() {
_remove(this._pane);
delete this._pane;
},
_resetState: function _resetState() {
this._resetStateTimeout = 0;
this._moved = false;
},
_clearDeferredResetState: function _clearDeferredResetState() {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_onMouseDown: function _onMouseDown(e) {
if (!e.shiftKey || e.which !== 1 && e.button !== 1) {
return false;
}
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
disableTextSelection();
disableImageDrag();
this._startPoint = this._map.mouseEventToContainerPoint(e);
on(document, {
contextmenu: stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
},
_onMouseMove: function _onMouseMove(e) {
if (!this._moved) {
this._moved = true;
this._box = create$1('div', 'leaflet-zoom-box', this._container);
addClass(this._container, 'leaflet-crosshair');
this._map.fire('boxzoomstart');
}
this._point = this._map.mouseEventToContainerPoint(e);
var bounds = new Bounds(this._point, this._startPoint),
size = bounds.getSize();
setPosition(this._box, bounds.min);
this._box.style.width = size.x + 'px';
this._box.style.height = size.y + 'px';
},
_finish: function _finish() {
if (this._moved) {
_remove(this._box);
removeClass(this._container, 'leaflet-crosshair');
}
enableTextSelection();
enableImageDrag();
off(document, {
contextmenu: stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
},
_onMouseUp: function _onMouseUp(e) {
if (e.which !== 1 && e.button !== 1) {
return;
}
this._finish();
if (!this._moved) {
return;
}
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
this._clearDeferredResetState();
this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
var bounds = new LatLngBounds(this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point));
this._map.fitBounds(bounds).fire('boxzoomend', {
boxZoomBounds: bounds
});
},
_onKeyDown: function _onKeyDown(e) {
if (e.keyCode === 27) {
this._finish();
}
}
});
// @section Handlers
// @property boxZoom: Handler
// Box (shift-drag with mouse) zoom handler.
Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
/*
* L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @option doubleClickZoom: Boolean|String = true
// Whether the map can be zoomed in by double clicking on it and
// zoomed out by double clicking while holding shift. If passed
// `'center'`, double-click zoom will zoom to the center of the
// view regardless of where the mouse was.
doubleClickZoom: true
});
var DoubleClickZoom = Handler.extend({
addHooks: function addHooks() {
this._map.on('dblclick', this._onDoubleClick, this);
},
removeHooks: function removeHooks() {
this._map.off('dblclick', this._onDoubleClick, this);
},
_onDoubleClick: function _onDoubleClick(e) {
var map = this._map,
oldZoom = map.getZoom(),
delta = map.options.zoomDelta,
zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
if (map.options.doubleClickZoom === 'center') {
map.setZoom(zoom);
} else {
map.setZoomAround(e.containerPoint, zoom);
}
}
});
// @section Handlers
//
// Map properties include interaction handlers that allow you to control
// interaction behavior in runtime, enabling or disabling certain features such
// as dragging or touch zoom (see `Handler` methods). For example:
//
// ```js
// map.doubleClickZoom.disable();
// ```
//
// @property doubleClickZoom: Handler
// Double click zoom handler.
Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
/*
* L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @option dragging: Boolean = true
// Whether the map be draggable with mouse/touch or not.
dragging: true,
// @section Panning Inertia Options
// @option inertia: Boolean = *
// If enabled, panning of the map will have an inertia effect where
// the map builds momentum while dragging and continues moving in
// the same direction for some time. Feels especially nice on touch
// devices. Enabled by default unless running on old Android devices.
inertia: !android23,
// @option inertiaDeceleration: Number = 3000
// The rate with which the inertial movement slows down, in pixels/second².
inertiaDeceleration: 3400,
// px/s^2
// @option inertiaMaxSpeed: Number = Infinity
// Max speed of the inertial movement, in pixels/second.
inertiaMaxSpeed: Infinity,
// px/s
// @option easeLinearity: Number = 0.2
easeLinearity: 0.2,
// TODO refactor, move to CRS
// @option worldCopyJump: Boolean = false
// With this option enabled, the map tracks when you pan to another "copy"
// of the world and seamlessly jumps to the original one so that all overlays
// like markers and vector layers are still visible.
worldCopyJump: false,
// @option maxBoundsViscosity: Number = 0.0
// If `maxBounds` is set, this option will control how solid the bounds
// are when dragging the map around. The default value of `0.0` allows the
// user to drag outside the bounds at normal speed, higher values will
// slow down map dragging outside bounds, and `1.0` makes the bounds fully
// solid, preventing the user from dragging outside the bounds.
maxBoundsViscosity: 0.0
});
var Drag = Handler.extend({
addHooks: function addHooks() {
if (!this._draggable) {
var map = this._map;
this._draggable = new Draggable(map._mapPane, map._container);
this._draggable.on({
dragstart: this._onDragStart,
drag: this._onDrag,
dragend: this._onDragEnd
}, this);
this._draggable.on('predrag', this._onPreDragLimit, this);
if (map.options.worldCopyJump) {
this._draggable.on('predrag', this._onPreDragWrap, this);
map.on('zoomend', this._onZoomEnd, this);
map.whenReady(this._onZoomEnd, this);
}
}
addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
this._draggable.enable();
this._positions = [];
this._times = [];
},
removeHooks: function removeHooks() {
removeClass(this._map._container, 'leaflet-grab');
removeClass(this._map._container, 'leaflet-touch-drag');
this._draggable.disable();
},
moved: function moved() {
return this._draggable && this._draggable._moved;
},
moving: function moving() {
return this._draggable && this._draggable._moving;
},
_onDragStart: function _onDragStart() {
var map = this._map;
map._stop();
if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
var bounds = toLatLngBounds(this._map.options.maxBounds);
this._offsetLimit = toBounds(this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1).add(this._map.getSize()));
this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
} else {
this._offsetLimit = null;
}
map.fire('movestart').fire('dragstart');
if (map.options.inertia) {
this._positions = [];
this._times = [];
}
},
_onDrag: function _onDrag(e) {
if (this._map.options.inertia) {
var time = this._lastTime = +new Date(),
pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
this._positions.push(pos);
this._times.push(time);
this._prunePositions(time);
}
this._map.fire('move', e).fire('drag', e);
},
_prunePositions: function _prunePositions(time) {
while (this._positions.length > 1 && time - this._times[0] > 50) {
this._positions.shift();
this._times.shift();
}
},
_onZoomEnd: function _onZoomEnd() {
var pxCenter = this._map.getSize().divideBy(2),
pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
},
_viscousLimit: function _viscousLimit(value, threshold) {
return value - (value - threshold) * this._viscosity;
},
_onPreDragLimit: function _onPreDragLimit() {
if (!this._viscosity || !this._offsetLimit) {
return;
}
var offset = this._draggable._newPos.subtract(this._draggable._startPos);
var limit = this._offsetLimit;
if (offset.x < limit.min.x) {
offset.x = this._viscousLimit(offset.x, limit.min.x);
}
if (offset.y < limit.min.y) {
offset.y = this._viscousLimit(offset.y, limit.min.y);
}
if (offset.x > limit.max.x) {
offset.x = this._viscousLimit(offset.x, limit.max.x);
}
if (offset.y > limit.max.y) {
offset.y = this._viscousLimit(offset.y, limit.max.y);
}
this._draggable._newPos = this._draggable._startPos.add(offset);
},
_onPreDragWrap: function _onPreDragWrap() {
// TODO refactor to be able to adjust map pane position after zoom
var worldWidth = this._worldWidth,
halfWidth = Math.round(worldWidth / 2),
dx = this._initialWorldOffset,
x = this._draggable._newPos.x,
newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
this._draggable._absPos = this._draggable._newPos.clone();
this._draggable._newPos.x = newX;
},
_onDragEnd: function _onDragEnd(e) {
var map = this._map,
options = map.options,
noInertia = !options.inertia || this._times.length < 2;
map.fire('dragend', e);
if (noInertia) {
map.fire('moveend');
} else {
this._prunePositions(+new Date());
var direction = this._lastPos.subtract(this._positions[0]),
duration = (this._lastTime - this._times[0]) / 1000,
ease = options.easeLinearity,
speedVector = direction.multiplyBy(ease / duration),
speed = speedVector.distanceTo([0, 0]),
limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
if (!offset.x && !offset.y) {
map.fire('moveend');
} else {
offset = map._limitOffset(offset, map.options.maxBounds);
requestAnimFrame(function () {
map.panBy(offset, {
duration: decelerationDuration,
easeLinearity: ease,
noMoveStart: true,
animate: true
});
});
}
}
}
});
// @section Handlers
// @property dragging: Handler
// Map dragging handler (by both mouse and touch).
Map.addInitHook('addHandler', 'dragging', Drag);
/*
* L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
*/
// @namespace Map
// @section Keyboard Navigation Options
Map.mergeOptions({
// @option keyboard: Boolean = true
// Makes the map focusable and allows users to navigate the map with keyboard
// arrows and `+`/`-` keys.
keyboard: true,
// @option keyboardPanDelta: Number = 80
// Amount of pixels to pan when pressing an arrow key.
keyboardPanDelta: 80
});
var Keyboard = Handler.extend({
keyCodes: {
left: [37],
right: [39],
down: [40],
up: [38],
zoomIn: [187, 107, 61, 171],
zoomOut: [189, 109, 54, 173]
},
initialize: function initialize(map) {
this._map = map;
this._setPanDelta(map.options.keyboardPanDelta);
this._setZoomDelta(map.options.zoomDelta);
},
addHooks: function addHooks() {
var container = this._map._container;
// make the container focusable by tabbing
if (container.tabIndex <= 0) {
container.tabIndex = '0';
}
on(container, {
focus: this._onFocus,
blur: this._onBlur,
mousedown: this._onMouseDown
}, this);
this._map.on({
focus: this._addHooks,
blur: this._removeHooks
}, this);
},
removeHooks: function removeHooks() {
this._removeHooks();
off(this._map._container, {
focus: this._onFocus,
blur: this._onBlur,
mousedown: this._onMouseDown
}, this);
this._map.off({
focus: this._addHooks,
blur: this._removeHooks
}, this);
},
_onMouseDown: function _onMouseDown() {
if (this._focused) {
return;
}
var body = document.body,
docEl = document.documentElement,
top = body.scrollTop || docEl.scrollTop,
left = body.scrollLeft || docEl.scrollLeft;
this._map._container.focus();
window.scrollTo(left, top);
},
_onFocus: function _onFocus() {
this._focused = true;
this._map.fire('focus');
},
_onBlur: function _onBlur() {
this._focused = false;
this._map.fire('blur');
},
_setPanDelta: function _setPanDelta(panDelta) {
var keys = this._panKeys = {},
codes = this.keyCodes,
i,
len;
for (i = 0, len = codes.left.length; i < len; i++) {
keys[codes.left[i]] = [-1 * panDelta, 0];
}
for (i = 0, len = codes.right.length; i < len; i++) {
keys[codes.right[i]] = [panDelta, 0];
}
for (i = 0, len = codes.down.length; i < len; i++) {
keys[codes.down[i]] = [0, panDelta];
}
for (i = 0, len = codes.up.length; i < len; i++) {
keys[codes.up[i]] = [0, -1 * panDelta];
}
},
_setZoomDelta: function _setZoomDelta(zoomDelta) {
var keys = this._zoomKeys = {},
codes = this.keyCodes,
i,
len;
for (i = 0, len = codes.zoomIn.length; i < len; i++) {
keys[codes.zoomIn[i]] = zoomDelta;
}
for (i = 0, len = codes.zoomOut.length; i < len; i++) {
keys[codes.zoomOut[i]] = -zoomDelta;
}
},
_addHooks: function _addHooks() {
on(document, 'keydown', this._onKeyDown, this);
},
_removeHooks: function _removeHooks() {
off(document, 'keydown', this._onKeyDown, this);
},
_onKeyDown: function _onKeyDown(e) {
if (e.altKey || e.ctrlKey || e.metaKey) {
return;
}
var key = e.keyCode,
map = this._map,
offset;
if (key in this._panKeys) {
if (!map._panAnim || !map._panAnim._inProgress) {
offset = this._panKeys[key];
if (e.shiftKey) {
offset = toPoint(offset).multiplyBy(3);
}
map.panBy(offset);
if (map.options.maxBounds) {
map.panInsideBounds(map.options.maxBounds);
}
}
} else if (key in this._zoomKeys) {
map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
} else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
map.closePopup();
} else {
return;
}
stop(e);
}
});
// @section Handlers
// @section Handlers
// @property keyboard: Handler
// Keyboard navigation handler.
Map.addInitHook('addHandler', 'keyboard', Keyboard);
/*
* L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @section Mousewheel options
// @option scrollWheelZoom: Boolean|String = true
// Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
// it will zoom to the center of the view regardless of where the mouse was.
scrollWheelZoom: true,
// @option wheelDebounceTime: Number = 40
// Limits the rate at which a wheel can fire (in milliseconds). By default
// user can't zoom via wheel more often than once per 40 ms.
wheelDebounceTime: 40,
// @option wheelPxPerZoomLevel: Number = 60
// How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
// mean a change of one full zoom level. Smaller values will make wheel-zooming
// faster (and vice versa).
wheelPxPerZoomLevel: 60
});
var ScrollWheelZoom = Handler.extend({
addHooks: function addHooks() {
on(this._map._container, 'mousewheel', this._onWheelScroll, this);
this._delta = 0;
},
removeHooks: function removeHooks() {
off(this._map._container, 'mousewheel', this._onWheelScroll, this);
},
_onWheelScroll: function _onWheelScroll(e) {
var delta = getWheelDelta(e);
var debounce = this._map.options.wheelDebounceTime;
this._delta += delta;
this._lastMousePos = this._map.mouseEventToContainerPoint(e);
if (!this._startTime) {
this._startTime = +new Date();
}
var left = Math.max(debounce - (+new Date() - this._startTime), 0);
clearTimeout(this._timer);
this._timer = setTimeout(bind(this._performZoom, this), left);
stop(e);
},
_performZoom: function _performZoom() {
var map = this._map,
zoom = map.getZoom(),
snap = this._map.options.zoomSnap || 0;
map._stop(); // stop panning and fly animations if any
// map the delta with a sigmoid function to -4..4 range leaning on -1..1
var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
this._delta = 0;
this._startTime = null;
if (!delta) {
return;
}
if (map.options.scrollWheelZoom === 'center') {
map.setZoom(zoom + delta);
} else {
map.setZoomAround(this._lastMousePos, zoom + delta);
}
}
});
// @section Handlers
// @property scrollWheelZoom: Handler
// Scroll wheel zoom handler.
Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
/*
* L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @section Touch interaction options
// @option tap: Boolean = true
// Enables mobile hacks for supporting instant taps (fixing 200ms click
// delay on iOS/Android) and touch holds (fired as `contextmenu` events).
tap: true,
// @option tapTolerance: Number = 15
// The max number of pixels a user can shift his finger during touch
// for it to be considered a valid tap.
tapTolerance: 15
});
var Tap = Handler.extend({
addHooks: function addHooks() {
on(this._map._container, 'touchstart', this._onDown, this);
},
removeHooks: function removeHooks() {
off(this._map._container, 'touchstart', this._onDown, this);
},
_onDown: function _onDown(e) {
if (!e.touches) {
return;
}
preventDefault(e);
this._fireClick = true;
// don't simulate click or track longpress if more than 1 touch
if (e.touches.length > 1) {
this._fireClick = false;
clearTimeout(this._holdTimeout);
return;
}
var first = e.touches[0],
el = first.target;
this._startPos = this._newPos = new Point(first.clientX, first.clientY);
// if touching a link, highlight it
if (el.tagName && el.tagName.toLowerCase() === 'a') {
addClass(el, 'leaflet-active');
}
// simulate long hold but setting a timeout
this._holdTimeout = setTimeout(bind(function () {
if (this._isTapValid()) {
this._fireClick = false;
this._onUp();
this._simulateEvent('contextmenu', first);
}
}, this), 1000);
this._simulateEvent('mousedown', first);
on(document, {
touchmove: this._onMove,
touchend: this._onUp
}, this);
},
_onUp: function _onUp(e) {
clearTimeout(this._holdTimeout);
off(document, {
touchmove: this._onMove,
touchend: this._onUp
}, this);
if (this._fireClick && e && e.changedTouches) {
var first = e.changedTouches[0],
el = first.target;
if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
removeClass(el, 'leaflet-active');
}
this._simulateEvent('mouseup', first);
// simulate click if the touch didn't move too much
if (this._isTapValid()) {
this._simulateEvent('click', first);
}
}
},
_isTapValid: function _isTapValid() {
return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
},
_onMove: function _onMove(e) {
var first = e.touches[0];
this._newPos = new Point(first.clientX, first.clientY);
this._simulateEvent('mousemove', first);
},
_simulateEvent: function _simulateEvent(type, e) {
var simulatedEvent = document.createEvent('MouseEvents');
simulatedEvent._simulated = true;
e.target._simulatedClick = true;
simulatedEvent.initMouseEvent(type, true, true, window, 1, e.screenX, e.screenY, e.clientX, e.clientY, false, false, false, false, 0, null);
e.target.dispatchEvent(simulatedEvent);
}
});
// @section Handlers
// @property tap: Handler
// Mobile touch hacks (quick tap and touch hold) handler.
if (touch && !pointer) {
Map.addInitHook('addHandler', 'tap', Tap);
}
/*
* L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @section Touch interaction options
// @option touchZoom: Boolean|String = *
// Whether the map can be zoomed by touch-dragging with two fingers. If
// passed `'center'`, it will zoom to the center of the view regardless of
// where the touch events (fingers) were. Enabled for touch-capable web
// browsers except for old Androids.
touchZoom: touch && !android23,
// @option bounceAtZoomLimits: Boolean = true
// Set it to false if you don't want the map to zoom beyond min/max zoom
// and then bounce back when pinch-zooming.
bounceAtZoomLimits: true
});
var TouchZoom = Handler.extend({
addHooks: function addHooks() {
addClass(this._map._container, 'leaflet-touch-zoom');
on(this._map._container, 'touchstart', this._onTouchStart, this);
},
removeHooks: function removeHooks() {
removeClass(this._map._container, 'leaflet-touch-zoom');
off(this._map._container, 'touchstart', this._onTouchStart, this);
},
_onTouchStart: function _onTouchStart(e) {
var map = this._map;
if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) {
return;
}
var p1 = map.mouseEventToContainerPoint(e.touches[0]),
p2 = map.mouseEventToContainerPoint(e.touches[1]);
this._centerPoint = map.getSize()._divideBy(2);
this._startLatLng = map.containerPointToLatLng(this._centerPoint);
if (map.options.touchZoom !== 'center') {
this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
}
this._startDist = p1.distanceTo(p2);
this._startZoom = map.getZoom();
this._moved = false;
this._zooming = true;
map._stop();
on(document, 'touchmove', this._onTouchMove, this);
on(document, 'touchend', this._onTouchEnd, this);
preventDefault(e);
},
_onTouchMove: function _onTouchMove(e) {
if (!e.touches || e.touches.length !== 2 || !this._zooming) {
return;
}
var map = this._map,
p1 = map.mouseEventToContainerPoint(e.touches[0]),
p2 = map.mouseEventToContainerPoint(e.touches[1]),
scale = p1.distanceTo(p2) / this._startDist;
this._zoom = map.getScaleZoom(scale, this._startZoom);
if (!map.options.bounceAtZoomLimits && (this._zoom < map.getMinZoom() && scale < 1 || this._zoom > map.getMaxZoom() && scale > 1)) {
this._zoom = map._limitZoom(this._zoom);
}
if (map.options.touchZoom === 'center') {
this._center = this._startLatLng;
if (scale === 1) {
return;
}
} else {
// Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
if (scale === 1 && delta.x === 0 && delta.y === 0) {
return;
}
this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
}
if (!this._moved) {
map._moveStart(true, false);
this._moved = true;
}
cancelAnimFrame(this._animRequest);
var moveFn = bind(map._move, map, this._center, this._zoom, {
pinch: true,
round: false
});
this._animRequest = requestAnimFrame(moveFn, this, true);
preventDefault(e);
},
_onTouchEnd: function _onTouchEnd() {
if (!this._moved || !this._zooming) {
this._zooming = false;
return;
}
this._zooming = false;
cancelAnimFrame(this._animRequest);
off(document, 'touchmove', this._onTouchMove);
off(document, 'touchend', this._onTouchEnd);
// Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
if (this._map.options.zoomAnimation) {
this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
} else {
this._map._resetView(this._center, this._map._limitZoom(this._zoom));
}
}
});
// @section Handlers
// @property touchZoom: Handler
// Touch zoom handler.
Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
Map.BoxZoom = BoxZoom;
Map.DoubleClickZoom = DoubleClickZoom;
Map.Drag = Drag;
Map.Keyboard = Keyboard;
Map.ScrollWheelZoom = ScrollWheelZoom;
Map.Tap = Tap;
Map.TouchZoom = TouchZoom;
Object.freeze = freeze;
function _classPrivateFieldInitSpec$1(obj, privateMap, value) { _checkPrivateRedeclaration$1(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration$1(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
var _token = /*#__PURE__*/new WeakMap();
var _sourceRepository = /*#__PURE__*/new WeakMap();
var _isRefreshing = /*#__PURE__*/new WeakMap();
var _refreshingPromise = /*#__PURE__*/new WeakMap();
var TokenContainer = /*#__PURE__*/function () {
function TokenContainer(props) {
babelHelpers.classCallCheck(this, TokenContainer);
_classPrivateFieldInitSpec$1(this, _token, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$1(this, _sourceRepository, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$1(this, _isRefreshing, {
writable: true,
value: false
});
_classPrivateFieldInitSpec$1(this, _refreshingPromise, {
writable: true,
value: null
});
babelHelpers.classPrivateFieldSet(this, _token, props.token);
babelHelpers.classPrivateFieldSet(this, _sourceRepository, props.sourceRepository);
}
babelHelpers.createClass(TokenContainer, [{
key: "refreshToken",
value: function refreshToken() {
var _this = this;
if (babelHelpers.classPrivateFieldGet(this, _isRefreshing)) {
return babelHelpers.classPrivateFieldGet(this, _refreshingPromise);
}
babelHelpers.classPrivateFieldSet(this, _refreshingPromise, babelHelpers.classPrivateFieldGet(this, _sourceRepository).getProps().then(function (sourceProps) {
_this.token = sourceProps.sourceParams.token;
babelHelpers.classPrivateFieldSet(_this, _isRefreshing, false);
return sourceProps.sourceParams.token;
})["catch"](function (response) {
babelHelpers.classPrivateFieldSet(_this, _isRefreshing, false);
console.error(response);
}));
babelHelpers.classPrivateFieldSet(this, _isRefreshing, true);
return babelHelpers.classPrivateFieldGet(this, _refreshingPromise);
}
}, {
key: "token",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _token);
},
set: function set(token) {
babelHelpers.classPrivateFieldSet(this, _token, token);
}
}]);
return TokenContainer;
}();
function _classPrivateMethodInitSpec$1(obj, privateSet) { _checkPrivateRedeclaration$2(obj, privateSet); privateSet.add(obj); }
function _classPrivateFieldInitSpec$2(obj, privateMap, value) { _checkPrivateRedeclaration$2(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration$2(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classPrivateMethodGet$1(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
var _tokenContainer = /*#__PURE__*/new WeakMap();
var _hostName = /*#__PURE__*/new WeakMap();
var _waitingRequests = /*#__PURE__*/new WeakMap();
var _processingUnauthorized = /*#__PURE__*/new WeakMap();
var _processUnauthorizedResponse = /*#__PURE__*/new WeakSet();
var TileLayerAuth = /*#__PURE__*/function (_Leaflet$TileLayer) {
babelHelpers.inherits(TileLayerAuth, _Leaflet$TileLayer);
function TileLayerAuth() {
var _babelHelpers$getProt;
var _this;
babelHelpers.classCallCheck(this, TileLayerAuth);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = babelHelpers.possibleConstructorReturn(this, (_babelHelpers$getProt = babelHelpers.getPrototypeOf(TileLayerAuth)).call.apply(_babelHelpers$getProt, [this].concat(args)));
_classPrivateMethodInitSpec$1(babelHelpers.assertThisInitialized(_this), _processUnauthorizedResponse);
_classPrivateFieldInitSpec$2(babelHelpers.assertThisInitialized(_this), _tokenContainer, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$2(babelHelpers.assertThisInitialized(_this), _hostName, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$2(babelHelpers.assertThisInitialized(_this), _waitingRequests, {
writable: true,
value: []
});
_classPrivateFieldInitSpec$2(babelHelpers.assertThisInitialized(_this), _processingUnauthorized, {
writable: true,
value: false
});
return _this;
}
babelHelpers.createClass(TileLayerAuth, [{
key: "setTokenContainer",
value: function setTokenContainer(tokenContainer) {
babelHelpers.classPrivateFieldSet(this, _tokenContainer, tokenContainer);
}
}, {
key: "setHostName",
value: function setHostName(hostName) {
babelHelpers.classPrivateFieldSet(this, _hostName, hostName);
}
}, {
key: "requestTile",
value: function requestTile(url, img, done, isUnAuth) {
var _this2 = this;
fetch(url, {
method: 'GET',
cache: 'force-cache',
headers: new Headers({
'Authorization': "Bearer ".concat(babelHelpers.classPrivateFieldGet(this, _tokenContainer).token),
'Bx-Location-Osm-Host': babelHelpers.classPrivateFieldGet(this, _hostName)
})
}).then(function (response) {
if (response.status === 200) {
return response.blob();
}
if (response.status === 401 && !isUnAuth) {
_classPrivateMethodGet$1(_this2, _processUnauthorizedResponse, _processUnauthorizedResponse2).call(_this2, url, img, done);
return null;
}
console.error("Response status: ".concat(response.status));
}).then(function (blobResponse) {
if (blobResponse) {
var reader = new FileReader();
reader.onload = function () {
img.src = reader.result;
};
reader.readAsDataURL(blobResponse);
done(null, img);
}
})["catch"](function (response) {
console.error(response);
});
}
}, {
key: "createTile",
value: function createTile(coords, done) {
var url = this.getTileUrl(coords);
var img = document.createElement('img');
if (babelHelpers.classPrivateFieldGet(this, _processingUnauthorized)) {
babelHelpers.classPrivateFieldGet(this, _waitingRequests).push([url, img, done]);
} else {
this.requestTile(url, img, done, false);
}
return img;
}
}]);
return TileLayerAuth;
}(TileLayer);
function _processUnauthorizedResponse2(url, img, done) {
var _this3 = this;
babelHelpers.classPrivateFieldSet(this, _processingUnauthorized, true);
babelHelpers.classPrivateFieldGet(this, _waitingRequests).push([url, img, done]);
babelHelpers.classPrivateFieldGet(this, _tokenContainer).refreshToken().then(function (sourceToken) {
var _loop = function _loop() {
var item = babelHelpers.classPrivateFieldGet(_this3, _waitingRequests).pop();
setTimeout(function () {
_this3.requestTile(item[0], item[1], item[2], true);
}, 1);
};
while (babelHelpers.classPrivateFieldGet(_this3, _waitingRequests).length > 0) {
_loop();
}
babelHelpers.classPrivateFieldSet(_this3, _processingUnauthorized, false);
});
}
Icon.Default.imagePath = '/bitrix/js/location/osm/leaflet/images/';
var _templateObject;
function _classPrivateMethodInitSpec$2(obj, privateSet) { _checkPrivateRedeclaration$3(obj, privateSet); privateSet.add(obj); }
function _classPrivateFieldInitSpec$3(obj, privateMap, value) { _checkPrivateRedeclaration$3(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration$3(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classStaticPrivateFieldSpecGet(receiver, classConstructor, descriptor) { _classCheckPrivateStaticAccess(receiver, classConstructor); _classCheckPrivateStaticFieldDescriptor(descriptor, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
function _classCheckPrivateStaticFieldDescriptor(descriptor, action) { if (descriptor === undefined) { throw new TypeError("attempted to " + action + " private static field before its declaration"); } }
function _classCheckPrivateStaticAccess(receiver, classConstructor) { if (receiver !== classConstructor) { throw new TypeError("Private static access of wrong provenance"); } }
function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
function _classPrivateMethodGet$2(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
/**
* Class for the autocomplete locations and addresses inputs
*/
var _zoom = /*#__PURE__*/new WeakMap();
var _mode = /*#__PURE__*/new WeakMap();
var _location = /*#__PURE__*/new WeakMap();
var _languageId = /*#__PURE__*/new WeakMap();
var _sourceLanguageId$1 = /*#__PURE__*/new WeakMap();
var _map = /*#__PURE__*/new WeakMap();
var _marker = /*#__PURE__*/new WeakMap();
var _geocodingService = /*#__PURE__*/new WeakMap();
var _isUpdating = /*#__PURE__*/new WeakMap();
var _timerId = /*#__PURE__*/new WeakMap();
var _changeDelay = /*#__PURE__*/new WeakMap();
var _tileUrlTemplate = /*#__PURE__*/new WeakMap();
var _attribution = /*#__PURE__*/new WeakMap();
var _mapFactoryMethod = /*#__PURE__*/new WeakMap();
var _markerFactoryMethod = /*#__PURE__*/new WeakMap();
var _tileLayerFactoryMethod = /*#__PURE__*/new WeakMap();
var _locationRepository = /*#__PURE__*/new WeakMap();
var _isResizeInvalidated = /*#__PURE__*/new WeakMap();
var _adjustZoom = /*#__PURE__*/new WeakSet();
var _onMapClick = /*#__PURE__*/new WeakSet();
var _createTimer = /*#__PURE__*/new WeakSet();
var _getReverseZoom = /*#__PURE__*/new WeakSet();
var _emitOnLocationChangedEvent = /*#__PURE__*/new WeakSet();
var _onMarkerUpdatePosition = /*#__PURE__*/new WeakSet();
var _invalidateMapSize = /*#__PURE__*/new WeakSet();
var MapService = /*#__PURE__*/function (_MapBase) {
babelHelpers.inherits(MapService, _MapBase);
/** {number} */
/** {ControlMode} */
/** {?Location} */
/** {String} */
/** {String} */
/** {Leaflet.map} */
/** {Leaflet.marker} */
/** {GeocodingService} */
function MapService(props) {
var _this;
babelHelpers.classCallCheck(this, MapService);
_this = babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(MapService).call(this, props));
_classPrivateMethodInitSpec$2(babelHelpers.assertThisInitialized(_this), _invalidateMapSize);
_classPrivateMethodInitSpec$2(babelHelpers.assertThisInitialized(_this), _onMarkerUpdatePosition);
_classPrivateMethodInitSpec$2(babelHelpers.assertThisInitialized(_this), _emitOnLocationChangedEvent);
_classPrivateMethodInitSpec$2(babelHelpers.assertThisInitialized(_this), _getReverseZoom);
_classPrivateMethodInitSpec$2(babelHelpers.assertThisInitialized(_this), _createTimer);
_classPrivateMethodInitSpec$2(babelHelpers.assertThisInitialized(_this), _onMapClick);
_classPrivateMethodInitSpec$2(babelHelpers.assertThisInitialized(_this), _adjustZoom);
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _zoom, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _mode, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _location, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _languageId, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _sourceLanguageId$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _map, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _marker, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _geocodingService, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _isUpdating, {
writable: true,
value: false
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _timerId, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _changeDelay, {
writable: true,
value: 700
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _tileUrlTemplate, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _attribution, {
writable: true,
value: 'Map data © OpenStreetMap contributors'
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _mapFactoryMethod, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _markerFactoryMethod, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _tileLayerFactoryMethod, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _locationRepository, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$3(babelHelpers.assertThisInitialized(_this), _isResizeInvalidated, {
writable: true,
value: false
});
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _languageId, props.languageId);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _sourceLanguageId$1, props.sourceLanguageId);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _geocodingService, props.geocodingService);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _mapFactoryMethod, props.mapFactoryMethod);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _markerFactoryMethod, props.markerFactoryMethod);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _tileLayerFactoryMethod, props.tileLayerFactoryMethod);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _tileUrlTemplate, "".concat(props.mapServiceUrl, "/hot/en/{z}/{x}/{y}.png"));
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _locationRepository, props.locationRepository);
return _this;
}
babelHelpers.createClass(MapService, [{
key: "onLocationChangedEventSubscribe",
value: function onLocationChangedEventSubscribe(listener) {
this.subscribe(_classStaticPrivateFieldSpecGet(MapService, MapService, _onChangedEvent), listener);
}
}, {
key: "onStartChangingSubscribe",
value: function onStartChangingSubscribe(listener) {
this.subscribe(_classStaticPrivateFieldSpecGet(MapService, MapService, _onStartChanging), listener);
}
}, {
key: "onEndChangingSubscribe",
value: function onEndChangingSubscribe(listener) {
this.subscribe(_classStaticPrivateFieldSpecGet(MapService, MapService, _onEndChanging), listener);
}
}, {
key: "onMapViewChangedSubscribe",
value: function onMapViewChangedSubscribe(listener) {
this.subscribe(_classStaticPrivateFieldSpecGet(MapService, MapService, _onMapViewChanged), listener);
}
}, {
key: "render",
value: function render(props) {
var _this2 = this;
babelHelpers.classPrivateFieldSet(this, _mode, props.mode);
babelHelpers.classPrivateFieldSet(this, _location, props.location || null);
var container = main_core.Tag.render(_templateObject || (_templateObject = babelHelpers.taggedTemplateLiteral(["<div class=\"location-osm-map-container\"></div>"])));
props.mapContainer.appendChild(container);
return new Promise(function (resolve) {
babelHelpers.classPrivateFieldSet(_this2, _map, babelHelpers.classPrivateFieldGet(_this2, _mapFactoryMethod).call(_this2, container, {
attributionControl: false,
zoomControl: BX.prop.getBoolean(props, 'zoomControl', true)
}));
babelHelpers.classPrivateFieldGet(_this2, _map).on('load', function () {
resolve();
});
babelHelpers.classPrivateFieldGet(_this2, _map).on('click', function (e) {
_classPrivateMethodGet$2(_this2, _onMapClick, _onMapClick2).call(_this2, e.latlng.lat, e.latlng.lng);
});
window.addEventListener('resize', function (event) {
babelHelpers.classPrivateFieldSet(_this2, _isResizeInvalidated, true);
_classPrivateMethodGet$2(_this2, _invalidateMapSize, _invalidateMapSize2).call(_this2);
});
babelHelpers.classPrivateFieldSet(_this2, _marker, babelHelpers.classPrivateFieldGet(_this2, _markerFactoryMethod).call(_this2, [babelHelpers.classPrivateFieldGet(_this2, _location).latitude, babelHelpers.classPrivateFieldGet(_this2, _location).longitude], {
draggable: babelHelpers.classPrivateFieldGet(_this2, _mode) === location_core.ControlMode.edit,
autoPan: true
}));
babelHelpers.classPrivateFieldGet(_this2, _marker).addTo(babelHelpers.classPrivateFieldGet(_this2, _map));
babelHelpers.classPrivateFieldGet(_this2, _marker).on('move', function (e) {
_classPrivateMethodGet$2(_this2, _onMarkerUpdatePosition, _onMarkerUpdatePosition2).call(_this2, e.latlng.lat, e.latlng.lng);
});
babelHelpers.classPrivateFieldGet(_this2, _map).setView([babelHelpers.classPrivateFieldGet(_this2, _location).latitude, babelHelpers.classPrivateFieldGet(_this2, _location).longitude], MapService.getZoomByLocation(babelHelpers.classPrivateFieldGet(_this2, _location)));
var tile = babelHelpers.classPrivateFieldGet(_this2, _tileLayerFactoryMethod).call();
tile.initialize(babelHelpers.classPrivateFieldGet(_this2, _tileUrlTemplate), {
/**
* In order to avoid blurry tiles on retina screens, we need to apply the below options:
* detectRetina: true,
* maxNativeZoom: 22,
* maxZoom: 18,
*
* but we can't do it right now because of the following bug in the leaflet library:
* https://github.com/Leaflet/Leaflet/issues/8850
* which causes fetching non-existent tiles (19, 20, etc. zoom levels)
*/
maxZoom: 18
});
tile.addTo(babelHelpers.classPrivateFieldGet(_this2, _map));
babelHelpers.classPrivateFieldGet(_this2, _map).on('zoomend', function () {
babelHelpers.classPrivateFieldSet(_this2, _zoom, babelHelpers.classPrivateFieldGet(_this2, _map).getZoom());
});
var attribution = new Control.Attribution();
attribution.setPrefix('');
attribution.addAttribution(babelHelpers.classPrivateFieldGet(_this2, _attribution));
babelHelpers.classPrivateFieldGet(_this2, _map).addControl(attribution);
});
}
}, {
key: "onMapShow",
value: function onMapShow() {
if (babelHelpers.classPrivateFieldGet(this, _isResizeInvalidated)) {
babelHelpers.classPrivateFieldSet(this, _isResizeInvalidated, false);
_classPrivateMethodGet$2(this, _invalidateMapSize, _invalidateMapSize2).call(this);
}
}
}, {
key: "destroy",
value: function destroy() {
main_core.Event.unbindAll(this);
babelHelpers.classPrivateFieldGet(this, _map).remove();
babelHelpers.classPrivateFieldGet(this, _marker).remove();
babelHelpers.get(babelHelpers.getPrototypeOf(MapService.prototype), "destroy", this).call(this);
}
}, {
key: "mode",
set: function set(mode) {
babelHelpers.classPrivateFieldSet(this, _mode, mode);
if (babelHelpers.classPrivateFieldGet(this, _marker)) {
babelHelpers.classPrivateFieldGet(this, _marker).draggable = mode === location_core.ControlMode.edit;
}
},
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _mode);
}
}, {
key: "map",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _map);
},
set: function set(map) {
babelHelpers.classPrivateFieldSet(this, _map, map);
}
}, {
key: "marker",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _marker);
},
set: function set(marker$$1) {
babelHelpers.classPrivateFieldSet(this, _marker, marker$$1);
}
}, {
key: "zoom",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _zoom);
},
set: function set(zoom) {
babelHelpers.classPrivateFieldSet(this, _zoom, zoom);
if (babelHelpers.classPrivateFieldGet(this, _map)) {
babelHelpers.classPrivateFieldGet(this, _map).setZoom(zoom);
}
}
}, {
key: "location",
set: function set(location) {
babelHelpers.classPrivateFieldSet(this, _location, location);
if (location) {
if (babelHelpers.classPrivateFieldGet(this, _marker)) {
babelHelpers.classPrivateFieldSet(this, _isUpdating, true);
babelHelpers.classPrivateFieldGet(this, _marker).setLatLng([location.latitude, location.longitude]);
babelHelpers.classPrivateFieldSet(this, _isUpdating, false);
}
if (babelHelpers.classPrivateFieldGet(this, _map)) {
if (!babelHelpers.classPrivateFieldGet(this, _map).hasLayer(babelHelpers.classPrivateFieldGet(this, _marker))) {
babelHelpers.classPrivateFieldGet(this, _marker).addTo(babelHelpers.classPrivateFieldGet(this, _map));
}
babelHelpers.classPrivateFieldGet(this, _map).panTo([location.latitude, location.longitude]);
}
} else if (babelHelpers.classPrivateFieldGet(this, _marker)) {
babelHelpers.classPrivateFieldGet(this, _marker).remove();
}
_classPrivateMethodGet$2(this, _adjustZoom, _adjustZoom2).call(this);
},
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _location);
}
}]);
return MapService;
}(location_core.MapBase);
function _adjustZoom2() {
if (!babelHelpers.classPrivateFieldGet(this, _location)) {
return;
}
var zoom = MapService.getZoomByLocation(babelHelpers.classPrivateFieldGet(this, _location));
if (zoom !== null && zoom !== babelHelpers.classPrivateFieldGet(this, _zoom)) {
this.zoom = zoom;
}
}
function _onMapClick2(lat, lng) {
if (babelHelpers.classPrivateFieldGet(this, _mode) === location_core.ControlMode.edit) {
if (!babelHelpers.classPrivateFieldGet(this, _map).hasLayer(babelHelpers.classPrivateFieldGet(this, _marker))) {
babelHelpers.classPrivateFieldGet(this, _marker).addTo(babelHelpers.classPrivateFieldGet(this, _map));
}
babelHelpers.classPrivateFieldGet(this, _marker).setLatLng([lat, lng]);
_classPrivateMethodGet$2(this, _createTimer, _createTimer2).call(this, lat, lng);
}
}
function _createTimer2(lat, lng) {
var _this3 = this;
if (babelHelpers.classPrivateFieldGet(this, _timerId) !== null) {
clearTimeout(babelHelpers.classPrivateFieldGet(this, _timerId));
}
babelHelpers.classPrivateFieldSet(this, _timerId, setTimeout(function () {
var requestId = main_core.Text.getRandom();
_this3.emit(_classStaticPrivateFieldSpecGet(MapService, MapService, _onStartChanging), {
requestId: requestId
});
babelHelpers.classPrivateFieldSet(_this3, _timerId, null);
babelHelpers.classPrivateFieldGet(_this3, _map).panTo([lat, lng]);
var point = new location_core.Point(lat, lng);
babelHelpers.classPrivateFieldGet(_this3, _geocodingService).reverse(point, _classPrivateMethodGet$2(_this3, _getReverseZoom, _getReverseZoom2).call(_this3)).then(function (location) {
var result;
if (location) {
result = babelHelpers.classPrivateFieldGet(_this3, _locationRepository).findByExternalId(location.externalId, OSM.code, babelHelpers.classPrivateFieldGet(_this3, _languageId)).then(function (foundLocation) {
/**
* Use marker coordinates
*/
if (foundLocation) {
foundLocation.longitude = point.longitude;
if (foundLocation.address) {
foundLocation.address.longitude = point.longitude;
}
foundLocation.latitude = point.latitude;
if (foundLocation.address) {
foundLocation.address.latitude = point.latitude;
}
}
return foundLocation;
});
} else {
result = new Promise(function (resolve) {
resolve(null);
});
}
return result;
}).then(function (location) {
_this3.emit(_classStaticPrivateFieldSpecGet(MapService, MapService, _onEndChanging), {
requestId: requestId
});
_classPrivateMethodGet$2(_this3, _emitOnLocationChangedEvent, _emitOnLocationChangedEvent2).call(_this3, location);
})["catch"](function (response) {
_this3.emit(_classStaticPrivateFieldSpecGet(MapService, MapService, _onEndChanging), {
requestId: requestId
});
location_core.ErrorPublisher.getInstance().notify(response.errors);
});
}, babelHelpers.classPrivateFieldGet(this, _changeDelay)));
}
function _getReverseZoom2() {
return babelHelpers.classPrivateFieldGet(this, _zoom) >= 15 ? 18 : babelHelpers.classPrivateFieldGet(this, _zoom);
}
function _emitOnLocationChangedEvent2(location) {
if (babelHelpers.classPrivateFieldGet(this, _mode) === location_core.ControlMode.edit) {
this.emit(_classStaticPrivateFieldSpecGet(MapService, MapService, _onChangedEvent), {
location: location
});
}
}
function _onMarkerUpdatePosition2(lat, lng) {
if (!babelHelpers.classPrivateFieldGet(this, _isUpdating) && babelHelpers.classPrivateFieldGet(this, _mode) === location_core.ControlMode.edit) {
_classPrivateMethodGet$2(this, _createTimer, _createTimer2).call(this, lat, lng);
}
}
function _invalidateMapSize2() {
var _this4 = this;
setTimeout(function () {
babelHelpers.classPrivateFieldGet(_this4, _map).invalidateSize();
}, 10);
}
var _onChangedEvent = {
writable: true,
value: 'onChanged'
};
var _onStartChanging = {
writable: true,
value: 'onStartChanging'
};
var _onEndChanging = {
writable: true,
value: 'onEndChanging'
};
var _onMapViewChanged = {
writable: true,
value: 'onMapViewChanged'
};
var _templateObject$1, _templateObject2;
function _classPrivateMethodInitSpec$3(obj, privateSet) { _checkPrivateRedeclaration$4(obj, privateSet); privateSet.add(obj); }
function _classPrivateFieldInitSpec$4(obj, privateMap, value) { _checkPrivateRedeclaration$4(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration$4(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classStaticPrivateFieldSpecGet$1(receiver, classConstructor, descriptor) { _classCheckPrivateStaticAccess$1(receiver, classConstructor); _classCheckPrivateStaticFieldDescriptor$1(descriptor, "get"); return _classApplyDescriptorGet$1(receiver, descriptor); }
function _classCheckPrivateStaticFieldDescriptor$1(descriptor, action) { if (descriptor === undefined) { throw new TypeError("attempted to " + action + " private static field before its declaration"); } }
function _classCheckPrivateStaticAccess$1(receiver, classConstructor) { if (receiver !== classConstructor) { throw new TypeError("Private static access of wrong provenance"); } }
function _classApplyDescriptorGet$1(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
function _classPrivateMethodGet$3(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
var _zoom$1 = /*#__PURE__*/new WeakMap();
var _mode$1 = /*#__PURE__*/new WeakMap();
var _location$1 = /*#__PURE__*/new WeakMap();
var _languageId$1 = /*#__PURE__*/new WeakMap();
var _sourceLanguageId$2 = /*#__PURE__*/new WeakMap();
var _map$1 = /*#__PURE__*/new WeakMap();
var _marker$1 = /*#__PURE__*/new WeakMap();
var _markerNode = /*#__PURE__*/new WeakMap();
var _geocodingService$1 = /*#__PURE__*/new WeakMap();
var _timerId$1 = /*#__PURE__*/new WeakMap();
var _changeDelay$1 = /*#__PURE__*/new WeakMap();
var _tileUrlTemplate$1 = /*#__PURE__*/new WeakMap();
var _attribution$1 = /*#__PURE__*/new WeakMap();
var _mapFactoryMethod$1 = /*#__PURE__*/new WeakMap();
var _markerFactoryMethod$1 = /*#__PURE__*/new WeakMap();
var _iconFactoryMethod = /*#__PURE__*/new WeakMap();
var _tileLayerFactoryMethod$1 = /*#__PURE__*/new WeakMap();
var _locationRepository$1 = /*#__PURE__*/new WeakMap();
var _isMapChanging = /*#__PURE__*/new WeakMap();
var _adjustZoom$1 = /*#__PURE__*/new WeakSet();
var _onMove = /*#__PURE__*/new WeakSet();
var _onMoveStart = /*#__PURE__*/new WeakSet();
var _onMoveEnd = /*#__PURE__*/new WeakSet();
var _onZoomStart = /*#__PURE__*/new WeakSet();
var _onZoomEnd = /*#__PURE__*/new WeakSet();
var _createTimer$1 = /*#__PURE__*/new WeakSet();
var _getReverseZoom$1 = /*#__PURE__*/new WeakSet();
var _emitOnLocationChangedEvent$1 = /*#__PURE__*/new WeakSet();
var MapMobileService = /*#__PURE__*/function (_MapBase) {
babelHelpers.inherits(MapMobileService, _MapBase);
/** {number} */
/** {ControlMode} */
/** {?Location} */
/** {String} */
/** {String} */
/** {Leaflet.map} */
/** {Leaflet.marker} */
/** {GeocodingService} */
function MapMobileService(props) {
var _this;
babelHelpers.classCallCheck(this, MapMobileService);
_this = babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(MapMobileService).call(this, props));
_classPrivateMethodInitSpec$3(babelHelpers.assertThisInitialized(_this), _emitOnLocationChangedEvent$1);
_classPrivateMethodInitSpec$3(babelHelpers.assertThisInitialized(_this), _getReverseZoom$1);
_classPrivateMethodInitSpec$3(babelHelpers.assertThisInitialized(_this), _createTimer$1);
_classPrivateMethodInitSpec$3(babelHelpers.assertThisInitialized(_this), _onZoomEnd);
_classPrivateMethodInitSpec$3(babelHelpers.assertThisInitialized(_this), _onZoomStart);
_classPrivateMethodInitSpec$3(babelHelpers.assertThisInitialized(_this), _onMoveEnd);
_classPrivateMethodInitSpec$3(babelHelpers.assertThisInitialized(_this), _onMoveStart);
_classPrivateMethodInitSpec$3(babelHelpers.assertThisInitialized(_this), _onMove);
_classPrivateMethodInitSpec$3(babelHelpers.assertThisInitialized(_this), _adjustZoom$1);
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _zoom$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _mode$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _location$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _languageId$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _sourceLanguageId$2, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _map$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _marker$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _markerNode, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _geocodingService$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _timerId$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _changeDelay$1, {
writable: true,
value: 700
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _tileUrlTemplate$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _attribution$1, {
writable: true,
value: 'Map data © OpenStreetMap contributors'
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _mapFactoryMethod$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _markerFactoryMethod$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _iconFactoryMethod, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _tileLayerFactoryMethod$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _locationRepository$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$4(babelHelpers.assertThisInitialized(_this), _isMapChanging, {
writable: true,
value: false
});
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _languageId$1, props.languageId);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _sourceLanguageId$2, props.sourceLanguageId);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _geocodingService$1, props.geocodingService);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _mapFactoryMethod$1, props.mapFactoryMethod);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _markerFactoryMethod$1, props.markerFactoryMethod);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _iconFactoryMethod, props.iconFactoryMethod);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _tileLayerFactoryMethod$1, props.tileLayerFactoryMethod);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _tileUrlTemplate$1, "".concat(props.mapServiceUrl, "/hot/en/{z}/{x}/{y}.png"));
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _locationRepository$1, props.locationRepository);
return _this;
}
babelHelpers.createClass(MapMobileService, [{
key: "panTo",
value: function panTo(latitude, longitude) {
if (babelHelpers.classPrivateFieldGet(this, _map$1)) {
babelHelpers.classPrivateFieldGet(this, _map$1).panTo([latitude, longitude]);
}
}
}, {
key: "onLocationChangedEventSubscribe",
value: function onLocationChangedEventSubscribe(listener) {
this.subscribe(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onChangedEvent$1), listener);
}
}, {
key: "onStartChangingSubscribe",
value: function onStartChangingSubscribe(listener) {
this.subscribe(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onStartChanging$1), listener);
}
}, {
key: "onEndChangingSubscribe",
value: function onEndChangingSubscribe(listener) {
this.subscribe(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onEndChanging$1), listener);
}
}, {
key: "onMapViewChangedSubscribe",
value: function onMapViewChangedSubscribe(listener) {
this.subscribe(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onMapViewChanged$1), listener);
}
}, {
key: "render",
value: function render(props) {
var _this2 = this;
babelHelpers.classPrivateFieldSet(this, _mode$1, props.mode);
babelHelpers.classPrivateFieldSet(this, _location$1, props.location || null);
var container = main_core.Tag.render(_templateObject$1 || (_templateObject$1 = babelHelpers.taggedTemplateLiteral(["<div class=\"location-osm-map-mobile-container\"></div>"])));
props.mapContainer.appendChild(container);
return new Promise(function (resolve) {
babelHelpers.classPrivateFieldSet(_this2, _map$1, babelHelpers.classPrivateFieldGet(_this2, _mapFactoryMethod$1).call(_this2, container, {
attributionControl: false,
zoomControl: BX.prop.getBoolean(props, 'zoomControl', true)
}));
babelHelpers.classPrivateFieldGet(_this2, _map$1).on('load', function () {
resolve();
});
if (babelHelpers.classPrivateFieldGet(_this2, _mode$1) === location_core.ControlMode.edit) {
babelHelpers.classPrivateFieldSet(_this2, _markerNode, main_core.Tag.render(_templateObject2 || (_templateObject2 = babelHelpers.taggedTemplateLiteral(["<div class=\"location-map-mobile-center-marker\"></div>"]))));
container.appendChild(babelHelpers.classPrivateFieldGet(_this2, _markerNode));
} else {
babelHelpers.classPrivateFieldSet(_this2, _marker$1, babelHelpers.classPrivateFieldGet(_this2, _markerFactoryMethod$1).call(_this2, [babelHelpers.classPrivateFieldGet(_this2, _location$1).latitude, babelHelpers.classPrivateFieldGet(_this2, _location$1).longitude], {
icon: babelHelpers.classPrivateFieldGet(_this2, _iconFactoryMethod).call(_this2, {
iconUrl: '/bitrix/js/location/css/image/marker.png',
iconSize: [26, 37],
iconAnchor: [13, 37]
})
}));
babelHelpers.classPrivateFieldGet(_this2, _marker$1).addTo(babelHelpers.classPrivateFieldGet(_this2, _map$1));
}
babelHelpers.classPrivateFieldGet(_this2, _map$1).setView([babelHelpers.classPrivateFieldGet(_this2, _location$1).latitude, babelHelpers.classPrivateFieldGet(_this2, _location$1).longitude], MapMobileService.getZoomByLocation(babelHelpers.classPrivateFieldGet(_this2, _location$1)));
var tile = babelHelpers.classPrivateFieldGet(_this2, _tileLayerFactoryMethod$1).call();
tile.initialize(babelHelpers.classPrivateFieldGet(_this2, _tileUrlTemplate$1), {
/**
* In order to avoid blurry tiles on retina screens, we need to apply the below options:
* detectRetina: true,
* maxNativeZoom: 22,
* maxZoom: 18,
*
* but we can't do it right now because of the following bug in the leaflet library:
* https://github.com/Leaflet/Leaflet/issues/8850
* which causes fetching non-existent tiles (19, 20, etc. zoom levels)
*/
maxZoom: 18
});
tile.addTo(babelHelpers.classPrivateFieldGet(_this2, _map$1));
babelHelpers.classPrivateFieldGet(_this2, _map$1).on('zoomstart', function (e) {
return _classPrivateMethodGet$3(_this2, _onZoomStart, _onZoomStart2).call(_this2);
});
babelHelpers.classPrivateFieldGet(_this2, _map$1).on('zoomend', function (e) {
return _classPrivateMethodGet$3(_this2, _onZoomEnd, _onZoomEnd2).call(_this2);
});
var attribution = new Control.Attribution();
attribution.setPrefix('');
attribution.addAttribution(babelHelpers.classPrivateFieldGet(_this2, _attribution$1));
babelHelpers.classPrivateFieldGet(_this2, _map$1).addControl(attribution);
babelHelpers.classPrivateFieldGet(_this2, _map$1).on('move', function (e) {
return _classPrivateMethodGet$3(_this2, _onMove, _onMove2).call(_this2);
});
babelHelpers.classPrivateFieldGet(_this2, _map$1).on('movestart', function (e) {
return _classPrivateMethodGet$3(_this2, _onMoveStart, _onMoveStart2).call(_this2);
});
babelHelpers.classPrivateFieldGet(_this2, _map$1).on('moveend', function (e) {
return _classPrivateMethodGet$3(_this2, _onMoveEnd, _onMoveEnd2).call(_this2);
});
if (props.searchOnRender) {
var center = babelHelpers.classPrivateFieldGet(_this2, _map$1).getCenter();
_classPrivateMethodGet$3(_this2, _createTimer$1, _createTimer2$1).call(_this2, center.lat, center.lng);
}
});
}
}, {
key: "destroy",
value: function destroy() {
main_core.Event.unbindAll(this);
babelHelpers.classPrivateFieldGet(this, _map$1).remove();
babelHelpers.get(babelHelpers.getPrototypeOf(MapMobileService.prototype), "destroy", this).call(this);
}
}, {
key: "mode",
set: function set(mode) {
babelHelpers.classPrivateFieldSet(this, _mode$1, mode);
},
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _mode$1);
}
}, {
key: "map",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _map$1);
},
set: function set(map) {
babelHelpers.classPrivateFieldSet(this, _map$1, map);
}
}, {
key: "zoom",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _zoom$1);
},
set: function set(zoom) {
babelHelpers.classPrivateFieldSet(this, _zoom$1, zoom);
if (babelHelpers.classPrivateFieldGet(this, _map$1)) {
babelHelpers.classPrivateFieldGet(this, _map$1).setZoom(zoom);
}
}
}, {
key: "location",
set: function set(location) {
babelHelpers.classPrivateFieldSet(this, _location$1, location);
if (location) {
this.panTo(location.latitude, location.longitude);
}
_classPrivateMethodGet$3(this, _adjustZoom$1, _adjustZoom2$1).call(this);
},
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _location$1);
}
}]);
return MapMobileService;
}(location_core.MapBase);
function _adjustZoom2$1() {
if (!babelHelpers.classPrivateFieldGet(this, _location$1)) {
return;
}
var zoom = MapMobileService.getZoomByLocation(babelHelpers.classPrivateFieldGet(this, _location$1));
if (zoom !== null && zoom !== babelHelpers.classPrivateFieldGet(this, _zoom$1)) {
this.zoom = zoom;
}
}
function _onMove2() {
if (babelHelpers.classPrivateFieldGet(this, _timerId$1) !== null) {
clearTimeout(babelHelpers.classPrivateFieldGet(this, _timerId$1));
}
}
function _onMoveStart2() {
if (babelHelpers.classPrivateFieldGet(this, _mode$1) === location_core.ControlMode.edit) {
babelHelpers.classPrivateFieldSet(this, _isMapChanging, true);
main_core.Dom.addClass(babelHelpers.classPrivateFieldGet(this, _markerNode), 'location-map-mobile-center-marker-up');
}
this.emit(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onMapViewChanged$1));
}
function _onMoveEnd2() {
if (babelHelpers.classPrivateFieldGet(this, _mode$1) === location_core.ControlMode.edit) {
if (babelHelpers.classPrivateFieldGet(this, _isMapChanging) === false) {
return;
}
var upClass = 'location-map-mobile-center-marker-up';
if (main_core.Dom.hasClass(babelHelpers.classPrivateFieldGet(this, _markerNode), upClass)) {
main_core.Dom.removeClass(babelHelpers.classPrivateFieldGet(this, _markerNode), upClass);
}
var center = babelHelpers.classPrivateFieldGet(this, _map$1).getCenter();
_classPrivateMethodGet$3(this, _createTimer$1, _createTimer2$1).call(this, center.lat, center.lng);
babelHelpers.classPrivateFieldSet(this, _isMapChanging, false);
}
}
function _onZoomStart2() {
this.emit(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onMapViewChanged$1));
}
function _onZoomEnd2() {
babelHelpers.classPrivateFieldSet(this, _zoom$1, babelHelpers.classPrivateFieldGet(this, _map$1).getZoom());
}
function _createTimer2$1(lat, lng) {
var _this3 = this;
if (babelHelpers.classPrivateFieldGet(this, _timerId$1) !== null) {
clearTimeout(babelHelpers.classPrivateFieldGet(this, _timerId$1));
}
babelHelpers.classPrivateFieldSet(this, _timerId$1, setTimeout(function () {
var requestId = main_core.Text.getRandom();
_this3.emit(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onStartChanging$1), {
requestId: requestId
});
babelHelpers.classPrivateFieldSet(_this3, _timerId$1, null);
var point = new location_core.Point(lat, lng);
babelHelpers.classPrivateFieldGet(_this3, _geocodingService$1).reverse(point, _classPrivateMethodGet$3(_this3, _getReverseZoom$1, _getReverseZoom2$1).call(_this3)).then(function (location) {
var result;
if (location) {
result = babelHelpers.classPrivateFieldGet(_this3, _locationRepository$1).findByExternalId(location.externalId, OSM.code, babelHelpers.classPrivateFieldGet(_this3, _languageId$1)).then(function (foundLocation) {
if (foundLocation) {
foundLocation.longitude = point.longitude;
if (foundLocation.address) {
foundLocation.address.longitude = point.longitude;
}
foundLocation.latitude = point.latitude;
if (foundLocation.address) {
foundLocation.address.latitude = point.latitude;
}
}
return foundLocation;
});
} else {
result = new Promise(function (resolve) {
resolve(null);
});
}
return result;
}).then(function (location) {
_this3.emit(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onEndChanging$1), {
requestId: requestId
});
_classPrivateMethodGet$3(_this3, _emitOnLocationChangedEvent$1, _emitOnLocationChangedEvent2$1).call(_this3, location);
})["catch"](function (response) {
_this3.emit(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onEndChanging$1), {
requestId: requestId
});
location_core.ErrorPublisher.getInstance().notify(response.errors);
});
}, babelHelpers.classPrivateFieldGet(this, _changeDelay$1)));
}
function _getReverseZoom2$1() {
return babelHelpers.classPrivateFieldGet(this, _zoom$1) >= 15 ? 18 : babelHelpers.classPrivateFieldGet(this, _zoom$1);
}
function _emitOnLocationChangedEvent2$1(location) {
if (babelHelpers.classPrivateFieldGet(this, _mode$1) === location_core.ControlMode.edit) {
this.emit(_classStaticPrivateFieldSpecGet$1(MapMobileService, MapMobileService, _onChangedEvent$1), {
location: location
});
}
}
var _onChangedEvent$1 = {
writable: true,
value: 'onChanged'
};
var _onStartChanging$1 = {
writable: true,
value: 'onStartChanging'
};
var _onEndChanging$1 = {
writable: true,
value: 'onEndChanging'
};
var _onMapViewChanged$1 = {
writable: true,
value: 'onMapViewChanged'
};
function _classPrivateFieldInitSpec$5(obj, privateMap, value) { _checkPrivateRedeclaration$5(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration$5(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
var _searchRequester = /*#__PURE__*/new WeakMap();
var _reverseRequester = /*#__PURE__*/new WeakMap();
var GeocodingService = /*#__PURE__*/function (_GeocodingServiceBase) {
babelHelpers.inherits(GeocodingService, _GeocodingServiceBase);
function GeocodingService(params) {
var _this;
babelHelpers.classCallCheck(this, GeocodingService);
_this = babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(GeocodingService).call(this));
_classPrivateFieldInitSpec$5(babelHelpers.assertThisInitialized(_this), _searchRequester, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$5(babelHelpers.assertThisInitialized(_this), _reverseRequester, {
writable: true,
value: void 0
});
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _searchRequester, params.searchRequester);
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _reverseRequester, params.reverseRequester);
return _this;
}
babelHelpers.createClass(GeocodingService, [{
key: "geocodeConcrete",
value: function geocodeConcrete(addressString) {
return babelHelpers.classPrivateFieldGet(this, _searchRequester).request({
query: addressString
});
}
/**
* @param {Point} point
* @param {number} zoom 1 - 18
* @return {*}
*/
}, {
key: "reverse",
value: function reverse(point, zoom) {
return babelHelpers.classPrivateFieldGet(this, _reverseRequester).request({
point: point,
zoom: zoom
});
}
}]);
return GeocodingService;
}(location_core.GeocodingServiceBase);
function _classPrivateFieldInitSpec$6(obj, privateMap, value) { _checkPrivateRedeclaration$6(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration$6(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
var _languageId$2 = /*#__PURE__*/new WeakMap();
var _sourceLanguageId$3 = /*#__PURE__*/new WeakMap();
var _mapService = /*#__PURE__*/new WeakMap();
var _mapMobileService = /*#__PURE__*/new WeakMap();
var _geocodingService$2 = /*#__PURE__*/new WeakMap();
var _autocompleteService = /*#__PURE__*/new WeakMap();
/**
* Class for the using OpenStreetMap data as data source
*/
var OSM = /*#__PURE__*/function (_BaseSource) {
babelHelpers.inherits(OSM, _BaseSource);
// todo: do we need this here?
function OSM(props) {
var _this;
babelHelpers.classCallCheck(this, OSM);
_this = babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(OSM).call(this, props));
_classPrivateFieldInitSpec$6(babelHelpers.assertThisInitialized(_this), _languageId$2, {
writable: true,
value: ''
});
_classPrivateFieldInitSpec$6(babelHelpers.assertThisInitialized(_this), _sourceLanguageId$3, {
writable: true,
value: ''
});
_classPrivateFieldInitSpec$6(babelHelpers.assertThisInitialized(_this), _mapService, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$6(babelHelpers.assertThisInitialized(_this), _mapMobileService, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$6(babelHelpers.assertThisInitialized(_this), _geocodingService$2, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$6(babelHelpers.assertThisInitialized(_this), _autocompleteService, {
writable: true,
value: void 0
});
if (!main_core.Type.isString(props.languageId) || props.languageId.trim() === '') {
throw new location_core.SourceCreationError('props.languageId must be a string');
}
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _languageId$2, props.languageId);
if (!main_core.Type.isString(props.sourceLanguageId) || props.sourceLanguageId.trim() === '') {
throw new location_core.SourceCreationError('props.sourceLanguageId must be a string');
}
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _sourceLanguageId$3, props.sourceLanguageId);
if (!(props.mapService instanceof MapService)) {
throw new location_core.SourceCreationError('props.mapService must be instanceof MapService');
}
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _mapService, props.mapService);
if (!(props.mapMobileService instanceof MapMobileService)) {
throw new location_core.SourceCreationError('props.mapMobileService must be instanceof MapMobileService');
}
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _mapMobileService, props.mapMobileService);
if (!(props.autocompleteService instanceof AutocompleteService)) {
throw new location_core.SourceCreationError('props.autocompleteService must be instanceof AutocompleteService');
}
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _autocompleteService, props.autocompleteService);
if (!(props.geocodingService instanceof GeocodingService)) {
throw new location_core.SourceCreationError('props.geocodingService must be instanceof GeocodingService');
}
babelHelpers.classPrivateFieldSet(babelHelpers.assertThisInitialized(_this), _geocodingService$2, props.geocodingService);
return _this;
}
babelHelpers.createClass(OSM, [{
key: "sourceCode",
get: function get() {
return OSM.code;
}
}, {
key: "map",
get: function get() {
return this.mapService;
}
}, {
key: "mapService",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _mapService);
}
}, {
key: "mapMobile",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _mapMobileService);
}
}, {
key: "autocompleteService",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _autocompleteService);
}
}, {
key: "geocodingService",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _geocodingService$2);
}
}, {
key: "languageId",
get: function get() {
return babelHelpers.classPrivateFieldGet(this, _languageId$2);
}
}]);
return OSM;
}(location_core.BaseSource);
babelHelpers.defineProperty(OSM, "code", 'OSM');
function _classPrivateMethodInitSpec$4(obj, privateSet) { _checkPrivateRedeclaration$7(obj, privateSet); privateSet.add(obj); }
function _classPrivateFieldInitSpec$7(obj, privateMap, value) { _checkPrivateRedeclaration$7(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration$7(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classPrivateMethodGet$4(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
var _responseConverter = /*#__PURE__*/new WeakMap();
var _hostName$1 = /*#__PURE__*/new WeakMap();
var _tokenContainer$1 = /*#__PURE__*/new WeakMap();
var _fetch = /*#__PURE__*/new WeakSet();
var _processUnauthorizedResponse$1 = /*#__PURE__*/new WeakSet();
var BaseRequester = /*#__PURE__*/function () {
function BaseRequester(props) {
babelHelpers.classCallCheck(this, BaseRequester);
_classPrivateMethodInitSpec$4(this, _processUnauthorizedResponse$1);
_classPrivateMethodInitSpec$4(this, _fetch);
_classPrivateFieldInitSpec$7(this, _responseConverter, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$7(this, _hostName$1, {
writable: true,
value: void 0
});
_classPrivateFieldInitSpec$7(this, _tokenContainer$1, {
writable: true,
value: void 0
});
this.serviceUrl = props.serviceUrl;
this.languageId = props.languageId;
this.sourceLanguageId = props.sourceLanguageId;
babelHelpers.classPrivateFieldSet(this, _responseConverter, props.responseConverter);
babelHelpers.classPrivateFieldSet(this, _hostName$1, props.hostName);
babelHelpers.classPrivateFieldSet(this, _tokenContainer$1, props.tokenContainer);
}
/**
* @param params
* @return string
*/
// eslint-disable-next-line no-unused-vars
babelHelpers.createClass(BaseRequester, [{
key: "createUrl",
value: function createUrl(params) {
throw new Error('Not implemented');
}
/**
*
* @param params
* @return {Promise<Array<Location> | Location | null | * | *[] | void>}
*/
}, {
key: "request",
value: function request(params) {
var _this = this;
return _classPrivateMethodGet$4(this, _fetch, _fetch2).call(this, params).then(function (response) {
return response ? babelHelpers.classPrivateFieldGet(_this, _responseConverter).convertResponse(response, params) : [];
})["catch"](function (response) {
console.error(response);
});
}
/**
* Sends request to server
* @param {Object} params
* @param {boolean} isUnAuth
* @return {Promise<Object>} Object is response which was converted from json string to object
*/
}]);
return BaseRequester;
}();
function _fetch2(params) {
var _this2 = this;
var isUnAuth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
return fetch(this.createUrl(params), {
method: 'GET',
headers: new Headers({
'Authorization': "Bearer ".concat(babelHelpers.classPrivateFieldGet(this, _tokenContainer$1).token),
'Bx-Location-Osm-Host': babelHelpers.classPrivateFieldGet(this, _hostName$1)
}),
referrerPolicy: 'no-referrer'
}).then(function (response) {
if (response.status === 200) {
return response.json();
}
if (response.status === 401 && !isUnAuth) {
return _classPrivateMethodGet$4(_this2, _processUnauthorizedResponse$1, _processUnauthorizedResponse2$1).call(_this2, params);
}
console.error("Response status: ".concat(response.status));
response.text().then(function (text) {
main_core.Runtime.debug(text);
});
return null;
});
}
function _processUnauthorizedResponse2$1(params) {
var _this3 = this;
return babelHelpers.classPrivateFieldGet(this, _tokenContainer$1).refreshToken().then(function () {
return _classPrivateMethodGet$4(_this3, _fetch, _fetch2).call(_this3, params, true);
});
}
var SearchRequester = /*#__PURE__*/function (_BaseRequester) {
babelHelpers.inherits(SearchRequester, _BaseRequester);
function SearchRequester() {
babelHelpers.classCallCheck(this, SearchRequester);
return babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(SearchRequester).apply(this, arguments));
}
babelHelpers.createClass(SearchRequester, [{
key: "createUrl",
value: function createUrl(params) {
var _params$limit;
var limit = (_params$limit = params.limit) !== null && _params$limit !== void 0 ? _params$limit : 5;
var result = "".concat(this.serviceUrl, "/?\n\t\t\taction=osmgateway.location.search\n\t\t\t¶ms[q]=").concat(encodeURIComponent(params.query), "\n\t\t\t¶ms[format]=json\n\t\t\t¶ms[limit]=").concat(limit, "\n\t\t\t¶ms[accept-language]=").concat(this.sourceLanguageId);
if (params.viewbox) {
result += "¶ms[viewbox]=".concat(params.viewbox);
}
return result;
}
}]);
return SearchRequester;
}(BaseRequester);
var ReverseRequester = /*#__PURE__*/function (_BaseRequester) {
babelHelpers.inherits(ReverseRequester, _BaseRequester);
function ReverseRequester() {
babelHelpers.classCallCheck(this, ReverseRequester);
return babelHelpers.possibleConstructorReturn(this, babelHelpers.getPrototypeOf(ReverseRequester).apply(this, arguments));
}
babelHelpers.createClass(ReverseRequester, [{
key: "createUrl",
value: function createUrl(params) {
var zoom = params.zoom || 18;
return "".concat(this.serviceUrl, "/?\n\t\t\taction=osmgateway.location.reverse\n\t\t\t¶ms[lat]=").concat(params.point.latitude, "\n\t\t\t¶ms[lon]=").concat(params.point.longitude, "\n\t\t\t¶ms[format]=json\n\t\t\t¶ms[zoom]=").concat(zoom, "\n\t\t\t¶ms[addressdetails]=0\t\t\t\n\t\t\t¶ms[accept-language]=").concat(this.sourceLanguageId);
}
}]);
return ReverseRequester;
}(BaseRequester);
/**
* Abstract class BaseResponseConverter
*/
var BaseResponseConverter = /*#__PURE__*/function () {
function BaseResponseConverter(props) {
var _props$sourceCode;
babelHelpers.classCallCheck(this, BaseResponseConverter);
this.languageId = props.languageId;
this.sourceCode = (_props$sourceCode = props.sourceCode) !== null && _props$sourceCode !== void 0 ? _props$sourceCode : OSM.code;
}
/**
*
* @param response
* @param params
* @return Array<Location>|Location|null
*/
// eslint-disable-next-line no-unused-vars
babelHelpers.createClass(BaseResponseConverter, [{
key: "convertResponse",
value: function convertResponse(response, params) {
throw new Error('Method "convertResponse()" not implemented');
}
}]);
return BaseResponseConverter;
}();
function _classPrivateMethodInitSpec$5(obj, privateSet) { _checkPrivateRedeclaration$8(obj, privateSet); privateSet.add(obj); }
function _checkPrivateRedeclaration$8(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classPrivateMethodGet$5(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
var _createLocation = /*#__PURE__*/new WeakSet();
var _convertLocationType = /*#__PURE__*/new WeakSet();
var _createExternalId = /*#__PURE__*/new WeakSet();
var NominatimResponseConverter = /*#__PURE__*/function (_BaseResponseConverte) {
babelHelpers.inherits(NominatimResponseConverter, _BaseResponseConverte);
function NominatimResponseConverter() {
var _babelHelpers$getProt;
var _this;
babelHelpers.classCallCheck(this, NominatimResponseConverter);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = babelHelpers.possibleConstructorReturn(this, (_babelHelpers$getProt = babelHelpers.getPrototypeOf(NominatimResponseConverter)).call.apply(_babelHelpers$getProt, [this].concat(args)));
_classPrivateMethodInitSpec$5(babelHelpers.assertThisInitialized(_this), _createExternalId);
_classPrivateMethodInitSpec$5(babelHelpers.assertThisInitialized(_this), _convertLocationType);
_classPrivateMethodInitSpec$5(babelHelpers.assertThisInitialized(_this), _createLocation);
return _this;
}
babelHelpers.createClass(NominatimResponseConverter, [{
key: "convertResponse",
/**
*
* @param {Array|Object} response
* @param {Object} params
* @return Array<Location>|Location|null
*/
// eslint-disable-next-line no-unused-vars
value: function convertResponse(response, params) {
var _this2 = this;
var result = null;
if (Array.isArray(response)) {
result = [];
if (response.length > 0) {
response.forEach(function (item) {
var location = _classPrivateMethodGet$5(_this2, _createLocation, _createLocation2).call(_this2, item);
if (location) {
result.push(location);
}
});
}
} else if (babelHelpers["typeof"](response) === 'object') {
result = _classPrivateMethodGet$5(this, _createLocation, _createLocation2).call(this, response);
}
return result;
}
}]);
return NominatimResponseConverter;
}(BaseResponseConverter);
function _createLocation2(responseItem) {
var externalId = _classPrivateMethodGet$5(this, _createExternalId, _createExternalId2).call(this, responseItem.osm_type, responseItem.osm_id);
if (!externalId) {
return null;
}
return new location_core.Location({
externalId: externalId,
latitude: responseItem.lat,
longitude: responseItem.lon,
type: _classPrivateMethodGet$5(this, _convertLocationType, _convertLocationType2).call(this, responseItem.type),
name: responseItem.display_name,
languageId: this.languageId,
sourceCode: this.sourceCode
});
}
function _convertLocationType2(type) {
var typeMap = {
country: location_core.LocationType.COUNTRY,
municipality: location_core.LocationType.LOCALITY,
city: location_core.LocationType.LOCALITY,
town: location_core.LocationType.LOCALITY,
village: location_core.LocationType.LOCALITY,
postal_town: location_core.LocationType.LOCALITY,
road: location_core.LocationType.STREET,
street_address: location_core.LocationType.ADDRESS_LINE_1,
county: location_core.LocationType.ADM_LEVEL_4,
state_district: location_core.LocationType.ADM_LEVEL_3,
state: location_core.LocationType.ADM_LEVEL_2,
region: location_core.LocationType.ADM_LEVEL_1,
floor: location_core.LocationType.FLOOR,
postal_code: location_core.AddressType.POSTAL_CODE,
room: location_core.LocationType.ROOM,
sublocality: location_core.LocationType.SUB_LOCALITY,
city_district: location_core.LocationType.SUB_LOCALITY_LEVEL_1,
district: location_core.LocationType.SUB_LOCALITY_LEVEL_1,
borough: location_core.LocationType.SUB_LOCALITY_LEVEL_1,
suburb: location_core.LocationType.SUB_LOCALITY_LEVEL_1,
subdivision: location_core.LocationType.SUB_LOCALITY_LEVEL_1,
house_number: location_core.LocationType.BUILDING,
house_name: location_core.LocationType.BUILDING,
building: location_core.LocationType.BUILDING
};
var result = location_core.LocationType.UNKNOWN;
if (typeof typeMap[type] !== 'undefined') {
result = typeMap[type];
}
return result;
}
function _createExternalId2(osmType, osmId) {
if (!osmType || !osmId) {
return null;
}
return osmType.substr(0, 1).toLocaleUpperCase() + osmId;
}
function _classPrivateMethodInitSpec$6(obj, privateSet) { _checkPrivateRedeclaration$9(obj, privateSet); privateSet.add(obj); }
function _checkPrivateRedeclaration$9(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _classPrivateMethodGet$6(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
var _createLocation$1 = /*#__PURE__*/new WeakSet();
var _getItemTypeHint = /*#__PURE__*/new WeakSet();
var _makeClarification = /*#__PURE__*/new WeakSet();
var _createLocationNameFromResponse = /*#__PURE__*/new WeakSet();
var _createAddressFromResponse = /*#__PURE__*/new WeakSet();
var _createAnswerHash = /*#__PURE__*/new WeakSet();
var _convertLocationType$1 = /*#__PURE__*/new WeakSet();
var _createExternalId$1 = /*#__PURE__*/new WeakSet();
var AutocompleteResponseConverter = /*#__PURE__*/function (_BaseResponseConverte) {
babelHelpers.inherits(AutocompleteResponseConverter, _BaseResponseConverte);
function AutocompleteResponseConverter() {
var _babelHelpers$getProt;
var _this;
babelHelpers.classCallCheck(this, AutocompleteResponseConverter);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = babelHelpers.possibleConstructorReturn(this, (_babelHelpers$getProt = babelHelpers.getPrototypeOf(AutocompleteResponseConverter)).call.apply(_babelHelpers$getProt, [this].concat(args)));
_classPrivateMethodInitSpec$6(babelHelpers.assertThisInitialized(_this), _createExternalId$1);
_classPrivateMethodInitSpec$6(babelHelpers.assertThisInitialized(_this), _convertLocationType$1);
_classPrivateMethodInitSpec$6(babelHelpers.assertThisInitialized(_this), _createAnswerHash);
_classPrivateMethodInitSpec$6(babelHelpers.assertThisInitialized(_this), _createAddressFromResponse);
_classPrivateMethodInitSpec$6(babelHelpers.assertThisInitialized(_this), _createLocationNameFromResponse);
_classPrivateMethodInitSpec$6(babelHelpers.assertThisInitialized(_this), _makeClarification);
_classPrivateMethodInitSpec$6(babelHelpers.assertThisInitialized(_this), _getItemTypeHint);
_classPrivateMethodInitSpec$6(babelHelpers.assertThisInitialized(_this), _createLocation$1);
return _this;
}
babelHelpers.createClass(AutocompleteResponseConverter, [{
key: "convertResponse",
value: function convertResponse(response, params) {
var _this2 = this;
if (!response || !Array.isArray(response.features) || response.features.length === 0) {
return [];
}
var result = [];
var hashMap = [];
var addressLine2 = response.address_tail ? response.address_tail : '';
response.features.forEach(function (item) {
if (babelHelpers["typeof"](item.properties) !== 'object') {
return;
}
// Collapse a similar location data
var hash = _classPrivateMethodGet$6(_this2, _createAnswerHash, _createAnswerHash2).call(_this2, item.properties);
if (hashMap.indexOf(hash) !== -1) {
return;
}
// Collapse block end.
hashMap.push(hash);
var location = _classPrivateMethodGet$6(_this2, _createLocation$1, _createLocation2$1).call(_this2, item, params.autocompleteServiceParams);
if (location) {
if (location.address && addressLine2) {
location.address.setFieldValue(location_core.AddressType.ADDRESS_LINE_2, addressLine2);
}
result.push(location);
}
});
return result;
}
}]);
return AutocompleteResponseConverter;
}(BaseResponseConverter);
function _createLocation2$1(responseItem, autocompleteServiceParams) {
if (!responseItem.properties) {
return null;
}
var props = responseItem.properties;
var externalId = _classPrivateMethodGet$6(this, _createExternalId$1, _createExternalId2$1).call(this, props.osm_type, props.osm_id);
if (!externalId) {
return null;
}
var name = _classPrivateMethodGet$6(this, _createLocationNameFromResponse, _createLocationNameFromResponse2).call(this, responseItem);
if (name === '') {
return null;
}
var type = _classPrivateMethodGet$6(this, _convertLocationType$1, _convertLocationType2$1).call(this, props.type);
var address = _classPrivateMethodGet$6(this, _createAddressFromResponse, _createAddressFromResponse2).call(this, responseItem, type);
var location = new location_core.Location({
address: address,
externalId: externalId,
sourceCode: this.sourceCode,
type: type,
name: name,
languageId: this.languageId
});
if (responseItem.geometry && responseItem.geometry.coordinates && responseItem.geometry.coordinates[0] && responseItem.geometry.coordinates[1]) {
location.latitude = String(responseItem.geometry.coordinates[1]);
location.longitude = String(responseItem.geometry.coordinates[0]);
}
if (address) {
var clarification = _classPrivateMethodGet$6(this, _makeClarification, _makeClarification2).call(this, address);
if (clarification !== '') {
location.setFieldValue(location_core.LocationType.TMP_TYPE_CLARIFICATION, clarification);
}
}
// We'll show a hint about the location type
if (props.osm_value && props.osm_key) {
var typeHint = _classPrivateMethodGet$6(this, _getItemTypeHint, _getItemTypeHint2).call(this, props.osm_key, props.osm_value);
if (typeHint !== '') {
location.setFieldValue(location_core.LocationType.TMP_TYPE_HINT, typeHint);
}
}
return location;
}
function _getItemTypeHint2(osmKey, osmValue) {
var result = '';
if (osmValue === 'city' || osmValue === 'town') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_CITY');
} else if (osmValue === 'village' || osmValue === 'hamlet') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_VILLAGE');
} else if (osmValue === 'locality') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_LOCALITY');
} else if (osmValue === 'hotel') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_HOTEL');
} else if (osmValue === 'suburb') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_SUBURB');
} else if (osmValue === 'island') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_ISLAND');
} else if (osmValue === 'cafe') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_CAFE');
} else if (osmValue === 'restaurant') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_RESTAURANT');
} else if (osmValue === 'river') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_RIVER');
} else if (osmKey === 'shop') {
result = main_core.Loc.getMessage('LOCATION_OSM_AUTOCOMPLETE_TYPE_SHOP');
}
return result;
}
function _makeClarification2(address) {
var clarification = '';
if (address.getFieldValue(location_core.AddressType.LOCALITY)) {
clarification += address.getFieldValue(location_core.AddressType.LOCALITY);
}
if (address.getFieldValue(location_core.AddressType.ADM_LEVEL_1)) {
if (clarification !== '') {
clarification += ', ';
}
clarification += address.getFieldValue(location_core.AddressType.ADM_LEVEL_1);
}
if (address.getFieldValue(location_core.AddressType.COUNTRY)) {
if (clarification !== '') {
clarification += ', ';
}
clarification += address.getFieldValue(location_core.AddressType.COUNTRY);
}
return clarification;
}
function _createLocationNameFromResponse2(responseItem) {
var _props$name;
if (!responseItem.properties) {
return '';
}
var props = responseItem.properties;
var name = (_props$name = props === null || props === void 0 ? void 0 : props.name) !== null && _props$name !== void 0 ? _props$name : '';
if (props.street) {
if (name !== '') {
name += ', ';
}
name += props.street;
}
if (props.housenumber) {
if (name !== '') {
name += ', ';
}
name += props.housenumber;
}
return name;
}
function _createAddressFromResponse2(responseItem, locationType) {
if (!responseItem.properties) {
return null;
}
var props = responseItem.properties;
var address = new location_core.Address({
languageId: this.languageId
});
if (responseItem.geometry && responseItem.geometry.coordinates && responseItem.geometry.coordinates[0] && responseItem.geometry.coordinates[1]) {
address.latitude = String(responseItem.geometry.coordinates[1]);
address.longitude = String(responseItem.geometry.coordinates[0]);
}
if (props.name) {
address.setFieldValue(locationType, props.name);
}
if (props.housenumber) {
address.setFieldValue(location_core.AddressType.BUILDING, props.housenumber);
}
if (props.street) {
address.setFieldValue(location_core.AddressType.STREET, props.street);
}
if (props.city) {
address.setFieldValue(location_core.AddressType.LOCALITY, props.city);
}
if (props.state && props.state !== props.city) {
address.setFieldValue(location_core.AddressType.ADM_LEVEL_1, props.state);
}
if (props.country) {
address.setFieldValue(location_core.AddressType.COUNTRY, props.country);
}
if (props.postcode) {
address.setFieldValue(location_core.AddressType.POSTAL_CODE, props.postcode);
}
return address;
}
function _createAnswerHash2(fields) {
var _fields$country, _fields$state, _fields$county, _fields$locality, _fields$city, _fields$street, _fields$name, _fields$housenumber, _fields$type;
var result = '';
result += (_fields$country = fields === null || fields === void 0 ? void 0 : fields.country) !== null && _fields$country !== void 0 ? _fields$country : '';
result += (_fields$state = fields === null || fields === void 0 ? void 0 : fields.state) !== null && _fields$state !== void 0 ? _fields$state : '';
result += (_fields$county = fields === null || fields === void 0 ? void 0 : fields.county) !== null && _fields$county !== void 0 ? _fields$county : '';
result += (_fields$locality = fields === null || fields === void 0 ? void 0 : fields.locality) !== null && _fields$locality !== void 0 ? _fields$locality : '';
result += (_fields$city = fields === null || fields === void 0 ? void 0 : fields.city) !== null && _fields$city !== void 0 ? _fields$city : '';
result += (_fields$street = fields === null || fields === void 0 ? void 0 : fields.street) !== null && _fields$street !== void 0 ? _fields$street : '';
result += (_fields$name = fields === null || fields === void 0 ? void 0 : fields.name) !== null && _fields$name !== void 0 ? _fields$name : '';
result += (_fields$housenumber = fields === null || fields === void 0 ? void 0 : fields.housenumber) !== null && _fields$housenumber !== void 0 ? _fields$housenumber : '';
result += (_fields$type = fields === null || fields === void 0 ? void 0 : fields.type) !== null && _fields$type !== void 0 ? _fields$type : '';
return result;
}
function _convertLocationType2$1(type) {
var typeMap = {
postcode: location_core.AddressType.POSTAL_CODE,
housenumber: location_core.LocationType.BUILDING,
house: location_core.LocationType.BUILDING,
name: location_core.AddressType.ADDRESS_LINE_2,
country: location_core.LocationType.COUNTRY,
city: location_core.LocationType.LOCALITY,
district: location_core.LocationType.SUB_LOCALITY_LEVEL_1,
locality: location_core.LocationType.LOCALITY,
street: location_core.LocationType.STREET,
state: location_core.LocationType.ADM_LEVEL_1,
region: location_core.LocationType.ADM_LEVEL_1,
county: location_core.LocationType.ADM_LEVEL_2
};
var result = location_core.LocationType.UNKNOWN;
if (typeof typeMap[type] !== 'undefined') {
result = typeMap[type];
} else {
console.warn('Unknown response location type: ', type);
}
return result;
}
function _createExternalId2$1(osmType, osmId) {
if (!osmType || !osmId) {
return null;
}
return osmType.substr(0, 1).toLocaleUpperCase() + osmId;
}
var OSMFactory = /*#__PURE__*/function () {
function OSMFactory() {
babelHelpers.classCallCheck(this, OSMFactory);
}
babelHelpers.createClass(OSMFactory, null, [{
key: "createOSMSource",
value: function createOSMSource(params) {
var tokenContainer = new TokenContainer({
token: params.token,
sourceRepository: new location_core.SourceRepository()
});
var osmParams = {
languageId: params.languageId,
sourceLanguageId: params.sourceLanguageId
};
var responseConverter = new NominatimResponseConverter({
languageId: params.languageId
});
var searchRequester = new SearchRequester({
languageId: params.languageId,
sourceLanguageId: params.sourceLanguageId,
tokenContainer: tokenContainer,
serviceUrl: params.serviceUrl,
hostName: params.hostName,
responseConverter: responseConverter
});
var reverseRequester = new ReverseRequester({
languageId: params.languageId,
sourceLanguageId: params.sourceLanguageId,
serviceUrl: params.serviceUrl,
hostName: params.hostName,
tokenContainer: tokenContainer,
responseConverter: responseConverter
});
var autocompleteResponseConverter = new AutocompleteResponseConverter({
languageId: params.languageId
});
osmParams.autocompleteService = new AutocompleteService({
languageId: params.languageId,
autocompletePromptsCount: params.autocompletePromptsCount || 7,
sourceLanguageId: params.sourceLanguageId,
responseConverter: autocompleteResponseConverter,
autocompleteReplacements: params.autocompleteReplacements
});
var geocodingService = new GeocodingService({
searchRequester: searchRequester,
reverseRequester: reverseRequester
});
osmParams.geocodingService = geocodingService;
osmParams.mapService = new MapService({
languageId: params.languageId,
geocodingService: geocodingService,
mapFactoryMethod: createMap,
markerFactoryMethod: marker,
locationRepository: new location_core.LocationRepository(),
sourceLanguageId: params.sourceLanguageId,
tileLayerFactoryMethod: function tileLayerFactoryMethod() {
var tileLayerAuth = new TileLayerAuth();
tileLayerAuth.setTokenContainer(tokenContainer);
tileLayerAuth.setHostName(params.hostName);
return tileLayerAuth;
},
serviceUrl: params.serviceUrl,
mapServiceUrl: params.mapServiceUrl
});
osmParams.mapMobileService = new MapMobileService({
languageId: params.languageId,
geocodingService: geocodingService,
mapFactoryMethod: createMap,
markerFactoryMethod: marker,
iconFactoryMethod: icon,
locationRepository: new location_core.LocationRepository(),
sourceLanguageId: params.sourceLanguageId,
tileLayerFactoryMethod: function tileLayerFactoryMethod() {
var tileLayerAuth = new TileLayerAuth();
tileLayerAuth.setTokenContainer(tokenContainer);
tileLayerAuth.setHostName(params.hostName);
return tileLayerAuth;
},
serviceUrl: params.serviceUrl,
mapServiceUrl: params.mapServiceUrl
});
return new OSM(osmParams);
}
}]);
return OSMFactory;
}();
exports.OSM = OSM;
exports.OSMFactory = OSMFactory;
}((this.BX.Location.OSM = this.BX.Location.OSM || {}),BX,BX,BX.Location.Core));
//# sourceMappingURL=osm.bundle.js.map