Leaflet як оболонка для Яндекс-карт. Відображаємо 100000 маркерів на карті

Я дуже люблю Leaflet. З його допомогою можна дуже швидко будувати свої інтерактивні карти. Однак, практично всі доступні постачальники тайлів (шарів для карт) надають свої послуги за досить значні гроші. Існують такі OpenSource-проекти, як OSM, але не завжди їх тайли задовольняють своїм зовнішнім виглядом.

Мета
Мета полягала в тому, щоб зліпити свого повністю безкоштовного кентавра. Мені завжди подобалися Yandex карти, але не їх API. Тому я зацікавився питанням впровадження Яндекс-карти, як шару для Leaflet.


Приклад готового додатку. В репозиторії 48 Мбайт дамп бази.

Побіжне дослідження
Проінспектувавши запити легальної Яндекс-карти, я вирахував сервер тайлів з якими відбувається спілкування.

'http://vec{s}.maps.yandex.net/tiles?l=map&v=4.55.2&z={z}&x={x}&y={y}&scale=2&lang=ru_RU'

{s} - піддомен (subdomain), необхідний для того, щоб не потрапити в ліміт браузера за запитами до одного і того ж домену. Емпіричним шляхом вдалося вирахувати, що це 01, 02, 03, 04
{z} - масштаб шару (zoom)
{x - широта (latitude)
{y} - довгота (longitude)

Це всі дані, які нам необхідні, щоб використовувати тайли Яндекс-карти всередині Leaflet.

Реалізація
Для бэкенде я буду використовувати Ruby On Rails, щоб трохи розвіяти міф про те, що рейки повільні. Адже виводити на карту ми будемо 100 тисяч маркерів!

Першим ділом створимо модель Marker:
rails g model marker

Вміст міграції
class CreateMarkers < ActiveRecord::Migration
def change
create_table :markers do |t|
t.float :lat
t.float :lng
t.string :name
t.string :avatar
t.string :website
t.string :email
t.string :city
t.string :address
t.string :phone
t.text :about

t.timestamps null: false
end
end
end



rake db:create
rake db:migrate


Я написав невелику фабрику, яка генерує 100000 маркерів з заповненими Фейкером полями. Я використовую PostgreSQL. Дамп бази можна знайти у db/db.dump.

Фабрика
# test/factories/markers.rb
FactoryGirl.define do
factory :marker do
lat {Faker::Address.latitude}
lng {Faker::Address.longitude}
avatar {Faker::Avatar.image}
name {Faker::Name.name}
website {Faker::Internet.url}
email {Faker::Internet.email}
city {Faker::Address.city}
address {Faker::Address.street_address}
about {Faker::Hipster.paragraph}
phone {Faker::PhoneNumber.cell_phone}
end
end

# db/seeds.rb
100000.times do |num|
FactoryGirl.create(:marker)
ap "#{num}"
end



Для управління моделлю Marker згенеруємо контролер markers:

rails g controller markers


Код контролера
class MarkersController < ApplicationController
before_action :set_marker, only: [:show]

def index
respond_to do |format|
format.html
format.json {
pluck_fields = Marker.pluck(:id :lat, :lng)
render json: Oj.dump(pluck_fields)
}
end
end

def show
render "show", layout: false
end

private
def set_marker
@marker = Marker.find(params[:id])
end
end




Щоб не втрачати час на побудові AR-об'єкта, я викликаю метод pluck, який виконує SELECT-запит тільки до потрібних мені полів. Це дає значний приріст в продуктивності. Результат являє собою масив масивів:

[
[1,68.324,-168.542],
[2,55.522,59.454],
[3,-19.245,-79.233]
]


Так само я використовую гем Oj для швидкої генерації json. Втрати на view не перевищують 2мс для 100000 об'єктів.

Не забуваємо вказати новий ресурс в routes.rb:

Rails.application.routes.draw do
root to: "markers#index"
resources :markers, only: [:index, :show]
end


Приступаємо до самої карті.

Для такої великої кількості маркерів необхідний кластеризатор. У Leaflet є великий вибір різних плагінів, додають потрібний нам функціонал. Я зупинився на PruneCluster.

Підключаємо всі необхідні бібліотеки:

application.css
/*
*= normalize
*= require leaflet
*= require prune_cluster
*= require_tree .
*= require_self
*/


application.js
//= require jquery
//= require leaflet
//= require prune_cluster
//= require_self
//= require_tree .



Для того, щоб промалювати карту, необхідно зробити базову розмітку:

markers/index.html.slim
#map


application.css
#map {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
}



Тепер ми можемо намалювати leaflet-карту:
var map = L. map('map').setView([54.762,37.375], 8), // Карта всередині блоку #map
leafletView = new PruneClusterForLeaflet(); // Кластер, який ми будемо складати маркери


Так як карта не має жодного шару, ми побачимо тільки сірий фон. Додати шар на карту дуже просто:
L. tileLayer(
'http://vec{s}.maps.yandex.net/tiles?l=map&v=4.55.2&z={z}&x={x}&y={y}&scale=2&lang=uk_ua', {
subdomains: ['01', '02', '03', '04'],
attribution: '<a http="yandex.ua" target="_blank">Яндекс</a>',
reuseTiles: true,
updateWhenIdle: false
}
).addTo(map);


Тепер усередині контейнера #map відображається звична нам Яндекс-карта. Однак, нам необхідно перевизначити проекцію на карту меркатора на координатну, інакше буде помітний зсув по координатах. Принагідно зазначимо, звідки leaflet повинен забирати стандартні іконки для маркерів.

map.options.crs = L. CRS.EPSG3395;
L. Icon.Default.imagePath = "/leaflet";


Залишилося запросити всі маркери і промалювати їх на карті:

jQuery.getJSON("/markers.json", {}, function(res){
res.forEach(function (item) {
leafletView.RegisterMarker(new PruneCluster.Marker(item[1], item[2], {id: item[0]}));
});
map.addLayer(leafletView);
})


Зараз наша карта не несе ніякого сенсу, так як неможливо отримати жодної інформації про маркер. Додамо Popup, який буде викликатися при кліці по маркеру і забирати вміст із сервера:

leafletView.PrepareLeafletMarker = function (marker, data) {
marker.on('click', function () {
jQuery.ajax({
url: "/markers/"+data.id
}).done(function (res) {
marker.bindPopup(res);
marker.openPopup();
})
})
}


Створимо відповідну розмітку для Popup:
markers/show.html.slim
h1
| #{@marker.name}
.popup__address
| #{@marker.city}, #{@marker.address}

.nowrap
.popup__avatar
img src="#{@marker.avatar}" width="120" height="120"
.popup__contacts
.popup__contact
b Телефон:
div
| #{@marker.phone}
.popup__contact
b Ел. пошта:
div
a href="mailto:#{@marker.email}"
| #{@marker.email}
.popup__contact
b Вебсайт:
div
a href="${website}" target="_blank"
| #{@marker.website}
p
| #{@marker.about}



Підсумок
Ми інтегрували Leaflet c Яндекс-картами, а значить нам стали доступні всі плагіни для leaflet-карт. Написане додаток не тільки витримує навантаження в 100000 маркерів, але ще при цьому володіє досить корисним функціоналом.

Приклад готового додатку. В репозиторії 48 Мбайт дамп бази.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.