«Коллективный разум сообщества гоблинов гораздо больше суммы потенциала разрозненных гоблинов»
MediaWiki:WikiLeaflet.js
Зеленопедия
Замечание. Возможно, после сохранения вам придётся очистить кэш своего браузера, чтобы увидеть изменения.
- Firefox / Safari: Удерживая клавишу Shift, нажмите на панели инструментов Обновить либо нажмите Ctrl-F5 или Ctrl-R (⌘-R на Mac)
- Google Chrome: Нажмите Ctrl-Shift-R (⌘-Shift-R на Mac)
- Internet Explorer: Удерживая Ctrl, нажмите Обновить либо нажмите Ctrl-F5
- Opera: Выберите очистку кэша в меню Инструменты → Настройки
/* Скрипт WikiLeaflet с http://traditio.ru/wiki/WikiLeaflet , спиз**но с https://traditio.wiki/MediaWiki:WikiLeaflet.js */ /* * Скрипт WikiLeaflet версии 1.0 * Требования: jQuery 1.8 и LeafLet 0.7.3. * Для обновления скрипта с сервера перезагрузить * /w/index.php?title=MediaWiki%3AWikiLeaflet.js&action=raw&ctype=text/javascript&dontcountme=s&v=1.0 */ /* * Точка входа. Вызывается по AJAX из MediaWiki:Common.js: */ function wlRender (obj) { /* // V-- по умолчанию. V-- во избежание повторной обработки. var $map_containers = $obj ? $obj.find ('.wikileaf:not(.leaflet-container)') : $('.wikileaf:not(.leaflet-container)'); // Обработка каждой карты: if ($map_containers && $map_containers.each) { $map_containers.each (function () { var map_settings = wf$2settings ($(this), wlRules); attachMap (this, map_settings); }); } */ var map_settings = wf$2settings ($(obj), wlRules); attachMap (obj, map_settings); } // -- function wlRender (obj); /* * Настройки преобразований объектов вики-страницы в объекты карты: */ // Синтаксический сахар для конфигурации. Нужно определить перед wlRules: // Возможность упорядочивать вызов функций: Function.prototype.at = function (/* int */ order) { this.order = order; return this; }; function __url (/* string */ def) { return {__type: 'url', __default: def || ''}; } var __float = {__type: 'real', __default: null}; var __int = {__type: 'integer', __default: null}; var __float0 = {__type: 'real', __default: 0}; var wlRules = { /* * Настройки карты в целом. */ // Центр карты: wlCenter : {lat: __float, lon: __float, zoom: __int} // Пределы карты: , wlBounds : {south: __float0, west: __float0, north: __float0, east: __float0, flag: false} // Высота карты: , wlHeight : 500 // Начальный тайловый сервер: , wlTiles : function ($obj) { var str = wf$2atomic ($obj, 'string', 'osm'); return $.type (wlTileServers [str] ) !== 'undefined' ? str : $.type (wlTileServers [wlTileAliases [str]]) !== 'undefined' ? wlTileAliases [str] : wlTileAliases ['osm']; } // -- wlTiles // Насильственное задание режима редактирования: , wlEditable : $('textarea').length > 0 // Правила кластеризации: , wlCluster : {distance: 0, separator: ', ', iconType: 'default'} /* * Элементы карты. */ // Типы отметок: , wlMarker : { __index : 'iconID' // -- будет создан ассоциативный массив. , iconID : 'default' // Характеристики значка: , iconURL : __url (LeafletRoot + 'images/marker.png') , iconWidth : 25, iconHeight : 41 // Характеристики тени: , shadowURL : __url (LeafletRoot + 'trans.gif') , shadowWidth : function ($obj) { // В этой функции и далее до конца wlMarker, this -- формируемый объект настроек wlMarker. return wf$2atomic ($obj, 'integer', this.shadowURL === LeafletRoot + 'trans.gif' ? 41 : 1); } , shadowHeight : function ($obj) { return wf$2atomic ($obj, 'integer', this.shadowURL === LeafletRoot + 'trans.gif' ? 41 : 1); } // Характеристики якоря: , anchorWidth : function ($obj) { return wf$2atomic ($obj, 'integer', Math.floor (this.iconWidth / 2)); } , anchorHeight : function ($obj) { return wf$2atomic ($obj, 'integer', this.iconHeight); } // Характеристики всплывающего окна: , popupWidth : 0 , popupHeight : function ($obj) { return wf$2atomic ($obj, 'integer', -this.anchorHeight + 2); }.at (1) // -- выполнение после предыдущих функций. // Характеристики текстовой метки: , labelShiftWidth : function ($obj) { return wf$2atomic ($obj, 'integer', this.iconWidth - this.anchorWidth); }.at (1) // -- выполнение после предыдущих функций. , labelShiftHeight : 0 , labelText : '' , wlCategories : wlItems // -- список через ;. , clusteredIconType : 'default' // -- значок для кластеризованной точки. , clusterRating : 0 // -- эта точка захватит кластер, если у неё будет больше рейтинг. } // -- wlMarkers. // Отметки: , wlPoint : [{ // -- будет создан массив. lat: __float, lon: __float, alt: __float0 , content: '', iconType: 'default' , labelText: '', labelShiftWidth: 0, labelShiftHeight: 0 , wlCategories : wlItems // -- список через ;. , clusteredIconType : 'default' // -- значок для кластеризованной точки. , clusterRating : 0 // -- эта точка захватит кластер, если у неё будет больше рейтинг. }] // -- wlPoint. // GeoJSON: , wlGeoJSON : [{ // -- будет создан массив. json : '' , content: '' , wlCategories : wlItems // -- список через ;. }] // -- GeoJSON. // Фильтры: , wlFilterList : false }; // -- wlRules. /* * Функции разбора настроек карты: */ // Функция, находящая в $context объекты .selector и преобразующая их правилом settings в настройки карты. // Указатель this -- формируемый родительский объект: /* any */ function wf$find (/* jQuery */ $context, /* string */ selector, /* any */ settings) { wlStripParagraphs ($context); return wf$2settings.call (this, $context.find ('.' + selector), settings); } // -- /* any */ function wf$find (/* jQuery */ $context, /* string */ selector, /* any */ settings) // Функция, преобразующая $obj в настройки карты правилом settings. // Указатель this -- формируемый родительский объект: /* any */ function wf$2settings (/* jQuery */ $obj, /* any */ settings) { // Преобразование инструкций по разбору: var processed_settings = wfType (settings); var type = processed_settings.type; var defaults = processed_settings.defaults; var parent = this; var $source = processed_settings.source ($obj); var result; switch (type) { case 'function': // В this функции передаётся формируемый объект настроек: return defaults.call (parent, $source); case 'object': result = wf$2objects.call (parent, $source, defaults); // В зависимости от того, что нужно вышестоящему объекту, вернуть один объект или несколько: return (this.__type === 'assoc' || this.__type === 'array') ? result : result [result.length - 1]; case 'array': case 'assoc': return wf$2list.call (parent, $source, defaults, processed_settings.initial (parent), processed_settings.appender); // Атомарные типы: case 'boolean': return defaults || $source.length; case 'real': case 'integer': case 'string': case 'url': return wf$2atomic.call (parent, $source, type, defaults); } // -- switch (type) } // -- /* any */ function wf$2settings (/* jQuery */ $obj, /* any */ settings) // Функции для преобразования атомарных значений: /* atomic */ function wf$2atomic (/* jQuery */ $obj, /* string или function */ caster, /* atomic */ def) { // Обработка значения в зависимости от требуемого типа: var processor = $.isFunction (caster) ? caster : cast [caster]; var html = $.trim ($obj.html ()); return processor (html, def); } // -- /* atomic */ function wf$2atomic (/* jQuery */ $obj, /* string или function */ caster, /* atomic */ def) var cast = { real : function (/* string */ html, /* atomic */ def) { var result = html.replace (',', '.'); return result.match (/^-?\d+(\.\d+)?$/) ? parseFloat (result) : def; } , integer : function (/* string */ html, /* atomic */ def) { return html.match (/^-?\d+$/) ? parseInt (html, 10) : def; } , 'string' : function (/* string */ html, /* atomic */ def) { return html ? html : def; } , url : function (/* string */ html, /* atomic */ def) { // Переписать регулярное выражение: var img_path = '(thumb\\/)?[01-9a-zA-Z]{1}\\/[01-9a-zA-Z]{2}(\\/[01-9a-zA-Z_.()-]+\\.([Gg][Ii][Ff]|[Pp][Nn][Gg]|[Jj][Pp][Ee][Gg])){1,2}' var components = (new RegExp ('^(' + ImageRoot + ')?(' + img_path + ')$')).exec (html); return components ? (components [1] || ImageRoot) + components [2] : def; } // -- url : function (/* string */ html, /* atomic */ def) }; // -- функции для преобразования атомарных значений. // Получение объекта настроек -- записи. // $obj -- объект jQuery, содержащий нужный узел DOM, // structure -- структура объекта. // Возвращается всегда массив, хотя бы из одного элемента: /* array */ function wf$2objects (/* jQuery */ $obj, /* object */ structure) { var defaults = {__type: 'object'}; var delayed = []; var processed; $.each (structure, function (/* string */ field, /* any */ rule) { if ($.isFunction (rule)) { // Правило обработки -- функция. Все они с отложенным вызовом: var order = rule.order || 0; delayed [order] = delayed [order] || {}; delayed [order] [field] = rule; } else { // Рекурсивный вызов немедленно. Нужно также передать this: processed = wf$find.call (defaults, $obj, field, rule); } // -- if ($.isFunction (rule)) defaults [field] = processed; }); // -- $.each (structure, function (/* string */ field, /* any */ rule) var result; result = [defaults]; // Отложенный вызов функций: $.each (result, function (i, current) { $.each (delayed, function (j, order) { if (order) { $.each (order, function (/* string */ field, /* function */ func) { // Функция должна получить указатель на объект в this: current [field] = wf$find.call (current, $obj, field, func); }); // -- $.each (order, function (/* string */ field, /* function */ func } // -- if (order) }); // -- $.each (delayed, function (j, order) }); // -- $.each (result, function (i, current) // Удаление объектов, в которых не заполнены обязательные поля: var final = []; $.each (result, function () { var complete = true; $.each (this, function (field, value) { if (value === null) { return complete = false; } }); // -- $.each (this, function (field, value) if (complete) final.push (this); }); // -- $.each (result, function () return final; } // -- /* array */ function wf$2objects (/* jQuery */ $obj, /* object */ structure) // Получение массива или ассоциативного массива. // $obj -- объект jQuery с уже отобранными нужными узлами DOM, // structure -- правила формирования элемента массива, // initial -- начальное значение списка // appender -- функция, добавляющая простой массив к простому или ассоциативному массиву: /* array или object */ function wf$2list (/* jQuery */ $objects, /* object */ structure, /* array or assoc */ initial, /* function */ appender) { var result = initial; // -- инициализация. var context = this; $objects.each (function (i, dom) { // Рекурсивный вызов: var current = wf$2settings.call (result, $(dom), structure); if (current && !$.isArray (current)) { current = [current]; } appender (result, current); }); return result; } // -- /* array или object */ function wf$2list (/* jQuery */ $objects, /* object */ structure, /* function */ appender) // Получение типа из настроек: /* string */ function wfType (/* object */ settings) { var ret = { defaults : settings // -- глубокая копия. , type : $.type (settings) , source : function ($obj) { return $obj.last (); } }; switch (ret.type) { case 'object': ret.defaults = $.extend (true, {}, ret.defaults); // -- глубокая копия. if ($.type (settings.__type) === 'string') { ret.type = settings.__type; ret.defaults = settings.__default; } else if ($.type (settings.__index) !== 'undefined') { // Ассоциативный массив: ret.type = 'assoc'; var index = ret.defaults.__index; delete ret.defaults.__index; // Исходное значение: ret.initial = ret.defaults [index] ? function (/* object */ parent) { var ini = wf$2settings.call (parent, $(''), ret.defaults); if ($.isArray (ini)) { ini = ini [0]; } var ini_folded = {__type: 'assoc'}; ini_folded [ret.defaults [index]] = ini; return ini_folded; } : function () { return {__type: 'assoc'}; }; // Добавлятель: ret.appender = function (/* object */ append2, /* array */ appended) { $.each (appended || {}, function (i, current) { append2 [current [index]] = current; }); }; // Неуникальные значения: ret.source = function ($obj) { return $obj; }; } // -- if ($.type (settings.__type) === 'string') break; case 'array': // Простой массив: ret.defaults = settings [0]; ret.initial = function () { var r = ret.defaults ? [] : [ret.defaults]; r.__type = 'array'; return r; }; ret.appender = function (/* object */ append2, /* array */ appended) { $.merge ((append2 || []), (appended || [])); }; // Неуникальные значения: ret.source = function ($obj) { return $obj; }; break; case 'number': ret.type = Math.floor (settings) !== settings ? 'real' : 'integer'; break; } // -- switch ($.type (settings)) return ret; } // -- /* string */ function wfType (/* object */ settings) // Служебная функция для удаления подчинённых абзацев: function wlStripParagraphs (/* jQuery */ $object) { $object.children ('p').each (function () { var $this = $(this); $this.replaceWith ($this.contents ()); }); } // -- function wlStripParagraphs (/* jQuery */ $object). // Функция, режущая список в массив по ;: /* array */ function wlItems (/* jQuery */ $obj) { var html = wf$2atomic ($obj, 'string', ''); return html ? (html.split (/\s*;\s*/) || []) : []; } /* * Функции, выводящие карту: */ // Прикрепляет к объекту div карту на основании разобранных параметров settings: /* L.Map */ function attachMap (/* DOM */ div, /* object */ settings) { var $div = $(div); // window.alert ('In attachMap: ' + showObj (div)); // Максимальная ширина всплывалки: var popupMaxWidth = Math.floor (0.8 * $div.width ()); // Установка высоты и очистка карты: $div.css ('height', settings.wlHeight + 'px').html (''); // Типы значков: var markers = {}; var labels = {}; $.each (settings.wlMarker, function (id, marker) { if (id.substring (0, 2) === '__') return true; // -- пропуск __type. // Создание типа значка: var markerClass = new L.Icon ({//L.Icon.extend ({ iconUrl : marker.iconURL , shadowUrl : marker.shadowURL , iconSize : new L.Point (marker.iconWidth , marker.iconHeight) , shadowSize : new L.Point (marker.shadowWidth, marker.shadowHeight) , iconAnchor : new L.Point (marker.anchorWidth, marker.anchorHeight) , popupAnchor : new L.Point (marker.popupWidth , marker.popupHeight) }); markers [id] = markerClass; //new markerClass (); // Создание типа текстовой метки: labels [id] = { textLabel : marker.textLabel , labelShiftWidth : marker.labelShiftWidth , labelShiftHeight : marker.labelShiftHeight , wlCategories : marker.wlCategories }; }); // -- $.each (settings.wlMarker, function (id, marker). // Наследование меток и категорий от типа значка: var inheritAll = function (/* array */ points, /* object */ markers, /* boolean */ filters) { $.each (points, function () { var point = this; var iconType = markers [point.iconType] || markers ['default']; // Служебная функция-замыкание для экономии кода: var inherit = function (/* string */ attr) { var own = point [attr]; point [attr] = !own || $.isArray (own) && own.length === 0 ? iconType [attr] : own; } // -- var inherit = function (/* string */ attr, /* any */ own) if (filters) { inherit ('wlCategories'); } inherit ('labelText'); inherit ('clusteredIconType'); inherit ('clusterRating'); if (point.labelText !== '') { // Настройки для местоположения метки: inherit ('labelShiftWidth'); inherit ('labelShiftHeight'); } // -- if (this.labelText !== ''). }); // -- $.each (settings.wlPoint, function (). }; // -- var inheritAll = function (/* array */ points, /* object */ markers, /* boolean */ filters) // Служебные функции: var min = function (a, b) { return a < b || b === null ? a : b; } var max = function (a, b) { return a > b || b === null ? a : b; } var union = function (a, b) { if (!a) return b; if (!b) return a; return { south: min (a.south, b.south) , north: max (a.north, b.north) , west : min (a.west, b.west) , east : max (a.east, b.east) }; } // -- var union = function (a, b) // Препроцессинг точек и GeoJSON -- всё, что можно сделать, не зная зума: var preprocessPoints = /* object */ function (/* array */ points) { var bounds = null; $.each (points, function (i, point) { bounds = union (bounds, {south: point.lat, west: point.lon, north: point.lat, east: point.lon}); }); return {points: points, bounds: bounds}; } // -- var preprocessPoints = /* object */ function (/* array */ points, /* object */ center) var preprocessGeoJSON = function (/* array */ jsons) { var bounds = null; var processed_jsons = []; $.each (jsons, function (i, json) { var parsedJSON = null; try { parsedJSON = $.parseJSON (json.json); } catch (e) {} if (!parsedJSON) return true; // -- пропустить итерацию. var gjLayer = new L.GeoJSON (parsedJSON, { onEachFeature: function (/* GeoJSON */ feature, /* ILayer */ layer) { // Добавление стиля изнутри GeoJSON: if (feature.properties.style && feature.layer.setStyle) { feature.layer.setStyle (feature.properties.style); } // Попапы для частей GeoJSON: if (feature.properties && feature.properties.description) { layer.bindPopup (feature.properties.description); } } // -- onEachFeature: function (/* GeoJSON */ feature, /* ILayer */ layer) }); // -- var gjLayer = new L.GeoJSON (parsedJSON, ... gjLayer.wlCategories = json.wlCategories; gjLayer.content = json.content; processed_jsons.push (gjLayer); // Обновление крайних точек: var bc = gjLayer.getBounds (); bounds = union (bounds, { south: bc.getSouth (), north: bc.getNorth (), west: bc.getWest (), east: bc.getEast () }); }); // -- $.each (jsons, function (i, json) return {jsons: processed_jsons, bounds: bounds}; } // -- var preprocessGeoJSON = function (/* array */ jsons) // Асинхронное размещение объектов на карте: // Служебная функция-замыкание, общая для значков, помет и GeoJSON: var place = function (/* function */ constructor, /* object */ object_settings, /* DOM */ popup, /* L.Map */ m) { // Создание объекта: var obj = constructor (object_settings); if (!obj) return; // Получение категорий: var categories = settings.wlFilterList && object_settings.wlCategories.length > 0 ? object_settings.wlCategories : null; // Внесение в категории, если надо: if (categories) { // Объект в категориях: m.wlCategories = m.wlCategories || {}; $.each (categories, function (i, category) { m.wlCategories [category] = m.wlCategories [category] || L.layerGroup ().addTo (m); // Добавление к категории глубокой копии объекта, // на случай, если он в нескольких категориях: var clone = $.extend (true, {}, obj); // Подключение всплывающего HTML: if (popup) { clone.bindPopup (popup, {maxWidth: popupMaxWidth}); } m.wlCategories [category].addLayer (clone); }); // -- $.each (categories, function (i, category) } else { // Некатегоризированный объект: // Подключение всплывающего HTML: if (popup) { obj.bindPopup (popup, {maxWidth: popupMaxWidth}); } m.addLayer (obj); } // -- if (categories) return obj; }; // -- var place = function (/* function */ constructor, /* object */ object_settings, /* DOM */ popup, /* L.Map */ m) // Маркеры: var addMarkers = function (/* L.Map */ m, /* array */ points) { $.each (points, function (i, point) { // Маркер: place ( function (/* object */ point_settings) { marker = new L.Marker ( new L.LatLng (point_settings.lat, point_settings.lon) , {icon: markers [point_settings.iconType] || markers ['default']} ); return marker; } // -- function (/* object */ point_settings) , point , point.content , m ); // -- place (...) }); // -- $.each (points, function (i, point) }; // -- var addMarkers = function (/* L.Map */ m, /* array */ points) // Метки: var addLabels = function (/* L.Map */ m, /* array */ labels) { $.each (labels, function () { var label = this; // Текстовая пометка, если надо: if (label.labelText) { place ( function (/* object */ label_settings) { return new L.LabelOverlay ( new L.LatLng (label_settings.lat, label_settings.lon) , label_settings.labelText , {offset: new L.Point (label_settings.labelShiftWidth, label_settings.labelShiftHeight)} ); } // -- function (/* object */ label_settings) , label , null , m ); // -- place (...) } // -- if (label.labelText !== '') }); // -- $.each (labels, function (). }; // -- var addLabels = function (/* L.Map */ m, /* array */ labels) // Размещение GeoJSON на карте: var addGeoJSON = /* array */ function (/* L.Map */ m, /* array */ jsons) { $.each (jsons, function (i, json) { place ( function (/* object */ processed_json) { return processed_json; } // -- function (/* object */ processed_json) , json , json.content , m ); // -- place (...) }); // -- $.each (jsons, function (i, json) }; // -- var addGeoJSON = function (/* L.Map */ m, /* array */ jsons) // Кластеризация: var clusterise = function (/* L.Map */ m, /* array */ points, /* int */ d, /* string */ sep, /* string */ icon) { var d2 = Math.pow (d, 2); var clustered = []; // Служебная функция, возвращающая ссылку на подъодящий кластер (null, если нет): var findCluster = /* int */ function (/* L.Point */ p, /* L.Map */ m, /* array */ clusters, /* int */ d2) { var found = null; $.each (clusters, function (index, cluster) { if (cluster) { var p2 = m.project (L.latLng (cluster.lat, cluster.lon)); if (Math.pow (p2.x - p.x, 2) + Math.pow (p2.y - p.y, 2) <= d2) { found = index; return false; // -- завершение цикла. } } // -- if (cluster) }); // -- $.each (clustered, function (j, cluster) return found; } // -- var findCluster = function (/* L.Point */ p, /* L.Map */ m, /* array */ clusters, /* int */ d2) // Служебная функция, соединяющая в кластер точку с точкой или кластер с точкой: var mergePoints = /* object */ function (/* object */ p1, /* object */ p2, /* string */ sep, /* string */ icon) { // Служебная функция, оборачивающая html в таблицу: var add2Table = function (/* string */ tbl, /* string */ html, /* string */ icon_url) { var ret = tbl || '<table></table>'; return ret.substr (0, ret.length - 8) + '<tr><th><img src="' + icon_url + '" /></th>' + '<td>' + html + '</td></tr>' + '</table>'; } // -- var add2Table = function (/* string */ tbl, /* string */ html, /* string */ icon_url) var merged; if (!p1.is_cluster) { merged = $.extend (true, {is_cluster: true}, p1); merged.content = add2Table (null, p1.content, (markers [p1.iconType] || markers ['default']).options.iconUrl); } else { merged = p1; } // Слияние всплывающих окон: // Добавление новой строки к таблице: merged.content = add2Table (merged.content, p2.content, (markers [p2.iconType] || markers ['default']).options.iconUrl); // Выбор значка: merged.clusteredIconType = p1.clusteredIconType === p2.clusteredIconType || p1.clusterRating > p2.clusterRating ? p1.clusteredIconType : p2.clusterRating > p1.clusterRating ? p2.clusteredIconType : icon; merged.iconType = merged.clusteredIconType; // Слияние текстовых меток (поглощением или сложением): merged.labelText = (p1.labelText || '').indexOf (p2.labelText || '') > -1 ? p1.labelText : (p2.labelText || '').indexOf (p1.labelText || '') > -1 ? p2.labelText : (p1.labelText || '') + sep + (p2.labelText || ''); // Слияние категорий с удалением дубликатов: merged.wlCategories = $.merge (merged.wlCategories, p2.wlCategories).filter (function (a) { return !this [a] ? this [a] = true : false; }, {}); return merged; } // -- var mergePoints = function (/* object */ p1, /* object */ p2, /* string */ sep, /* string */ icon) $.each (points, function (i, point) { var p1 = m.project (L.latLng (point.lat, point.lon)); var index = findCluster (p1, m, clustered, d2); if (index !== null) { var new_cluster = mergePoints (clustered [index], point, sep, icon); clustered.push (new_cluster); delete clustered [index]; } else { clustered.push (point); } }); // -- $.each (points, function (i, point) return clustered.filter (function (elem) { return elem; }); } // -- var clusterise = function (/* L.Map */ m, /* array */ points, /* int */ d, /* string */ sep, /* string */ icon) // Показ всего, а также вывод контроля тайлов и категорий. // Вызов событием загрузки/зума: var addAll = function (/* L.Map */ m , /* array */ points , /* array */ jsons , /* object */ cluster , /* object */ tiles , /* boolean */ redraw) { if (redraw) { // Удалить все категории: delete m.wlCategories; // Удалить все слои: m.eachLayer (function (layer) { // кроме тайлов: if (!(layer instanceof L.TileLayer)) { m.removeLayer (layer); } }); } // -- if (redraw) // Кластеризация, если нужно: var points_clustered = cluster.distance ? clusterise (m, points, cluster.distance, cluster.separator, cluster.iconType) : points; // Размещение объектов с категоризацией: addMarkers (m, points_clustered); addLabels (m, points_clustered); addGeoJSON (m, jsons); // Выбор тайлов и фильтры: if (typeof L.control.layers !== 'undefined') { if (redraw) { if (m.wlLayerControl) { m.wlLayerControl.removeFrom (m); } } m.wlLayerControl = L.control.layers (tiles, m.wlCategories).addTo (m); } }; // -- var addAll = function (...) // Клонирование тайлов для карты. Без него переключатели нескольких карт на странице будут ломать друг друга: var tiles = {}; $.each (wlTileServers, function (/* string */ name, /* object */ params) { tiles [name] = L.tileLayer (params.url, params.options); }); // Создание объекта карты и первоначальные настройки: var map = new L.Map (div, { scrollWheelZoom: false // -- зум колесом выключен. }).addControl (L.control.scale () // -- контроль зума. ).addLayer (tiles [settings.wlTiles]); // -- тайлы по умолчанию. // Добавление инструментов редактирования: if (settings.wlEditable) { addEditTools ($div, map, popupMaxWidth); } // Наследование точками характеристик значков: inheritAll (settings.wlPoint, settings.wlMarker, settings.wlFilterList); // Инициализация крайних точек: // Препроцессинг точек и GeoJSON для выявления центра и границ: var preprocessedPoints = preprocessPoints (settings.wlPoint); var preprocessedGeoJSON = preprocessGeoJSON (settings.wlGeoJSON); var bounds = null; if (settings.wlBounds.south || settings.wlBounds.west || settings.wlBounds.north || settings.wlBounds.east) { // Границы заданы явно: bounds = settings.wlBounds; } else { // Границы рассчитываются по объектам карты: bounds = union (settings.wlCenter ? { south: settings.wlCenter.lat , north: settings.wlCenter.lat , west : settings.wlCenter.lon , east : settings.wlCenter.lon } : null, preprocessedPoints.bounds); bounds = union (bounds, preprocessedGeoJSON.bounds); } // -- if (settings.wlBounds.south || settings.wlBounds.west || settings.wlBounds.north || settings.wlBounds.east) // Если нет ни центра, ни объектов, ни границ: // Ph'nglui mglw'nafh Mithgol G'lenjik wgah'nagl fhtagn: bounds = bounds || {south: 44.5445, west: 37.9718, north: 44.6054, east: 38.1223}; // Пределы в формате Leaflet: var wlBounds = L.latLngBounds (L.latLng (bounds.south, bounds.west), L.latLng (bounds.north, bounds.east)); var center = settings.wlCenter && settings.wlCenter.lat && settings.wlCenter.lon // Центр задан явно: ? {lat: settings.wlCenter.lat, lon: settings.wlCenter.lon} // Центр рассчитан по объектам карты: : {lat: (bounds.south + bounds.north) / 2, lon: (bounds.west + bounds.east) / 2}; // Начальный зум: if (settings.wlCenter && settings.wlCenter.zoom !== null) { map.setZoom (settings.wlCenter.zoom); // -- зум задан. } else { map.fitBounds (wlBounds, {padding: [20, 20]}); // -- автозум. } // Начальный центр: map.setView (L.latLng (center.lat, center.lon)); // Нужно установить границы на замке: if (settings.wlBounds.flag) { map.setMaxBounds (wlBounds); } // -- if (settings.wlBounds.flag) // Наконец, размещение объектов карты: addAll (map, preprocessedPoints.points, preprocessedGeoJSON.jsons, settings.wlCluster, tiles); if (settings.wlCluster.distance) { // Если работает кластеризация, то надо перерисовывать при зуме: map.on ('zoomend', function (/* Event */ event) { addAll (event.target, preprocessedPoints.points, preprocessedGeoJSON.jsons, settings.wlCluster, tiles, true); }); } // -- if (settings.wlCluster.distance) return map; } // -- /* L.Map */ function attachMap (/* DOM */ div, /* object */ settings). L.LabelOverlay = L.Class.extend ({/* https://github.com/CloudMade/Leaflet/issues/154 */ initialize: function (/* LatLng */ latLng, /* String */ label, options) { this._latlng = latLng; this._label = label; L.Util.setOptions (this, options); }, options: { offset: new L.Point (0, 2) }, onAdd: function (map) { this._map = map; if (!this._container) { this._initLayout (); } map.getPanes ().overlayPane.appendChild (this._container); this._container.innerHTML = this._label; map.on ('viewreset', this._reset, this); this._reset (); }, onRemove: function (map) { map.getPanes ().overlayPane.removeChild (this._container); map.off ('viewreset', this._reset, this); }, _reset: function () { var pos = this._map.latLngToLayerPoint (this._latlng); var op = new L.Point (pos.x + this.options.offset.x, pos.y - this.options.offset.y); L.DomUtil.setPosition (this._container, op); }, _initLayout: function () { this._container = L.DomUtil.create ('div', 'leafletLabel'); } }); // -- L.LabelOverlay. // Список сокращённых идентификаторов допустимых серверов тайлов: var wlTileAliases = { osm : 'OpenStreetMap.Mapnik' /* , local : mw.config.wgServer*/ , cycle : 'OpenCycleMap' , mapquest : 'MapQuest' , openaerial : 'Open Aerial' , hydda : 'Hydda.Full' /* , osmosnimki : 'OSMosnimki' , kosmosnimki : 'Космоснимки'*/ , openmapsurfer : 'OpenMapSurfer Roads' , esriwp : 'Esri.WorldPhysical' , thundertr : 'Thunderforest.Transport' /* , sea : 'OpenSeaMap'*/ }; // -- var wgTileAliases. // Список допустимых серверов тайлов: var wlTileServers = { 'OpenStreetMap.Mapnik' : { url: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' , options: { minZoom: 0, maxZoom: 18, attribution: 'Map data © <a href="//osm.org/">OpenStreetMap</a> contributors' }} // -- 'OpenStreetMap.Mapnik'. , 'MapQuest' : { url: '//otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png' , options: { minZoom: 0, maxZoom: 18, subdomains: '1234', attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="//developer.mapquest.com/content/osm/mq_logo.png"> — Map data © <a href="http://osm.org/">OpenStreetMap</a> contributors' }} // -- 'MapQuest'. , 'Open Aerial' : { url: '//otile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg' , options: { minZoom: 0, maxZoom: 11, subdomains: '1234', attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="//developer.mapquest.com/content/osm/mq_logo.png"> — Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency' }} // -- 'Open Aerial'. /* , 'OSMosnimki' : { url: '//{s}.tile.osmosnimki.ru/kosmo/{z}/{x}/{y}.png' , options: { minZoom: 0, maxZoom: 17, subdomains: 'abcd', attribution: 'Tiles Courtesy of <a href="//kosmosnimki.ru/">Kosmosnimki.Ru</a> — Map data © <a href="//osm.org/">OpenStreetMap</a> contributors' }} // -- 'OSMosnimki'. , 'Космоснимки' : { url: '//{s}.tile.kosmosnimki.ru/kosmo/{z}/{x}/{y}.jpg' , options: { minZoom: 0, maxZoom: 18, subdomains: 'abcd', attribution: 'Tiles Courtesy of <a href="//kosmosnimki.ru/">Kosmosnimki.Ru</a>' }}// -- 'Космоснимки'.*/ , 'OpenMapSurfer Roads' : { url: '//korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}' , options: { minZoom: 0, maxZoom: 18, attribution: 'Tiles by <a href="//openmapsurfer.uni-hd.de/contact.html">Department of Geography, University of Heidelberg</a> — Map data © <a href="//osm.org/">OpenStreetMap</a> contributors' }} // -- 'OpenMapSurfer Roads'. , 'Esri.WorldPhysical' : { url: '//server.arcgisonline.com/ArcGIS/rest/services/World_Physical_Map/MapServer/tile/{z}/{y}/{x}' , options: { attribution: 'Tiles © Esri — Source: US National Park Service', maxZoom: 8 }} // -- 'Esri.WorldPhysical' , 'Hydda.Full' : { url: '//{s}.tile.openstreetmap.se/hydda/full/{z}/{x}/{y}.png' , options: { minZoom: 0, maxZoom: 18, attribution: 'Tiles courtesy of <a href="//openstreetmap.se/" target="_blank">OpenStreetMap Sweden</a> — Map data {attribution.OpenStreetMap}' }} // -- 'Hydda' /* , 'OpenSeaMap' : { url: '//tiles.openseamap.org/seamark/{z}/{x}/{y}.png' , options: { attribution: 'Map data: © <a href="//www.openseamap.org">OpenSeaMap</a> contributors' }} // -- 'OpenSeaMap' */ , 'Thunderforest.Transport' : { url: '//{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png' , options: { attribution: '© <a href="//www.opencyclemap.org">OpenCycleMap</a>, © <a href="//openstreetmap.org">OpenStreetMap</a> contributors, <a href="//creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>' }} // -- 'Thunderforest.Transport' , 'OpenCycleMap' : { url: '//{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png' , options: { minZoom: 0, maxZoom: 16, attribution: 'Map data © <a href="//osm.org/">OpenStreetMap</a> contributors' }} // -- 'OpenCycleMap'. }; /* wlTileServers [mw.config.wgServer] = new L.TileLayer (mw.config.wgServer + '/tiles/{z}/{x}/{y}.png', { minZoom: 0, maxZoom: 18, attribution: 'Map data © OpenStreetMap contributors' });*/ // -- wlTileServers [mw.config.wgServer] (local). // -- var wgTileServers. // Функция, добавляющая к карте инструменты редактирования: function addEditTools (/* jQuery */ $wikileaf, /* L.Map */ map, /* int */ popupMaxWidth) { // Уникальный идентификатор карты (хэш): var id = 'result_' + $wikileaf.html ().split ('').reduce (function (a, b) {a = ((a << 5) - a) + b.charCodeAt (0); return a & a}, 0); $wikileaf.after ('<div id="' + id + '"></div>'); // Привязка события для записи координат щелчка мышью: map.on ('click', function (e) { var latlngStr = '' + e.latlng.lat.toFixed (6) + '|' + e.latlng.lng.toFixed (6); var popup = new L.Popup ({maxWidth: popupMaxWidth}); popup.setLatLng (e.latlng); popup.setContent ('<b>Координаты жмяка мышою:</b><br><code>{{wl|точка|' + latlngStr + '}}</code>'); map.openPopup (popup); }).on ('drag zoomend', function (e) { var latlngStr = '' + map.getCenter ().lat.toFixed (6) + '|' + map.getCenter ().lng.toFixed (6); $('#' + id).html ( '<b>Координаты центра карты и увеличение:</b> <code>{{wl|центр|' + latlngStr + '|' + map.getZoom() + '}}</code>' + '<br />' + '<b>Границы карты:</b> <code>{{wl|границы' + '|' + map.getBounds ().getSouth () + '|' + map.getBounds ().getWest () + '|' + map.getBounds ().getNorth () + '|' + map.getBounds ().getEast () + '}}</code>' ); }); } // -- function addEditTools (/* jQuery */ $wikileaf, /* L.map */ map, /* int */ popupMaxWidth). /* * Прочие функции */ // Отладочная функция: function showObj (obj, depth) { var out = ''; if (!depth) { depth = 2; } var level = ''; var type = $.type (obj); if (type === 'array' && obj !== null && depth > 1) { out += 'array [\n'; level = level + ' '; $.each (obj, function (key, val) { out += level + key + ' : ' + showObj (val, depth - 1) + '\n'; }); out += ']'; } else if (type === 'object' && obj !== null && depth > 1) { out += 'object (' + obj.constructor.name + ') : {\n'; level = level + ' '; $.each (obj, function (key, val) { out += level + key + ' : ' + showObj (val, depth - 1) + '\n'; }); out += '}'; } else if (type === 'object' && obj !== null && depth <= 1) { out = level + '[object]\n'; } else if (type === 'string' || type === 'number') { out = level + type + ': ' + obj; } else if (obj && typeof (obj.toString) !== 'undefined') { out = level + type + ': ' + obj.toString (); } else { out = level + 'null'; } return out; }