⬆️ ⬇️

Leaflet as a shell for Yandex.Maps - we display 100 thousand markers on the map

I love Leaflet very much. With it, you can very quickly build your interactive maps. However, almost all available tile providers (layers for maps) provide their services for very impressive money. There are such OpenSource projects as OSM , but not always their tiles satisfy their appearance.



purpose



The goal was to blind your completely free centaur. I always liked Yandex maps, but not their API. Therefore, I became interested in the introduction of a Yandex map, as a layer for Leaflet.



An example of a finished application. In the repository 48 MB dump base.



Working example Can not survive Habraeffekt.

')

Cursory study



Having inspected the requests of a legal Yandex card, I calculated the server of the tiles with which we are communicating.



'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) 


This is all the data we need to use the Yandex card tiles inside the Leaflet.



Implementation



For the backend, I'll use Ruby on Rails to slightly dispel the myth that the rails are slow. After all, we will display 100 thousand markers on the map!



First, create a Marker model:

 rails g model marker 


Migration Content
 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 




I wrote a small factory that generates 100,000 markers with fake-filled fields. I am using PostgreSQL . The database dump can be found in db / db.dump .



Factory
 # 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 






To control the Marker model, we will generate the markers controller:



 rails g controller markers 




Controller code
 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 






In order not to waste time on building an AR object, I call the pluck method, which performs a SELECT query only on the fields I need. This gives a significant increase in performance. The result is an array of arrays:



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




I also use the Oj gem to quickly generate json. Losses on the view do not exceed 2ms for 100,000 objects.



Do not forget to specify a new resource in routes.rb:



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




Getting to the card itself.



Clustering is required for such a large number of markers. Leaflet has a large selection of different plugins that add the functionality we need. I stopped at PruneCluster .



We connect all the necessary libraries:



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 . 






In order to draw a map, you need to make basic markup:



markers / index.html.slim
  #map 




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






Now we can draw a leaflet card:

 var map = L.map('map').setView([54.762,37.375], 8), //    #map leafletView = new PruneClusterForLeaflet(); // ,       




Since the map has no layers, we will only see a gray background. Adding a layer to the map is very simple:

 L.tileLayer( 'http://vec{s}.maps.yandex.net/tiles?l=map&v=4.55.2&z={z}&x={x}&y={y}&scale=2&lang=ru_RU', { subdomains: ['01', '02', '03', '04'], attribution: '<a http="yandex.ru" target="_blank"></a>', reuseTiles: true, updateWhenIdle: false } ).addTo(map); 




Now the Yandex map familiar to us is displayed inside the #map container. However, we need to redefine the projection of the map from the spherical mercator to the elliptical, otherwise there will be a noticeable shift in coordinates. At the same time, we will specify where the leaflet should take default icons for the markers.



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




It remains to query all the markers and draw them on the map:



 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); }) 




Now our map does not carry any meaning, since it is impossible to get any information about the marker. Add a popup that will be called when clicking on a marker and retrieving content from the server:



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




Create the appropriate markup for 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=" #{@marker.website}" target="_blank" | #{@marker.website} p | #{@marker.about} 






Total



We integrated Leaflet with Yandex-cards, which means that all the plug-ins for leaflet-cards became available to us. The written application not only withstands a load of 100,000 markers, but also has quite useful functionality.



An example of a finished application. In the repository 48 MB dump base.

Source: https://habr.com/ru/post/283354/



All Articles