Пользователь
0,2
рейтинг
26 января 2012 в 12:27

Дизайн → Leaflet — API карт от Cloudmade. Рецензия

To Mourner — бойся своих желаний, они могут исполниться. Шутка.

Начнём с начала



На главной Leaflet API нас встречает quickstart-пример. С него и начнём.

// create a CloudMade tile layer
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/YOUR-API-KEY/997/256/{z}/{x}/{y}.png',
    cloudmadeAttribution = 'Map data © 2011 OpenStreetMap contributors, Imagery © 2011 CloudMade',
    cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});

// initialize the map on the "map" div
var map = new L.Map('map');

// set the map view to a given center and zoom and add the CloudMade layer
map.setView(new L.LatLng(51.505, -0.09), 13).addLayer(cloudmade);

// create a marker in the given location and add it to the map
var marker = new L.Marker(new L.LatLng(51.5, -0.09));
map.addLayer(marker);

// attach a given HTML content to the marker and immediately open it
marker.bindPopup("A pretty CSS3 popup.<br />Easily customizable.").openPopup();


Пример начинается с создания слоя с тайлами от cloudmade. Само API вроде как тоже «by cloudmade». Внимание, вопрос: а что, для родительского/дружественного проекта нельзя сделать удобный способ добавления слоя тайлов? Типа такого:

var cloudmade = new L.CloudMade.TileLayer(YOUR-API-KEY);
?
Или даже такого:
map.addLayer('cloudmade', { apiKey: YOUR-API-KEY });


Не знаю, какие отношения связывают Leaflet и Cloudmade, но уж сделать удобно клиенту Cloudmade — точно не последняя задача Leaflet API. Заставлять пользователя самостоятельно добавлять копирайт Cloudmade — это какое-то насилие над здравым смыслом.

Чайнинг





В отличие от Google Maps API v2/OpenLayers, Leaflet представляет jQuery-подобную парадигму (большинство действий чайнятся). Ну так что же стесняться-то?

var marker,
      map = (new L.Map('map'))
        .setView(new L.LatLng(51.505, -0.09), 13)
        .addLayer('cloudmade', { apiKey: YOUR-API-KEY })
        .addLayer((marker = new L.Marker(new L.LatLng(51.5, -0.09)))
            .bindPopup("A pretty CSS3 popup.<br />Easily customizable.")
        );

marker.openPopup();


Кстати, из приведённого примера хорошо заметно неудобство смешивания объектного подхода и чайнинга — (new X()).y() не самая красивая конструкция в JS.

Кстати, почему маркеры добавляются через addLayer? L.Layer в Leaflet — вполне понятная отдельная сущность — слой. Почему через addLayer добавляются и другие сущности — маркеры, геометрии?

И потом, если разницы между addLayer и addMarker нет (т.е. вся логика добавления зашита в самом объекте), разве не логично сдублировать этот метод в сам объект?

var map = (new L.Map('map'))
        .setView(new L.LatLng(51.505, -0.09), 13)
        .addLayer('cloudmade', { apiKey: YOUR-API-KEY }),
      marker = (new L.Marker(new L.LatLng(51.5, -0.09)))
        .appendTo(map)
        .bindPopup("A pretty CSS3 popup.<br />Easily customizable.")
        .openPopup();


Теперь, если заменить констукторы фабриками, получится совсем красиво:

var map = L.map('map')
             .setView(L.latLng(51.505, -0.09), 13))
             .addLayer('cloudmade', { apiKey: YOUR-API-KEY }),

      marker = L.marker(L.latLng(51.5, -0.09))
            .appendTo(map)
            .bindPopup("A pretty CSS3 popup.<br />Easily customizable.")
            .openPopup();


Сравните с исходным примером.

Кстати, забегая вперёд, setView и addLayers можно делать прямо в конструкторе — почему бы не вынести эту возможность прямо в quickstart?

Поковырямся немытыми руками



ОК, убедили, я хочу попользоваться вашим АПИ. Смотрю в код и… недоумеваю. Как его подключить-то? Ладно, я понимаю — зачем перегружать пример всякими html-тэгами и т.п., но как подключать АПИ надо же показать. Поставили галочку, идём в пример.

Before writing any code for the map, you need to do the following preparation steps on your page:
Include Leaflet CSS files in the head section of your document:
<link rel="stylesheet" href="leaflet/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="leaflet/leaflet.ie.css" /><![endif]-->

Include Leaflet JavaScript file somewhere on the page (preferably before body close tag):
<script src="leaflet/leaflet.js"></script>

Put a div element with a certain id where you want your map to be and make sure it has defined width and height:
<div id="map" style="height: 200px"></div> <!-- width equals available horizontal space by default -->

Now you're ready to initialize the map and do some stuff with it.


До этого момента (я уже прочел доку, прежде чем лезть в примеры), Leaflet оставлял о себе исключительно приятное впечатление. Но вот этот небольшой кусок примера вызывает больше вопросов, чем вся документация вместе взятая.

Во-первых, откуда ж на моей странице возьмётся папка leaflet с кодом leaflet-а? Кажется, что вот как раз получение кода библиотеки и нужно описывать в разделе «Preparing your page». Если вы думаете, что вебмастера легко заметят, что ссылки никуда не ведут, залезут на гитхаб и скачают код — вы очень глубоко ошибаетесь. Я уверен, что ваш саппорт завален вопросами «я скопировал код — ниче не работает — что делать???»

Далее, зачем заставлять вебмастера самого размещать код подключения css? Почему не оставить эту работу js-скрипту? (Кстати, graceful degradation под IE — моё почтение.)

