MediaWiki:WikiLeaflet.js
Перейти к навигации
Перейти к поиску
Замечание: Чтобы после сохранения вступили в силу изменения стилей, перезагрузите файл //traditio.wiki/w/load.php?debug=false&lang=ru&modules=site&only=styles&skin=vector&*, если используете скин Vector, или //traditio.wiki/w/load.php?debug=false&lang=ru&modules=site&only=styles&skin=common&*, если используете скин Common.
Чтобы вступили в силу изменения скриптов, перезагрузите файл //traditio.wiki/w/load.php?debug=false&lang=ru&modules=site&only=scripts&skin=vector&*, если используете скин Vector, или //traditio.wiki/w/load.php?debug=false&lang=ru&modules=site&only=scripts&skin=common&*, если используете скин Common.
Гаджеты и импортируемые скрипты загружаются отдельными файлами.
/*
* Скрипт 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;
}