Current Path : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/location/osm/dist/ |
Current File : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/location/osm/dist/osm.bundle.js |
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: '<a href="https://leafletjs.com" title="A JS library for interactive maps" target="_blank">Leaflet</a> | Map data © <a href="https://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> 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: '<a href="https://leafletjs.com" title="A JS library for interactive maps" target="_blank">Leaflet</a> | Map data © <a href="https://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> 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