API карт від 2ГІС рецензія

Нещодавно 2ГІС порадував усіх нас випуском версії 1.0 свого картографічного JS API. API карт вітчизняного виробництва – штука рідкісна; Mail.ru і Rambler, наприклад, так і не спромоглися, хоч і обіцяли (пруф разів, пруф два). Подивимося, що в новосибірців.

Знайомимося

Не відкладатимемо в довгу скриньку, відкриваємо розділ "Швидкий старт" і копіюємо запропонований код. It works! Щоправда, заголовок показує кракозябрами — воно, правда, і не дивно, оскільки тега із зазначенням charset-а немає. Ну та гаразд, дрібниці життя. Дивимось на код.

Гаразд. Дивимося далі.

На перший погляд начебто нічого. Є деякі дива, звичайно (contentHtml замість htmlContent, неможливість задати контекст для callback-ів). Але код визначення знаходження балуна на карті мене потряс:

А простіше якось не можна? Метод isOpen балуну зробити, наприклад?

У неї всередині неонка

Гаразд, залишимо quickstart. Подивимося, що там усередині.

2гіс

Передзавантажувач 1.5 Кб - відмінно. Код АПІ 200 Кб – нормально. Таймінги так собі. CSS в 955 байт - wtf?

АПІ створює в глобальній області видимості три змінні - DG (Dolce & Gabbana? Deutsche Grammophon? не найкращий вибір для неймспейсу), OpenLayers (сюрприз!) і $.

Дивимось у код. І справді, нас зустрічає старий добрий OpenLayers.

Рішення дивне. По-перше, OpenLayers, м'яко кажучи, піддався від життя: в 2012 працювати без використання transforms & transitions – вже якось моветон. По-друге, BSD-ліцензія накладає деякі обмеження (http://trac.osgeo.org/openlayers/browser/license.txt), яких, взагалі-то, треба дотримуватися.

$ з'являється у глобальній області від prototype. Як мінімум, неакуратно, навітьпри тому, що при підключенні jQuery в $ виявляється саме він (що тоді заважає прибирати сліди присутності prototype завжди?)

До речі, верстка їде в quirks mode в IE будь-якої версії - в API бажано все ж таки QM підтримувати.

Самі тайлики невеликі по 20 Кб. Рендер їм, мабуть, дістався від десктопної версії.

карт

З одного боку, підкладка зроблена досить сучасно для веб-картографії - не перевантажена деталями і не надто контрастна, в сірих тонах а-ля Гугл.

З іншого боку, робота зі шрифтами та субпіксельне згладжування – жахливе. Крім того, пінги до сервера дуже великі.

Порядок координат - long-lat. Цікаво, яка проекція — сферична, як у гугла чи еліптична, як у Яндекса? Документація відповіді не дає. Пробуємо накласти зверху яндексовий гібрид - тайли розходяться. Отже, все ж таки, проекція сферична. У документації про шари про це ні слова — як тоді користувач повинен тайлики нарізати? Навмання?

Гаразд. Закінчимо з препаруванням, йдемо далі.

Комплект поставки

Отже, нам дано з функціоналу:

  • власне карта;
  • мітки;
  • графіка;
  • елементи керування (а точніше, елемент керування - zoom);
  • шари;
  • API для AJAX-запитів;
  • пошук.

Для старту API – у принципі, нормально. Дивує лише відсутність встановлених контролів — не встигли зробити?

можна

Переходимо до розділу «Карта» та натикаємось на «Вступ» – «Завантаження бібліотеки» – «Версія біліотеки». Якось несподівано бачити це в розділі «Карта», чи не так?

Потім скористаємося функцією DG.autoload, в яку розміщуємо код ініціалізації картки:

Саме такий спосіб розглядався у розділі «Швидкийстарт». Але у функції DG.autoload є певні обмеження: вона використовує обробник події window.onload. Тому якщо на сторінці вже визначено обробника window.onload, то будуть конфлікти (щоб обійти ці конфлікти в наявний обробник window.onload можна вставити код ініціалізації картки).

WTF? Навіщо перевизначати window.onload? Написати