А вот этот коммент «width equals available horizontal space by default» — на кого он рассчитан? На тех, кто не имеет никакого представления об HTML? Ну так этот коммент их только запутает. Им бы вот как раз пояснить, (а) откуда на их странице возьмётся leaflet/leaflet.js и как с гитхаба код качать, (б) что это за магические комментарии в подключении CSS и почему их нельзя просто так удалять.

Make sure this code is below both the map div and leaflet.js inclusion, or in a window.load or document.ready event handler.


Вы поясняете вебмастерам, что дивы тянутся по умолчанию на 100%, но при этом не считаете нужным сообщить, что это за зверь window.load и как в его обработчик добавить какой-то код?

Поймите, веб-мастера от вас хотят ровно одного: скопировать кусок кода, и чтобы он работал. Работал везде — хоть в head его ставь, хоть в body, хоть до window.onload, хоть после. И как бы я их вполне понимаю — если мне вдруг надо использовать стороннюю библиотеку, то последнее, чем я хочу заниматься — это ковырять референсный пример, выясняя, почему он не работает в моём окружении.

Ладно, я отвлёкся. Смотрим в firebug. Ребята, да вы читеры! JS API в 25 Кб — это колдовство какое-то. Тут — твёрдая и безоговорчная пятёрка. Кстати, у вас функция _leaflet_resize3 светится в глобальный неймспейс (v0.3), где-то var забыли.

Удивляет другое — почему не хостите эту библиотеку сами и заставляете подключать с домена пользователя? Не ахти ж какая нагрузка, да и договориться с партнером каким-нибудь можно. Зато у ваших пользователей не будет проблем с обновлением версий (и с критическими багами в старых версиях, которые рано или поздно появятся) + с распространением библиотеки она очень скоро окажется у большинства пользователей в кэше.

Десерт



Ладно, заканчиваем с примерами, переходим ко вкусному — к документации. И сразу вопрос — почему некоторые ссылки серые и никуда не ведут? Документация не готова? Сломалось что-то? Roadmap? Кстати, а к какой версии эта документация — к стабильной 0.2 или dev 0.3?

Начинаем читать.

// initialize the map on the "map" div with a given center and zoom 
var map = new L.Map('map', {
    center: new L.LatLng(51.505, -0.09), 
    zoom: 13
});

// create a CloudMade tile layer
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/YOUR-API-KEY/997/256/{z}/{x}/{y}.png',
    cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});

// add the CloudMade layer to the map
map.addLayer(cloudmade);


Оказывается, центр и зум карте можно задать в конструкторе. А можно не задавать. А что будет, если только центр задать? Или только зум? А главное, кому нужна неинициализированная карта? Какая у неё есть функциональность?

Опять создание cloudmade-овского слоя вынесено в самое начало — да прикрутите уже нормальный способ это сделать! Кстати, теперь копирайты уже можно не указывать?

Начинаем смотреть в параметры карты.
layers ILayer[] [] Layers that will be added to the map initially.

Оказывается, слои можно задать прямо в конструкторе. Так а что не задаёте-то в своих примерах?
minZoom Number 0 Minimum zoom level of the map. Overrides any minZoom set on map layers.
maxZoom Number 18 Maximum zoom level of the map. This overrides any maxZoom set on map layers.

Гм. Если опции карты всегда перекрывают опции слоя и у них есть дефолтное значение — зачем тогда нужны опции слоя? Непонятно…
dragging Boolean true Whether the map be draggable with mouse/touch or not.
touchZoom Boolean true Whether the map can be zoomed by touch-dragging with two fingers.
scrollWheelZoom Boolean true Whether the map can be zoomed by using the mouse wheel.
doubleClickZoom Boolean true Whether the map can be zoomed in by double clicking on it.

Три опции из четырех — инфинитивы, одна — герундий. Тогда уж или drag, или (touch|scrollWheel|doubleClick)Zooming.

Более интересно другое — почему ILayer-ы удостоены отдельной опции-массива layers, а IHandler-ы и IControl-ы — нет. Разве так не логичнее?

handlers IHandler[] ['drag', 'touchZoom', 'scrollWheel', 'doucleClick'] Map handlers that will be enabled initially


Ну и для контролов аналогично.

То же соображение касается и свойств карты. Почему бы не писать просто:
map.hadlers('drag').enable()


Такое решение (а) разгрузит интерфейс класса от множества лишних опций и свойств, (б) сделает добавление новых контролов и хэндлеров более формальным и удобным.

Допустим, я хочу добавить линейку на свою карту и хочу оформить её правильно в виде IHandler. Мне придётся отнаследоваться от L.Map и сделать примерно следующее:

var MyMap = function (id, options) {
    L.Map.call(this, id, options);
    this.ruler = new MyRulerHandlerClass(map);
    if (options && options.ruler) {
        this.ruler.enable();
    }
}


Хотя мне всего-то достаточно сделать что-то типа
L.handlers.register('ruler', MyRulerHandlerClass)

если завести статическое хранилище IHandler-ов карты по алиасам.

Перейдём к событиям.

click MouseEvent Fired when the user clicks (or taps) the map.
dblclick MouseEvent Fired when the user double-clicks (or double-taps) the map.
mousedown MouseEvent Fired when the user pushes the mouse button on the map.

А mouseup, contextmenu, mouseenter, mouseleave? Уж как-то совсем странно предоставлять событие mousedown и не давать слушать mouseup.

load Event Fired when the map is initialized (when its center and zoom are set for the first time).
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.


Уберите неинициализированное состояние карты, и в этих двух событиях отпадёт нужда. Кстати, событие load, говорящее о том, что загрузились все тайлы, было бы куда как полезнее.

