diff --git a/external/Yandex.addon.LoadApi.js b/external/Yandex.addon.LoadApi.js new file mode 100644 index 000000000..ef60ef3e9 --- /dev/null +++ b/external/Yandex.addon.LoadApi.js @@ -0,0 +1,112 @@ +// @options apiLoader: function or thennable = undefined +// Function that will be used to load Yandex JS API (if it turns out not enabled on layer add). +// Must return any Promise-like thennable object. +// Instead of function it's also possible to specify Promise/thennable directly as option value. + +// Alternatively: +// Predefined loader will be used if apiUrl / apiParams specified. + +// @options apiVersion: string = '2.1' +// Can be specified to use api version other then default, +// more info: https://tech.yandex.com/maps/jsapi/doc/2.1/versions/index-docpage/ + +// @options apiUrl: string = 'https://api-maps.yandex.ru/{version}/' +// This may need to be changed for using commercial versions of the api. +// It's also possible to directly include params in apiUrl. +// Please note that some parameters are mandatory, +// more info: https://tech.yandex.com/maps/jsapi/doc/2.1/dg/concepts/load-docpage/ + +// @option apiParams: object or string +// Parameters to use when enabling API. +// There are some predefined defaults (see in code), but 'apikey' is still mandatory. +// It's also possible to specify apikey directly as apiParams string value. + +// @method apiLoad(options?: Object): this +// Loads API immediately. +// If API loader / params are not specified in layer options, +// they must be provided in `options` argument (otherwise it may be omitted). + +/* global ymaps: true */ + +L.Yandex.include({ + _initLoader: function (options) { + if (this._loader) { return; } + options = options || this.options; + var loader = options.apiLoader; + if (loader) { + if (loader.then) { loader = {loading: loader}; } + } else { + var url = this._makeUrl(options); + loader = url && this._loadScript.bind(this,url); + } + if (loader) { + L.Yandex.prototype._loader = loader; + } + }, + + loadApi: function (options) { + if (typeof ymaps !== 'undefined') { return this; } + this._initLoader(options); + var loader = this._loader; + if (!loader) { + throw new Error('api params expected in options'); + } + if (!loader.loading) { + loader.loading = loader(); + } + return this; + }, + + _initApi: function (afterload) { + var loader = this._loader; + if (typeof ymaps !== 'undefined') { + return ymaps.ready(this._initMapObject, this); + } else if (afterload || !loader) { + throw new Error('API is not available'); + } + var loading = loader.loading; + if (!loading) { + loading = loader(); + loader.loading = loading; + } + loading.then(this._initApi.bind(this,'afterload')); + }, + + _apiDefaults: { // https://tech.yandex.com/maps/jsapi/doc/2.1/dg/concepts/load-docpage/ + url: 'https://api-maps.yandex.ru/{version}/', + version: '2.1', + params: { + lang: 'ru_RU', + onerror: 'console.error' + } + }, + + _makeUrl: function (options) { + var url = options.apiUrl, + params = options.apiParams, + def = this._apiDefaults; + if (!url && !params) { return false; } + if (params) { + if (typeof params === 'string') { params = { apikey: params }; } + params = L.extend({}, def.params, params); + url = (url || def.url) + + L.Util.getParamString(params,url); + } + return L.Util.template(url, { version: options.apiVersion || def.version }); + }, + + _loadScript: function (url) { + return new Promise(function (resolve, reject) { + var script = document.createElement('script'); + script.onload = resolve; + script.onerror = function () { + reject('API loading failed'); + }; + script.src = url; + document.body.appendChild(script); + }); + } + +}); + +L.Yandex.addInitHook(L.Yandex.prototype._initLoader); diff --git a/external/Yandex.js b/external/Yandex.js index bebc89ef2..ac1b66018 100644 --- a/external/Yandex.js +++ b/external/Yandex.js @@ -1,180 +1,157 @@ -/* - * L.TileLayer is used for standard xyz-numbered tile layers. - */ +// https://tech.yandex.com/maps/doc/jsapi/2.1/quick-start/index-docpage/ /* global ymaps: true */ L.Yandex = L.Layer.extend({ - includes: L.Evented ? L.Evented.prototype : L.Mixin.Events, options: { + type: 'yandex#map', // 'map', 'satellite', 'hybrid', 'map~vector' | 'overlay', 'skeleton' + mapOptions: { // https://tech.yandex.com/maps/doc/jsapi/2.1/ref/reference/Map-docpage/#Map__param-options + // yandexMapDisablePoiInteractivity: true, + balloonAutoPan: false, + suppressMapOpenBlock: true + }, + overlayOpacity: 0.8, minZoom: 0, - maxZoom: 18, - attribution: '', - opacity: 1, - traffic: false + maxZoom: 19 }, - possibleShortMapTypes: { - schemaMap: 'map', - satelliteMap: 'satellite', - hybridMap: 'hybrid', - publicMap: 'publicMap', - publicMapInHybridView: 'publicMapHybrid' - }, - - _getPossibleMapType: function (mapType) { - var result = 'yandex#map'; - if (typeof mapType !== 'string') { - return result; - } - for (var key in this.possibleShortMapTypes) { - if (mapType === this.possibleShortMapTypes[key]) { - result = 'yandex#' + mapType; - break; - } - if (mapType === ('yandex#' + this.possibleShortMapTypes[key])) { - result = mapType; - } - } - return result; - }, - - // Possible types: yandex#map, yandex#satellite, yandex#hybrid, yandex#publicMap, yandex#publicMapHybrid - // Or their short names: map, satellite, hybrid, publicMap, publicMapHybrid initialize: function (type, options) { - L.Util.setOptions(this, options); - //Assigning an initial map type for the Yandex layer - this._type = this._getPossibleMapType(type); - }, - - onAdd: function (map, insertAtTheBottom) { - this._map = map; - this._insertAtTheBottom = insertAtTheBottom; - - // create a container div for tiles - this._initContainer(); - this._initMapObject(); - - // set up events - map.on('viewreset', this._reset, this); - - this._limitedUpdate = L.Util.throttle(this._update, 150, this); - map.on('move', this._update, this); - - map._controlCorners.bottomright.style.marginBottom = '3em'; - - this._reset(); - this._update(true); + if (typeof type === 'object') { + options = type; + type = false; + } + options = L.Util.setOptions(this, options); + if (type) { options.type = type; } + this._isOverlay = options.type.indexOf('overlay') !== -1 || + options.type.indexOf('skeleton') !== -1; }, - onRemove: function (map) { - this._map._container.removeChild(this._container); - - this._map.off('viewreset', this._reset, this); - - this._map.off('move', this._update, this); - - if (map._controlCorners) { - map._controlCorners.bottomright.style.marginBottom = '0em'; + _setStyle: function (el, style) { + for (var prop in style) { + el.style[prop] = style[prop]; } }, - getAttribution: function () { - return this.options.attribution; + _initContainer: function (parentEl) { + var zIndexClass = this._isOverlay ? 'leaflet-overlay-pane' : 'leaflet-tile-pane'; + var _container = L.DomUtil.create('div', 'leaflet-yandex-container leaflet-pane ' + zIndexClass); + var opacity = this.options.opacity || this._isOverlay && this.options.overlayOpacity; + if (opacity) { + L.DomUtil.setOpacity(_container, opacity); + } + var auto = { width: '100%', height: '100%' }; + this._setStyle(parentEl, auto); // need to set this explicitly, + this._setStyle(_container, auto); // otherwise ymaps fails to follow container size changes + return _container; }, - setOpacity: function (opacity) { - this.options.opacity = opacity; - if (opacity < 1) { - L.DomUtil.setOpacity(this._container, opacity); + onAdd: function (map) { + var mapPane = map.getPane('mapPane'); + if (!this._container) { + this._container = this._initContainer(mapPane); + map.once('unload', this._destroy, this); + this._initApi(); } + mapPane.appendChild(this._container); + if (!this._yandex) { return; } + this._setEvents(map); + this._update(); }, - setElementSize: function (e, size) { - e.style.width = size.x + 'px'; - e.style.height = size.y + 'px'; + beforeAdd: function (map) { + map._addZoomLimit(this); }, - _initContainer: function () { - var tilePane = this._map._container, - first = tilePane.firstChild; + onRemove: function (map) { + map._removeZoomLimit(this); + }, - if (!this._container) { - this._container = L.DomUtil.create('div', 'leaflet-yandex-layer'); - this._container.id = '_YMapContainer_' + L.Util.stamp(this); - this._container.style.zIndex = 'auto'; + _destroy: function (e) { + if (!this._map || this._map === e.target) { + if (this._yandex) { + this._yandex.destroy(); + delete this._yandex; + } + delete this._container; } + }, - if (this.options.overlay) { - first = this._map._container.getElementsByClassName('leaflet-map-pane')[0]; - first = first.nextSibling; - // XXX: Bug with layer order - if (L.Browser.opera) - this._container.className += ' leaflet-objects-pane'; + _setEvents: function (map) { + var events = { + move: this._update + }; + if (this._zoomAnimated) { + events.zoomanim = this._animateZoom; } - tilePane.insertBefore(this._container, first); - - this.setOpacity(this.options.opacity); - this.setElementSize(this._container, this._map.getSize()); + map.on(events, this); + this.once('remove', function () { + map.off(events, this); + this._container.remove(); // we do not call this until api is initialized (ymaps API expects DOM element) + }, this); }, - _initMapObject: function () { - if (this._yandex) return; + _update: function () { + var map = this._map; + var center = map.getCenter(); + this._yandex.setCenter([center.lat, center.lng], map.getZoom()); + var offset = L.point(0,0).subtract(L.DomUtil.getPosition(map.getPane('mapPane'))); + L.DomUtil.setPosition(this._container, offset); // move to visible part of pane + }, - // Check that ymaps.Map is ready - if (ymaps.Map === undefined) { - return ymaps.load(['package.map'], this._initMapObject, this); - } + _resyncView: function () { // for use in addons + if (!this._map) { return; } + var ymap = this._yandex; + this._map.setView(ymap.getCenter(), ymap.getZoom(), { animate: false }); + }, - // If traffic layer is requested check if control.TrafficControl is ready - if (this.options.traffic) - if (ymaps.control === undefined || - ymaps.control.TrafficControl === undefined) { - return ymaps.load(['package.traffic', 'package.controls'], - this._initMapObject, this); + _animateZoom: function (e) { + var map = this._map; + var viewHalf = map.getSize()._divideBy(2); + var topLeft = map.project(e.center, e.zoom)._subtract(viewHalf)._round(); + var offset = map.project(map.getBounds().getNorthWest(), e.zoom)._subtract(topLeft); + var scale = map.getZoomScale(e.zoom); + this._yandex.panes._array.forEach(function (el) { + if (el.pane instanceof ymaps.pane.MovablePane) { + var element = el.pane.getElement(); + L.DomUtil.addClass(element, 'leaflet-zoom-animated'); + L.DomUtil.setTransform(element, offset, scale); } - //Creating ymaps map-object without any default controls on it - var map = new ymaps.Map(this._container, {center: [0, 0], zoom: 0, behaviors: [], controls: []}); - - if (this.options.traffic) - map.controls.add(new ymaps.control.TrafficControl({shown: true})); - - if (this._type === 'yandex#null') { - this._type = new ymaps.MapType('null', []); - map.container.getElement().style.background = 'transparent'; - } - map.setType(this._type); - - this._yandex = map; - this._update(true); - - //Reporting that map-object was initialized - this.fire('MapObjectInitialized', {mapObject: map}); + }); }, - _reset: function () { - this._initContainer(); + _initApi: function () { // to be extended in addons + ymaps.ready(this._initMapObject, this); }, - _update: function (force) { - if (!this._yandex) return; - this._resize(force); - - var center = this._map.getCenter(); - var _center = [center.lat, center.lng]; - var zoom = this._map.getZoom(); - - if (force || this._yandex.getZoom() !== zoom) - this._yandex.setZoom(zoom); - this._yandex.panTo(_center, {duration: 0, delay: 0}); + _mapType: function () { + var shortType = this.options.type; + if (!shortType || shortType.indexOf('#') !== -1) { + return shortType; + } + return 'yandex#' + shortType; }, - _resize: function (force) { - var size = this._map.getSize(), style = this._container.style; - if (style.width === size.x + 'px' && style.height === size.y + 'px') - if (force !== true) return; - this.setElementSize(this._container, size); - this._yandex.container.fitToViewport(); + _initMapObject: function () { + ymaps.mapType.storage.add('yandex#overlay', new ymaps.MapType('overlay', [])); + ymaps.mapType.storage.add('yandex#skeleton', new ymaps.MapType('skeleton', ['yandex#skeleton'])); + ymaps.mapType.storage.add('yandex#map~vector', new ymaps.MapType('map~vector', ['yandex#map~vector'])); + var ymap = new ymaps.Map(this._container, { + center: [0, 0], zoom: 0, behaviors: [], controls: [], + type: this._mapType() + }, this.options.mapOptions); + + if (this._isOverlay) { + ymap.container.getElement().style.background = 'transparent'; + } + this._container.remove(); + this._yandex = ymap; + if (this._map) { this.onAdd(this._map); } + + this.fire('load'); } }); + +L.yandex = function (type, options) { + return new L.Yandex(type, options); +}; diff --git a/external/versions.md b/external/versions.md index f42adff2c..db4783027 100644 --- a/external/versions.md +++ b/external/versions.md @@ -39,7 +39,7 @@ 3.1.0 * Bing.js: d169e06d44f47485bd6f66a6c25efcb19de87478 used in: basemap-bing - * Yandex.js: 21998f069d00952807ae070fb5b5c9992e7a6c1f + * Yandex.js, Yandex.addon.LoadApi.js: ed47982c0c2c9037d74557ae46783e0f294a201b used in: basemap-yandex * https://github.com/makinacorpus/Leaflet.FileLayer