швидше буде, аніж цей абзац документації. Навіщо, до того ж, цю примітку забрали з quickstart — щоб більше користувачів настало на ці граблі? Якось непродумано.

Щоб встановити центр карти викликаємо метод картки setCenter:

Що за point:? Неакуратно.

Знищує всі об'єкти, які містять картку. Щоб знищити сам об'єкт myMap після виклику даного методу рекомендується присвоїти null об'єкту картки:

Що за дивний шмат коду? Який він ставиться до destroy має? Хлопців, ну все ж таки документацію по базовому класу треба вичитувати.

myMap.addEventListener(objectId, eventType, callback)

Параметри: objectId String Ідентифікатор DOM-елемента, до якого прикріплюємо обробник.

Розрив шаблону. Навіщо при навішуванні обробника події на карту потрібно обов'язково вказувати DOM-елемент? Якщо вказати id, відмінний від map.getContainerId() — нічого не працює. Насильство над користувачем якесь.

Параметри: moveStep Number Так Крок, на скільки пікселів змістити карту на північ.

Знову розрив шаблону. Пікселі на північ – це як? Градуси дуги на північ – розумію, пікселі нагору – розумію, пікселі на північ – не розумію. Та й сенс існування цього методу від мене вислизає.

Параметри: minZoom Number Так Мінімально можливий коефіцієнт масштабування (zoom). Мінімальне допустиме значення: 1.

Мінімальнодопустиме значення - 1, контроль зуму теж не дає менше одного масштабу виставити. Але якщо при ініціалізації карти масштаб не задати, опинишся на 0-му. Цікаво як так. До речі, на 0-му масштабі getBounds карти віддає область [-268, 434] за довготою - чи давно у нас довготи більше 180 градусів з'явилися?

Встановити обмеження на межі картки myMap.setBoundsRestrictions(bounds, isChangePosition)

isChangePosition? Треба все ж дружити з англійською.

З дивностями addEventListener ми вже познайомилися. Далі – більше: addEventListener є, removeEventListener-а – ні. Навіщо тоді використовувати DOM-назви - вводити користувача в оману?

Імена подій потрібно писати ось так: "DgClick" - g маленька. Зважаючи на те, що у всіх інших місцях DG завжди пишеться великими літерами — дратівливо неконсистентно. У прикладах, до речі, знову карта ініціалізується без масштабування.

Майже кожна подія має свій тип. Сенс наявності такої великої кількості класів від мене вислизає. Наприклад, у DG.Events.Map можна запитати get(MinMax)Zoom - навіщо? Кому потрібний цей функціонал?

Параметри: options.geoPoint DG.GeoPoint Географічне положення точки, яку вказує маркер. options.icon DG.Icon Зображення маркера. Нижче описано клас DG.Icon докладніше. Якщо icon не вказано, використовується стандартна картинка. options.clickCallback Function Обробник, який викликається в момент натискання мишки по маркеру. Контекст виклику функції: об'єкт window.

callback на клік - це, звичайно, корисно. Але тоді вже треба йти далі і робити callback як мінімум на mouseenter/mouseleave.

Кожен маркер має належати певній групі. Це дозволяє виконувати групові операції. Наприклад, нехай у нас є два типимаркерів: одні для висотних будівель, інші для приватних будинків. Розмістивши перші в одну групу, а другі в іншу, ми можемо явно маніпулювати кожним з наборів маркерів.

Угруповання маркерів - це, звичайно, зручно. Але чому групи не можна вкладати у групи? Це ще зручніше, і позбавить від необхідності мати групу за замовчуванням.

Зазначу, що такі самі групи є для балунів, графіки і шарів. Навіщо їх описувати окремо? Введіть загальний базовий клас Group та GroupManager, документація скоротиться вдвічі.

Виконати операцію для кожного маркера групи myGroup.forEach(callback, context)

ГХМ. У жодному іншому методі контекст виконання поставити було не можна, а тут — можна. Для схожості на браузерний Array.forEach? Ну тоді всі інші методи, починаючи з addEventListener, потрібно приводити до браузерної сигнатури. До речі, у JS 1.6 параметр context в forEach необов'язковий, на відміну.

Розглянемо приклад створення балуна:

Порожній рядок у середині коду - це , який забули заескейпити (на відміну від квікстарту). Знову ж таки, неакуратно — приклад тепер не працює.

Параметри балуна знову дивують вільним поводженням з англійською мовою та здоровим глуздом (isClosed — чи показувати хрестик, contentSize — чи зафіксований розмір).

Цікаво, що подій у балуна немає (чи не описані?).

Хінтів нема. А не завадили б.

Про геометрію поганого слова не скажу — хороша, добротна графічна бібліотека з широкими можливостями налаштування (опції linecap — вибір округлення ліній у місці зламу — я, здається, ніде не зустрічав). Хіба що дивує відсутність подій. (Втім, чому дивує? Проблема відома — навіть прозорий canvas/svg контейнер закриває собою карту.:)). Додати геодезичні залишки.

Елементи управління

Позиціювання контролю – черговий приклад насильства над мозком користувача.

І це все заради того, щоб приліпити контроль у правий кут з офсетом 20,10. Я розумію, що у Старших Братів така ж балалайка — але ж це не привід копіювати невдалі рішення. Чому б замість

не писати ? Або навіть так: ?

І віднести заразом цю настройку в конструктор контролю, щоб не писати null замість імені групи controls.add?

З елементів керування доступний лише зум — чи не встигли зробити інше? Чи з верстальниками якісь проблеми? Мабуть, щоб компенсувати відсутність вбудованих елементів, відкрили інтерфейс для створення користувацьких - DG.Controls.Abstract.

Я, до речі, спробував додати на карту new DG.Controls.Abstract() - відпрацювало штатно, винятків не кинуло. Тож це не abstract, а base. Прив'язка методу успадкування класів extend до абстрактного класу не виглядає правильним рішенням — це цілком універсальний метод, навіщо обмежувати його застосування конкретним класом.

Сам інтерфейс виглядає цілком осудно - хіба що хелпери getStates()/setState() виглядають абсолютно марними.

API карт дозволяє накладати користувацькі шари поверх шару географічної карти. Це дозволяє відображати на карті практично будь-які об'єкти, маршрути, області. Наприклад, легко показати зони покриття міста інтернет-провайдерами, або ж у графічному вигляді відобразити області доставки магазину або піцерії із зазначенням часу та вартості доставки. Також можна вказати шлях проїзду до ваших офісів або маршрут екскурсії містом. Одним словом, можливості обмежені лише фантазією.

… а також необхідністю вгадатипроекцію, як ми з'ясували.

options.countTileServers Number Кількість субдоменів для тайлого сервера.

Параметр використовується разом із options.tilePrefix. За промовчанням options.countTileServers дорівнює нулю - тайли завантажуються з одного сервера.

options.tilePrefix String Префікс імені субдомену тайлового сервера.

Наприклад, параметр url дорівнює httр://example.com/$/$/$.png. Якщо ви визначаєте options.tilePrefix як mytile - отримуємо URL субдоменів тайлового сервера у вигляді: http://mytile1.example.com/, http://mytile2.example.com/, http://mytile3.example.com/ і так далі, залежно від параметра options.countTileServers. Тому параметр використовується разом із options.tilePrefix.

Ее. Що це за магія? Чому б просто ось так не зробити: http://mytile$.example.com/$/$/$.png?

Сенс існування AJAX API не зрозумілий зовсім. Будь-який ajax-api будь-якого фреймворку зручніший і функціональніший, навіщо картографічному АПІ взагалі залазити на цю галявину? До речі, у вас друкарська помилка - failt ure.

В цілому, 2ГІС API залишає подвійне враження.

З одного боку - так, повноцінне API, розроблене в стислий термін, з гарною власне функціональною складовою (90% потреб покриє), заділи для розширення залишені.

З іншого:

  • непродуманість та неконсистентність багатьох інтерфейсів;
  • нехтування тим, що називається look'n'feel - "смачністю", сексуальністю дизайну карти та UI;
  • неакуратна документація, мала кількість прикладів;
  • відсутність у складі АПІ інструментів для роботи з власне 2ГІС-івськими даними.

А у нас тут можна отримати грант на тестовий період Яндекс.Хмари. Варто лише у полі «секретний пароль» запровадити «Хабр»