movestart Event Fired when the view of the map starts changing (e.g. user starts dragging the map).
move Event Fired on any movement of the map view.
moveend Event Fired when the view of the map ends changed (e.g. user stopped dragging the map).
dragstart Event Fired when the user starts dragging the map.
drag Event Fired repeatedly while the user drags the map.
dragend Event Fired when the user stops dragging the map.
zoomend Event Fired when the map zoom changes.


Чем вызвана необходимость иметь два набора move-событый — move* и drag*? Чтобы отличать пользовательское действие от программного? Так почему в zoom так же не сделано? Почему нет zoomstart, zoom, scrollzoomstart, scrollzoom, scrollzoomend? Непонятно.

layeradd LayerEvent Fired when a new layer is added to the map.
layerremove LayerEvent Fired when some layer is removed from the map.


Я так понимаю, эти же события бросаются при добавлении маркеров? Как я об этом должен узнать? Понятие layer ещё не введено, слово даже шрифтом не выделено как программная сущность, ссылки нет.

locationfound LocationEvent Fired when geolocation (using locate or locateAndSetView method) went successfully.
locationerror ErrorEvent Fired when geolocation (using locate or locateAndSetView method) failed.


А где мне увидеть эти магические методы, о которых идёт речь? После событий в описании какие-то projections идут, ссылки нет. Кстати, что делают projections в описании интерфейса map — мне совсем непонятно.

Map panes

An object literal that contains different map panes that you can use to put your custom overlays in. The difference is mostly in zIndex order that such overlays get.


Круто. Что это? Как получить к этому доступ? Это не поле, не метод и не событие — что это за зверь-то такой? Примера нет.

Переходим к методам. Разбиение методов на Methods that modify map state / Methods that get map state / Methods for layers and controls / Conversion methods / Other methods («все животные делятся на а) принадлежащих Императору, б) набальзамированных, в) прирученных, г) молочных поросят, д) сирен, е) сказочных, ж) бродячих собак, з) включённых в эту классификацию, и) бегающих как сумасшедшие, к) неисчисляемых, л) нарисованных тончайшей кистью из верблюжьей шерсти, м) и прочих, н) только что разбивших кувшин, о) похожих издали на мух...») как бы намекает на основную проблему с методами: их слишком много. И, главное, дальше будет ещё больше. Если выносить «sometimes useful» методы в интерфейс основного класса — очень скоро они станут totally unuseful из-на невозможности что-то найти. Надо что-то делать :) Например, если метод является прокси к какому-то внутреннему объекту — то не стоит ли открыть этот объект вместо проксирования его методов? Ну и многие методы просто избыточны — например, зачем нужны отдельные методы locate и locateAndSetView, если можно просто обойтись флагом setView в опциях метода locate? Зачем нужны методы zoomIn/zoomOut, когда есть setZoom?

В методах (add|remove)Layer опять нет ни единого намёка на то, что такое layer. Любопытно, что есть методы (add|remove)Control, но нет (add|remove)Handler. А что произойдёт, если я добавлю новый контрол — поле .controlName в карте появится? Судя по всему — нет.

Итого, у вас аж 4 неконсистетных способа добавлять/удалять/обращаться к сущностям карты:

1) для слоёв: массив layers в опциях, (add|remove)Layer в методах;
2) для IHandler: набор именованных опций, набор именованных свойств, как добавлять новые — неясно вообще;
3) для IControl: и набор именованных опций/свойств, и интерфейс (add|remove)Control для неименованных контролов;
4) для MapPanes — метод карты, который возвращает литерал со списком pane-ов.

Многовато будет, и документация хромает.

Маркеры



Интерфейс маркера бедноват в сравнении с буйством карты, но умудряется наследовать её родовую травму в виде .dragging. Невозможность перезадать иконку не радует, но ещё больше не радует отсутствие нативной возможности связать с маркером какие-то данные. Маркер соответствует какой-то географической сущности, и, скорее всего, у него есть какой-то идентификатор, имя, описание, адрес и пр. Вы заставляете пользователя либо наследоваться и расширять класс, либо хитрить с замыканиями, либо плевать на всё и просто писать marker.id = 'myid'. Все варианты не очень красивы и потенциально опасны.

clickable Boolean true If false, the marker will not emit mouse events and will act as a part of the underlying map.


Неправильное решение — назвать интерактивность объекта «clickable».

А что мне делать, если я по клику хочу решить — пропускать это событие или нет? Доступа к дом-ноде нет.

Как мне поступить, если я хочу, чтобы по клику открывался балун, а по драгу таскалась карта? Вполне обычный юзкейс, см., например, остановки общественного транспорта на гугломапсах.

draggable Boolean false Whether the marker is draggable with mouse/touch or not.


Я так понимаю, поле .dragging у маркера будет в любом случае, независимо от опции draggable? И что будет, если я принудительно вызову .dragging.enable()? Почему у карты эта опция называется dragging? Ведь это же точно тот же IHandler для таскания, что и у карты — почему у него другие опции?

Куда пропало событие mousedown?

Кстати, про то, что маркер — это layer я так и не узнал. Только примеры по-прежнему намекают мне на эту странную идентичность. В документации ещё используется термин «overlay», который тоже нигде не определен плюс ещё и в именах сущностей не встречается.

Popup



Первый вопрос, который возникает — если есть сущность L.Popup, то могу ли я передать инстанцию класса L.Popup в bindPopup? По документации выходит, что нет. А почему тогда метод называется bindPopup, если он привязывает никакой не popup, а только данные и опции для него?

autoPan Boolean true Set it to false if you don't want the map to do panning animation to fit the opened popup.'
closeButton Boolean true Controls the presense of a close button in the popup.

Разнородные названия опций. Если autoPan = автоматически передвинь, то closeButton = закрой кнопку, а вовсе не «наличие кнопки закрытия».

Кстати, в чём смысл задания html-контента, если к DOM-у доступа нет? Раз уж я хочу задавать rich html content балуну, то я точно хочу и интерактива добавить — а у меня нет ни доступа к html, ни события «попап открылся».

Настроек интерактивности попапа нет — а зря, здесть они нужнее, чем в маркере. Например, скролл всегда безусловно проходит на карту — значит, разместить в попапе проскроленный контент невозможно.

Слои



Про печальку с cloudmade-овскими слоями я уже упоминал. Почему-то для других сервисов (WMS) отдельный класс для удобства заведён, а для родного — нет.

Что такое minZoom и maxZoom и как их оверрайдят настройки карты — не разъяснено. Также непонятно, зачем введена настройка «размер тайла». Типичные юзкейсы — наложить слой в другой проекции (wgs84, например), растянуть тайлы последнего масштаба и добавить +1 или +2 масштаба карте — эта настройка не решает.

Про полезность события «все тайлы загрузились» я уже упоминал.

Что такое TileLayer.Canvas я просто не понял. Документация описывает у него ровно один метод, и пример никакого представления о том, зачем он нужен, не даёт.

Графика



Графические элементы наследуют ту же печать с clickable, что и маркеры. Кстати, а почему draggable к ним не прикручен? Число бросаемых объектом событий по мере углубления в документацию всё сокращается :)

Метод setStyle задающий options — это странно.

Геометрии задаются массивами точек — как-то непоследовательно. Саму точку нужно обязательно задавать через new LatLng, а в полилинию можно засовывать геометрию обычным массивом, а не спец. объектом. А что будет, если пользователь сделает этому массиву splice в обход API-шного метода? Метода update у графики нет.

Опция noClip с комментарием «Disabled polyline clipping.» поставила меня в тупик. Особенно отсутствием такой же опции у остальных геометрий.

Что я знаю о кругах?



Круг, нарисованный через linecap на линии — это прикольная идея. Проблема только в том, что «честный» круг радиусом в n метров в меркаторовской проекции — вовсем не круг и даже не эллипс, а сложная фигура. Ладно, положим «честный» круг никому не нужен. Но всё же: какой радиус выбирается для расчетов? На север, на юг, на запад, на восток?

Кстати, позицию центра круга изменить можно, а радиус — нельзя. Почему?

L.CircleMarker
A circle of a fixed size with radius specified in pixels. Extends Circe (здесь опечатка — forgotten). Use Map#addLayer to add it to the map.


В упор не вижу отличий от просто Circle. По названию «CircleMarker» я бы подумал, что это круг + маркер, но описание никаких наводок не даёт.

The default radius is 10 and can be altered by passing a «radius» member in the path options object.


Зачем это сделано? Кому мешал радиус в сигнатуре? Или это такой способ сделать доступным изменение радиуса? Зачем тогда нужен просто Circle? По-хорошему, надо не Circle -> CircleMarker расширять, а Options -> CircleOptions, раз в опциях появляется дополнительное поле.

Группы



Группа LayerGroup, в которую можно класть не только слои, но и маркеры/графику — это очень странно. Ссылка ILayer всё ещё никуда не ведёт. Метод clearLayers, который удаляет всех детей группы (а не очищает тайловые слои, как лично я бы подумал из названия) — тоже очень странный.

Но группа FeatureGroup, которая к LayerGroup добавляет пропагацию событий и попап — это уже прямо совсем странно. Что должно кому сказать название Feature? Почему этот функционал нельзя прибить к базовой группе и не делать разделения Layer/Feature?

GeoJSON



Во-первых, само решение «накопительного» GeoJSON-а выглядит как-то странно. Или два раза addGeoJSON нельзя вызвать? Документация ответа не даёт.

var geojson = new L.GeoJSON();
geojson.on('featureparse', function(e) {
    // do something with e.layer depending on e.properties
});
geojson.addGeoJSON(geojsonObj);
map.addLayer(geojson);


Самое-то интересное и скрыто за «do something». А что сделать-то можно? Группа не даёт аксессоров до дочерних объектов. Если я добавил FeatureCollection через geojson — что я сделать-то с ней могу? Или FeatureCollection нельзя добавлять, затем и накопительный addGeoJSON?

И, кстати, почему JSON везде большими, а в geojson — маленькими? И ещё у вас опечатка: в coordsToLatlng и coordsToLatlngs должны быть «Lng» с большой.

В итоге



Что мы имеем в итоге?

Недостатки функционала, положим, можно легко списать на малый размер библиотеки (25Кб — рекорд). (Только вот что тут экономить, если библиотека весит меньше, чем один стандартный тайл?)

Неконсистентные интерфейсы добавления разных сущностей на карту — полагаю, болезни роста. Я думаю, всё-таки к релизу их нужно свести в единый интерфейс.

Графически и эстетически библиотека (и клаудмэйдовская подложка) оставляет очень приятное впечатление, и за это ей многое простится. Ну а неконсистентности и нелогичности автор, надеюсь, доработает напильником, благо версия пока всего лишь 0.3, можно себе позволить отрывать обратную совместимость.
@forgotten
карма
322,0
рейтинг 0,2
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Дизайн

Комментарии (34)

  • 0
    Недавно как раз смотрел в сторону cloudmade, но скудные маркеры меня не устроили.
    Не подскажете ли API карт, где маркеры хорошо реализованны? Мне для инфографики — кружки, текстовые метки, heatmap…
    • 0
      А чем большой Гу не устраивает?
      • 0
        Вы про гугл мапс? Так там же тоже маркеры однообразны, как, например, сделал текстовую метку вместо изображения маркера или круг со словом внутри?
        • 0
          Хм. И правда. А я почему-то был уверен, что там можно.
          Тогда вам к Яндексу.
        • 0
          использовать свою картинку?
          • 0
            Нет, не походит. Ниже написал что нашел polymaps — меня устраивает.
    • 0
      Ну, кастомные маркеры у них таки есть leaflet.cloudmade.com/examples/custom-icons.html
      или нужно именно текстовые?
      Как вариант генерировать картинку для маркера динамически
      • 0
        Я так понимаю, речь идёт о том, чтобы вместо картинки-иконки показывать свою кастомную разметку.
    • +1
      Могу посоветовать TileMill. Поддерживает SVG марекры и Google Charts.
      • +1
        Нашел только что то, что нужно.
        polymaps.org/

        Но на TileMill тоже посмотрю, спасибо.
  • 0
    Openlayers?
    • +1
      Ох не представляю, что может написать forgotten в обзоре OpenLayers… :) Наверное, целую многотомную книгу с повышенным содержанием матов и WTF. :)
  • +5
    Спасибо большое за детальную критику, это невероятно полезно для проекта!
    Комментировать придётся долго… Ну что ж, приступим. :)

    «Пример начинается с создания слоя с тайлами от cloudmade. Само API вроде как тоже «by cloudmade». Внимание, вопрос: а что, для родительского/дружественного проекта нельзя сделать удобный способ добавления слоя тайлов?»

    Идея была в том, чтобы создать абсолютно провайдеро-независимую опенсорс-библиотеку, подчеркивая то, что она не навязывает использование сервисов конкретных компаний и не содержит провайдер-ориентированного кода (это оставим плагинам и т.д.), и использовать можно любые тайлы, поэтому пример сразу такой, чтобы человек мог за две минуты поменять на тайлы, которые ему нравятся, например, от MapQuest или MapBox.

    Согласен, что нужно это на сайте подчеркнуть, чтобы было понятнее — даже багрепорт такой есть в GitHub issues. :)

    «Кстати, из приведённого примера хорошо заметно неудобство смешивания объектного подхода и чайнинга — (new X()).y() не самая красивая конструкция в JS.»

    Библиотека не навязывает использование чейнинга. Главное, что есть выбор — можно так, можно эдак, это как человеку удобнее. Я лично предпочитаю, например, сначала создать объект и присвоить переменной, а потом уже с отдельной строки с ней проводить операции. По этой же причине в примерах используются разные подходы — чтобы подчеркнуть наличие выбора. Хотя, наверное, это тоже нуждается в дополнительном уточнении на сайте.

    «Кстати, почему маркеры добавляются через addLayer? L.Layer в Leaflet — вполне понятная отдельная сущность — слой. Почему через addLayer добавляются и другие сущности — маркеры, геометрии?»

    Потому что слой в Leaflet — это всё, что можно привязать к определённому географическому положению (или положениям) на карте, будь то слой тайлов, маркер или даже попап. Не вижу смысла разделять сущности типа карты и тайлов для самой карты — пускай этой не особо существенной разницей занимаются соответствующие реализации слоёв, а карта пускай всего поменьше знает — это loose coupling.

    «Во-первых, откуда ж на моей странице возьмётся папка leaflet с кодом leaflet-а? Кажется, что вот как раз получение кода библиотеки и нужно описывать в разделе «Preparing your page».»

    Подразумевалось, что люди, которые этого не понимают, будут скачивать библиотеку с leaflet.cloudmade.com/download.html, а там это написано. :) Хотя вы правы, нужно чётче это прописать в примере и оставить ссылку на эту страницу.

    «Далее, зачем заставлять вебмастера самого размещать код подключения css? Почему не оставить эту работу js-скрипту?»

    Раньше я так и делал, но в этом подходе обнаружилось очень много проблем. Во-первых появление нестилизованной карты перед тем, как скрипт догрузит стили. Во-вторых, что более серьёзная проблема, для корректной инициализации и работы карты многие элементы должны быть стилизованными, но мало того, что отслеживание onload динамически загруженного стиля нетривиальная задача, так еще и код для карты может исполняться до того, как стиль загрузился, и придумывать для этого хорошее решение намного больше мороки, чем просто попросить человека добавить дополнительно стиль на странице. Последний подход намного надёжнее и работает лучше.

    «Удивляет другое — почему не хостите эту библиотеку сами и заставляете подключать с домена пользователя? Не ахти ж какая нагрузка, да и договориться с партнером каким-нибудь можно. Зато у ваших пользователей не будет проблем с обновлением версий (и с критическими багами в старых версиях, которые рано или поздно появятся) + с распространением библиотеки она очень скоро окажется у большинства пользователей в кэше.»

    Тоже применяли такой подход раньше, но на практике выяснилось, что это ОЧЕНЬ плохая затея (по крайней мере URL типа "[...]/somelib-latest.js". Представьте, что у вас сайт с тысячами пользователей в день, и всё у вас прекрасно работает, ничего не трогаете, вы уезжаете в отпуск, возвращаетесь и вдруг обнаруживаете, что последние две недели нифига не работало из-за мелкой специфичной регрессии в последней версии, которая проявляется только в вашем конкретном случае и поэтому не была замечена разработчиками библиотеки (ВСЕ баги всё равно отловить невозможно, что-нибудь да вылезает время от времени). А вот будь у вас урл на конкретную версию, тогда всё бы работало, а обновляясь вручную, вы имели бы возможность всё проверить перед деплойментом на продакшн-сервер. Именно по этой причине практически ни один популярный фреймворк не представляет подобной возможности и всё по версиям тоже.

    Вот сделать CDN с урлами на конкретные версии хочется, но пока не добрались до этого.

    Дальше отвечу в следующем комментарии. :)
    • 0
      > Идея была в том, чтобы создать абсолютно провайдеро-независимую опенсорс-библиотеку, подчеркивая то, что она не навязывает использование сервисов конкретных компаний и не содержит провайдер-ориентированного кода (это оставим плагинам и т.д.), и использовать можно любые тайлы, поэтому пример сразу такой, чтобы человек мог за две минуты поменять на тайлы, которые ему нравятся, например, от MapQuest или MapBox.

      Идеи-идеями, а в первую очередь нужно сделать удобно пользователю. Лучше добавить n удобных шорткатов для разных провайдеров, чем заставлять писать такие мозгоразрывающие конструкции.

      > Библиотека не навязывает использование чейнинга. Главное, что есть выбор — можно так, можно эдак, это как человеку удобнее.

      Мысль в том, что фабрики вместо (или вместе с) конструкторов выглядят гораздо красивее с чайнингом. Принципиально настаивать на обязательном использовании слова new несмотря на неудобства — это уже из области религии.

      > Потому что слой в Leaflet — это всё, что можно привязать к определённому географическому положению (или положениям) на карте, будь то слой тайлов, маркер или даже попап.

      Тогда тайловые слои надо переименовать. Но вообще идти вразрез со всеми остальными аналогичными продуктами — плохая идея. Это как на иконке «Сохранить» не рисовать дискетку.

      > Во-вторых, что более серьёзная проблема, для корректной инициализации и работы карты многие элементы должны быть стилизованными, но мало того, что отслеживание onload динамически загруженного стиля нетривиальная задача…

      Можно грузить css-код строкой и создавать тэг link динамически.

      > Тоже применяли такой подход раньше, но на практике выяснилось, что это ОЧЕНЬ плохая затея (по крайней мере URL типа "[...]/somelib-latest.js". Представьте, что у вас сайт с тысячами пользователей в день…

      Посмотрите, как решена эта проблема в Гугле.
      Есть урл на последнюю _стабильную_ версию, который рекомендован всем. Для разработки есть урл на последнюю выпущенную сборку + есть урлы на каждый конкретный релиз.
      • 0
        Идеи-идеями, а в первую очередь нужно сделать удобно пользователю. Лучше добавить n удобных шорткатов для разных провайдеров, чем заставлять писать такие мозгоразрывающие конструкции.

        По-моему конструкции элементарные, абсолютно ничего сложного в них не вижу, и разве сложно написать один раз за весь проект пару строчек? Удобные шорткаты для провайдеров я лучше вынесу в плагины. Идея, о которой я говорил, достаточно принципиальна.

        Мысль в том, что фабрики вместо (или вместе с) конструкторов выглядят гораздо красивее с чайнингом. Принципиально настаивать на обязательном использовании слова new несмотря на неудобства — это уже из области религии.

        То, что фабрики выглядят красивее — это тоже из области религии.

        Тогда тайловые слои надо переименовать.

        Зачем? Я так и не понял, чем конкретно такое обобщение осложняет пользование библиотекой.

        Посмотрите, как решена эта проблема в Гугле.
        Есть урл на последнюю _стабильную_ версию, который рекомендован всем. Для разработки есть урл на последнюю выпущенную сборку + есть урлы на каждый конкретный релиз.

        «Стабильность» версии не гарантирует отсутствия регрессий. И если уже сталкиваться с ними, то лучше при разработке, чем вдруг внезапно обнаружить у себя на продакшне.

        Мне очень не нравится, что так сделано у гугла. И делает он так совсем не для того, чтобы у всех пользователей всё было стабильно и круто. А по одной причине — для лучшего контроля над пользователями библиотеки, например, втулить насильно рекламу, если им вдруг однажды взбредёт такое в голову.
        • 0
          По идеологическим вопросам спорить смысла не вижу :)
  • 0
    И сразу вопрос — почему некоторые ссылки серые и никуда не ведут? Документация не готова? Сломалось что-то? Roadmap? Кстати, а к какой версии эта документация — к стабильной 0.2 или dev 0.3?

    Всё, что серое, не успел написать еще — наметил то, что будет в скором времени. :) Насчёт путаницы с версиями согласен, всё уточним и исправим.

    Гм. Если опции карты всегда перекрывают опции слоя и у них есть дефолтное значение — зачем тогда нужны опции слоя? Непонятно…

    Если жёстко не ограничить зум карты вручную, ограничения высчитываются автоматически в зависимости от того, какие слои на карте. Надо уточить там формулировку, да.

    Более интересно другое — почему ILayer-ы удостоены отдельной опции-массива layers, а IHandler-ы и IControl-ы — нет. Разве так не логичнее?


    Потому что layers по умолчанию пустой массив, его просто задавать сразу массивом, а в случае с контролами и хендлерами всё сложнее — есть определённый дефолтный набор для карты, который может еще и варьироваться в зависимости от браузера (напр., мобильный или десктопный). Поэтому их контролировать проще индивидуально, чем выяснять, какие должны быть в каких случаях, составлять массив в нём уже что-то добавлять/убирать.

    А mouseup, contextmenu, mouseenter, mouseleave? Уж как-то совсем странно предоставлять событие mousedown и не давать слушать mouseup.

    Всё есть, просто документацию доработать нужно. :)

    Уберите неинициализированное состояние карты, и в этих двух событиях отпадёт нужда.

    Изначально его не было, но со временем решил добавить. Бывают случаи, когда нужно инициализировать карту сначала частично (например, задать только зум и слои), а потом уже по какому-то событию доинициализировать. Чтобы убрать состояние инициализированности, нужно либо навязывать человеку инициализировать всё сразу в конструкторе и никак иначе (что неудобно), либо задавать дефолтные значения для центра и зума, что в примере выше приведёт к тому, что будет загружен сначала один набор тайлов, а потом другой. Событие load ничему не мешает, о нём в общем случае можно и не знать вообще, и использовать только в некоторых случаях, поэтому убирать нет причин.

    А вот насчёт viewreset — не понял, как это отпадёт нужда? Это самый важный ивент для реализации всех слоёв карты. Он говорит слоям, когда нужно перепроецировать координаты.

    Ну и многие методы просто избыточны — например, зачем нужны отдельные методы locate и locateAndSetView, если можно просто обойтись флагом setView в опциях метода locate?

    Забыл удалить из документации. :)

    Зачем нужны методы zoomIn/zoomOut, когда есть setZoom?

    Для удобства. Чтобы не писать
    map.setZoom(map.getZoom() + 1)
    . Но в принципе не критично, может быть, и стоит убрать.

    Вообще насчёт большого кол-ва и некоторой неконсистентности методов Map согласен, это я обязательно приведу в порядок вместе с документацией.

    Невозможность перезадать иконку не радует

    marker.setIcon(icon);
    — опять же, пробел документации.

    отсутствие нативной возможности связать с маркером какие-то данные

    Идентификатор любого объекта в Leaflet можно получить так:
    var id = L.Util.stamp(marker);
    . А где хранить какие данные — мне кажется, это лучше оставлять на совесть разработчикам — как кому удобнее. Предлагаете сделать что-то типа jQuery
    .data
    ?

    Почему-то для других сервисов (WMS) отдельный класс для удобства заведён, а для родного — нет.

    Потому что WMS — это открытый стандарт интерфейса сервиса, а конкретный сервис может быть какой угодно. То же самое с CloudMade — общий для подобных слоёв класс TileLayer заведён, а для конкретного сервиса будь добр, задай его опции. По-моему, очень логично.

    Также непонятно, зачем введена настройка «размер тайла».

    Не все тайл-сервисы предоставляют тайлы 256х256, бывают и другие размеры, скажем, 64х64 или 1024х1024 (например kothic.org/js/).

    Опция noClip с комментарием «Disabled polyline clipping.» поставила меня в тупик. Особенно отсутствием такой же опции у остальных геометрий.


    Клиппинг имеет смысл только для полилайнов и полигонов, а полигон наследует опции полилайна. Или вам непонятно, что такое клиппинг?

    В упор не вижу отличий от просто Circle. По названию «CircleMarker» я бы подумал, что это круг + маркер, но описание никаких наводок не даёт.

    Круг фиксированного размера в пикселях — это означает, что он будет радиусом в, скажем, 10 пикселей независимо от масштаба карты. Я думал, это очевидно…

    Зачем это сделано? Кому мешал радиус в сигнатуре? Или это такой способ сделать доступным изменение радиуса?

    CircleMarker применяется в тех же случаях, что и обычный маркер, т.е. не для обозначения определённого географического круга, а как круглый маркер, которому в отличие от обычного можно задать любой цвет, радиус и т.д. и менять всё это динамически. Поэтому он так и называется. Радиус можно задать, еще не добавил в документацию.

    Группа LayerGroup, в которую можно класть не только слои, но и маркеры/графику — это очень странно. Ссылка ILayer всё ещё никуда не ведёт. Метод clearLayers, который удаляет всех детей группы (а не очищает тайловые слои, как лично я бы подумал из названия) — тоже очень странный.


    Ну это, опять же, от непонимания концепции слоёв. :) Это не странно, а удобно и даёт много разных возможностей. Зачем делать для этого разные классы, если они будут одинаковыми и делать одно и то же?

    Но группа FeatureGroup, которая к LayerGroup добавляет пропагацию событий и попап — это уже прямо совсем странно. Что должно кому сказать название Feature?

    Feature — это всё, что на карте имеет смысл делать интерактивным. :) Да, объяснить в документации нужно.

    Самое-то интересное и скрыто за «do something». А что сделать-то можно? Группа не даёт аксессоров до дочерних объектов. Если я добавил FeatureCollection через geojson — что я сделать-то с ней могу? Или FeatureCollection нельзя добавлять, затем и накопительный addGeoJSON?


    Всё можно сделать. :) А у FeatureGroup есть еще setStyle.

    Недостатки функционала, положим, можно легко списать на малый размер библиотеки (25Кб — рекорд). (Только вот что тут экономить, если библиотека весит меньше, чем один стандартный тайл?)

    Делать не только в весе, но и в количестве кода, на которое браузер должен потратить и процессорное время, и память. Это очень критично в мобильных браузерах.

    А насчёт недостатка в функционале — в ней в общем-то не так много всего осталось реализовать. Не хочется превращать библиотеку в здоровенного неповоротливого монстра, подобного OpenLayers, в которой 95% функционала используется 5% пользователей, если не хуже.

    Ну а неконсистентности и нелогичности автор, надеюсь, доработает напильником, благо версия пока всего лишь 0.3, можно себе позволить отрывать обратную совместимость.

    Конечно доработаю. :) И особенно документацию, а то, как вижу, слишком мало ей уделял внимания в последнее время, нужно исправляться…

    В общем спасибо большое, тонны полезных комментариев, с множеством всего согласен, буду исправлять и дорабатывать!
    • 0
      > Поэтому их контролировать проще индивидуально, чем выяснять, какие должны быть в каких случаях, составлять массив в нём уже что-то добавлять/убирать.

      Ох я че-то в этом сомневаюсь. Попробуйте пописать код, который индивидуально (в зависимости от браузера) включает/выключает хэндлеры и контролы. Быстро поймёте, что массивом куда проще.

      > Бывают случаи, когда нужно инициализировать карту сначала частично (например, задать только зум и слои), а потом уже по какому-то событию доинициализировать.

      Не представляю себе таких случаев. Всегда можно дождаться готовности всех данных и потом инициализировать карту. Особенно если locate сделать статическим методом.

      > Или вам непонятно, что такое клиппинг?

      Мне-то понятно, да только я не очень репрезентативен.

      > Потому что WMS — это открытый стандарт интерфейса сервиса, а конкретный сервис может быть какой угодно. То же самое с CloudMade — общий для подобных слоёв класс TileLayer заведён, а для конкретного сервиса будь добр, задай его опции. По-моему, очень логично.

      Размен удобства пользователя на идеологическую чистоту, по моему скромному мнению, абсолютно не оправдан в 99% случаев.

      > Круг фиксированного размера в пикселях — это означает, что он будет радиусом в, скажем, 10 пикселей независимо от масштаба карты. Я думал, это очевидно…

      Вам очевидно, что Circle — круг в метрах, MarkerCircle — в пикселах? И это отличие оправдывает разные сигнатуры? И круг в пикселах логично отнаследован от круга в метрах?

      > Ну это, опять же, от непонимания концепции слоёв.

      Поверьте, я отлично понимаю Вашу концепцию. Но если Вы идёте поперёк течения да ещё и придаёте слову layer несвойственное ему значение — Вас неизбежно ждут проблемы.

      > Feature — это всё, что на карте имеет смысл делать интерактивным.

      Ну, тем самым вы закрываете себе возможность сделать слои интерактивными (ну, точнее, попадёте в неприятное с точки зрения интерфейсов положение, когда начнёте впиливать интерактивные слои). Я не рекомендовал бы так делать :)

      > В общем спасибо большое, тонны полезных комментариев, с множеством всего согласен, буду исправлять и дорабатывать!

      Пожалуйста.
  • 0
    Я пользуюсь. Очень удобно прикрутилось для моих задач (вывести в городе несколько маркеров и дать возможность пользователю добавить свой и перетащить в нужное место). Один только вопрос, если карта обновляется с openstreetmap, то как часто?
    • 0
      Тайлы CloudMade теоретически должны обновляться каждую неделю, но на практике бывают задержки. Но с Лифлетом хорошо то, что можно в любой момент переключиться на другого провайдера, если что. :)
  • +2
    Слишком много критики.
    1. Leaflet был написан в противовес тяжелым скриптам вроде openlayers и не ставил себе задачи наделать кучу фич. В первую очередь задачей была легкость и поддержка мобильных браузеров.
    2. Если вы не понимаете, что маркеры являются отдельным слоем, это не вина leaflet.
    3. addLayer('cloudmade') — Вы нарушили паттерн, теперь классу map необходимо будет знать как создать слой 'cloudmade'
    4. >Если вы думаете, что вебмастера легко заметят
    Идут лесом вебмастера, которые не заметят этого. Это не вебмастера.
    • –2
      Новый жанр, критика критики?
      • +1
        Жанр старый, но редкий: Заступился за либу так как хороших аналогов мало, а ее унизили так, как будто это очередной никому не нужный клон, каких море.
        • 0
          А, так это не критика критики, а критика критика.
          Ознакомьтесь с историей вопроса, ок?
          habrahabr.ru/blogs/ui/136855/
          habrahabr.ru/blogs/ui/136855/#comment_4559694
          • 0
            целью была защита, а не критика кого/чего-либо, просто местами у вас была слишком резкая и неудачная критика (на мой взгляд) извините, если задел, я просто выразил свое субъективное мнение.

            у 2gis и leaflet разные задачи, и довольно-таки неверно было сравнивать их на одном уровне.
            2gis — провайдер карт и просто обязан иметь полное api к своему сервису, а leaflet предоставляет простой и удобный (опять же, на мой взгляд) api, красивый интерфейс по умолчанию и офигенную скорость по сравнению с навороченными соперниками. это мой выбор для подключения на страницах, где не требуется выдавать маркеры со сложными всплывающими информерами, а просто и быстро показать отмеченное место. Для наворотов на сложных картах (которые по-любому будут дольше грузиться из-за интерактивных слоев) я выберу другую библиотеку, а для виджетов с контактами — скорее leaflet (чем openlayers, modestmaps или еще что-нибудь).

            Из того, что реально хочется, так это задание bounds для карты, чтобы нельзя было вылезти за их пределы (в дополнение к min|max zoom).
            • 0
              только не в ущерб текущей функциональности — ибо по-моему сейчас она работает на порядок лучше аналогов (а именно — без глюков при зуме, плавно и в то же время быстро).
        • 0
          Спасибо на добром слове! Я сам нарвался. :)
          • 0
            ну и не последним фактором является оформление, которое у leaflet на хорошем уровне. так держать!
            быстро, стильно, без излишних наворотов — то, чего большинству так не хватает.
  • 0
    Извините, а мне одному кажется, что это блок про пользовательские интерфейсы?
    Или автор рассказывает именно про них, просто я это умудрился не понять?
    • 0
      *блог
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Эта статья написана специально по просьбе создателя Leaflet Mourner.
      И, поверьте, в картографических API я кое-что понимаю.
      • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Как мне поступить, если я хочу, чтобы по клику открывался балун, а по драгу таскалась карта? Вполне обычный юзкейс


    Суры и асуры! Я только что написал такое в обработке клика (this — это маркер, this._icon — его DOM-элемент):

    this.dragging.enable();
    this._icon.click();
    this.dragging.disable();
    


    Вот как раз ради такого юзкейса, да. Неужели так и надо?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.