динамическая легенда к карте из тайлов в Leaflet

Mapserver, GeoServer, MapGuide, Google и другое ПО для веб-картографии
Trippal
Участник
Сообщения: 89
Зарегистрирован: 16 май 2012, 21:29
Репутация: 0

динамическая легенда к карте из тайлов в Leaflet

Сообщение Trippal » 07 авг 2015, 15:33

Уважаемые форумчане, добрый день!
Нужно написать функцию для формирования динамической легенды состоящей из тайлов - переработанный shape-файл в png плитку с раскраской в формате CSS. Мудрено, понимаю, но другого формата не нашел для отображения 14 млн точек в полигонах, topojson не спасет, слишком большой будет(даже он), сам шэйп- тоже не вариант, грузно все равно, а тайлы быстренько обновляются.
Суть в следующем:
L.Control (легенда) делится на две части - 20/80
первая часть (20%) занимает объект - цвет + описание который находится под курсором (мы наводим, цвет определяется, затем преобразуется в название)
вторая часть (80%) - показывает доминирующие 3-6 цветов.

есть два скрипта, но как их объединить в один L.Control?
дам две ссылки:
1)http://jsfiddle.net/DV9Bw/1/ -показывает цвет под курсором
2)http://lokeshdhakar.com/projects/color-thief/ -показывает доминирующие цвета

Trippal
Участник
Сообщения: 89
Зарегистрирован: 16 май 2012, 21:29
Репутация: 0

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение Trippal » 12 авг 2015, 09:35

Сам спросил, сам ответил, но вот странно, почему работает только на первоначально загруженный кусок карты, а если двигать, то ничего не отдает??

Код: Выделить всё

var map = L.map('map').setView([51.505, -0.09], 13);
var tl = L.tileLayer('http://c.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>',
    maxZoom: 18
}).addTo(map);
tl.on('tileload', function(tile, url){
    var top = $(tile.tile).css("top");
    var left = $(tile.tile).css("left");
    var url = $(tile.tile).attr("src");
    createCanvas(top, left, url);
});


var ourCustomControl = L.Control.extend({

  options: {
    position: 'topleft' 
    //control position - allowed: 'topleft', 'topright', 'bottomleft', 'bottomright'
  },

  onAdd: function (map) {
    var container = L.DomUtil.create('div', 'leaflet-control-custom');
    return container;
  }

});
map.addControl(new ourCustomControl());

function findPos(obj) {
    var curleft = 0, curtop = 0;
    if (obj.offsetParent) {
        do {
            curleft += obj.offsetLeft;
            curtop += obj.offsetTop;
        } while (obj = obj.offsetParent);

        return { x: curleft, y: curtop };
    }

    return undefined;
}

function rgbToHex(r, g, b) {
    if (r > 255 || g > 255 || b > 255) {
        throw "Invalid color component";
    }
    return ((r << 16) | (g << 8) | b).toString(16);
}

$('#map').on("mousemove", "#canvas",function(e) {
    var pos = findPos(this);
    var x = e.pageX - pos.x;
    var y = e.pageY - pos.y;
    var coord = "x=" + x + ", y=" + y;
    var c = this.getContext('2d');
    var imageData = c.getImageData(0, 0, this.width, this.height);
var pixelArray = imageData.data;
    
    var p = c.getImageData(x, y, 256, 256).data; 
    var hex = "#" + ("000000" + rgbToHex(p[0], p[1],p[2])).slice(-6);
    $('.leaflet-control-custom').html(coord + "<br>" + hex).css("background", hex);
});

function createCanvas(top, left, url){
    var canvas = $('<canvas />').attr({
        id: "canvas",
        width: 256,
        height: 256,
        class: "leaflet-tile leaflet-tile-loaded"
    });
    canvas.css("top", top);
    canvas.css("left", left);
    var ctx = $(canvas)[0].getContext('2d');
    var image = new Image();
    image.onload = function() {
        ctx.drawImage(this, 0, 0);
    };
    image.crossOrigin = "Anonymous";
    image.src = url;
    ctx.drawImage(image, 0, 0);
    $(".leaflet-tile-container").append(canvas);
}


\\Немного покопавшись понял в чем проблема, но возникает следующий квест:
Как сделать так чтобы при добавлении нового слоя он становился canvas плиткой?
То есть, когда я запускаю проект изначально стоит канвас, когда я включаю оверлей слой, то канвас на него не рисуется. почему так и как это решить?

parshin
Участник
Сообщения: 57
Зарегистрирован: 13 фев 2011, 10:34
Репутация: 26
Откуда: Moscow, Russia
Контактная информация:

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение parshin » 13 авг 2015, 19:25

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

Лучше воспользоваться L.TileLayer.Canvas (в 1.0beta - L.GridLayer). И там и там есть асинхронное создание тайла, можно грузить img, рисовать её в canvas, а потом возвращать canvas leaflet'у.
Как сделать так чтобы при добавлении нового слоя он становился canvas плиткой?
Если вы знаете, что все интересующие вас слои L.TIleLayer, то можно на основе их options создать свой canvas-слой, а исходные слои на карту не добавлять. Если в проекте могут оказаться произвольные слои, то не знаю, как вывернуться...


Ещё можно попробовать так. После добавления всех растровых слоёв подписаться на событие mousemove на всех img элементов внутри контейнеров интересующих слоёв. При движении курсора динамически создавать canvas элемент, рендерить в него img под курсором и получать цвет из canvas. Придётся решить проблему с порядком слоёв (если бывает больше одного слоя на карте) и с производительностью (рисование картинки в canvas - относительно дорогая операция; нужно хранить последний canvas и обновлять его только при переходе мышки на другой тайл).

Trippal
Участник
Сообщения: 89
Зарегистрирован: 16 май 2012, 21:29
Репутация: 0

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение Trippal » 14 авг 2015, 09:27

Ещё можно попробовать так. После добавления всех растровых слоёв подписаться на событие mousemove на всех img элементов внутри контейнеров интересующих слоёв. При движении курсора динамически создавать canvas элемент, рендерить в него img под курсором и получать цвет из canvas. Придётся решить проблему с порядком слоёв (если бывает больше одного слоя на карте) и с производительностью (рисование картинки в canvas - относительно дорогая операция; нужно хранить последний canvas и обновлять его только при переходе мышки на другой тайл).
Во с этого момента по-подробнее, так как я пришел к такой же мысли, но не получается написать код... :D

parshin
Участник
Сообщения: 57
Зарегистрирован: 13 фев 2011, 10:34
Репутация: 26
Откуда: Moscow, Russia
Контактная информация:

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение parshin » 14 авг 2015, 10:04

А поподробнее - это как? :)

Про контейнер слоя я, видимо, погорячился - он нулевого размера. Наверное, придётся подписаться на событие tileload, внутри которого на каждый тайл вешать mousemove. По событию mousemove создавать новый canvas, делать в него drawImage картинки под мышкой, затем получать цвет под мышкой. Если это заработает, нужно думать под оптимизацией и разруливанием ситуации с несколькими слоями на карте...

А, вот ещё подумал, и понял, что так не получится из-за cross origin restrictions - нужно, чтобы исходные картинки были запрошены с атрибутом crossOrigin. Leaflet 0.7 так не умеет, а в 1.0b появился параметр crossOrigin (true/false). То есть, либо для 0.7 писать свой класс на основе L.TileLayer.Canvas, либо использовать 1.0b и пытаться делать, как я выше написал...

Trippal
Участник
Сообщения: 89
Зарегистрирован: 16 май 2012, 21:29
Репутация: 0

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение Trippal » 14 авг 2015, 11:18

ох, как все закручено)
с моим опытом ещё две недели буду разбираться)
спасибо большое за советы!
правда у меня много плагинов завязаны на 0.7 версию

А я-то думаю...откуда такие познания leaflet? Вы же работаете над GeoMixer, а он соответственно на лифе и основан)
Приятно познакомиться, Александр! :D

parshin
Участник
Сообщения: 57
Зарегистрирован: 13 фев 2011, 10:34
Репутация: 26
Откуда: Moscow, Russia
Контактная информация:

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение parshin » 14 авг 2015, 12:10

ох, как все закручено)
Да, работа с картинками на уровне пикселей в браузере - нетривиальное развлечение :)
правда у меня много плагинов завязаны на 0.7 версию
К сожалению, в 1.0b внутренняя структура кода сильно поменялась, по крайней мере в части тайловых слоёв. Многим разработчикам плагинов придётся постарасться, чтобы поддержать обе версии. Но оно того стОит - внутренняя логика и возможности расширения растровых слоёв значительно улучшились! Вот бы ещё документацию для разработчиков плагинов кто-нибудь написал бы :)
А я-то думаю...откуда такие познания leaflet? Вы же работаете над GeoMixer, а он соответственно на лифе и основан)
Приятно познакомиться, Александр! :D
Да, в GeoMixer'е мы много (иногда кажется, что даже слишком) работали с leaflet.

Я тоже рад познакомиться, хоть и не могу найти, как вас зовут :)

Trippal
Участник
Сообщения: 89
Зарегистрирован: 16 май 2012, 21:29
Репутация: 0

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение Trippal » 14 авг 2015, 12:39

Меня зовут Дмитрий. В свое время хотел в сканекс устроиться на работу (я им ещё предлагал открыть представительство в СПб), но мне тогда отказали по всем параметрам(опыта было мало). Теперь насупившись иду своим путем =) Сам себе начальник, продавец, инженер и т.п. Правда тут совзонд охотится. Уж слишком сладок мой опыт в дорожной отрасли.

По поводу версионности, да, я заметил множество нововведений, даже в самом коде, ставших уже традиционными, компонентов. Однако, меня просто возмутило, что они убрали L.TileLayer.canvas...Поэтому и мучаюсь на будущее, когда он станет final realise. В любом случае перейду на единичку.
А может использовать вместо канваса грид тайлы? Они вроде динамичнее развиваются. Может и объем получится с грид получить в смешении с THREE.JS

parshin
Участник
Сообщения: 57
Зарегистрирован: 13 фев 2011, 10:34
Репутация: 26
Откуда: Moscow, Russia
Контактная информация:

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение parshin » 14 авг 2015, 12:51

По поводу версионности, да, я заметил множество нововведений, даже в самом коде, ставших уже традиционными, компонентов. Однако, меня просто возмутило, что они убрали L.TileLayer.canvas...Поэтому и мучаюсь на будущее, когда он станет final realise. В любом случае перейду на единичку.
Зря возмущаетесь - это очень логичное изменение. Раньше L.TileLayer.Сanvas наследовался от L.TileLayer, и это приводило к ряду проблем. Сейчас L.GridLayer - это именно та общая часть, которая была у L.TileLayer и L.TileLayer.Canvas. Использовать L.GridLayer так же просто, как и старый L.TileLayer.Сanvas. Например, https://github.com/Leaflet/Leaflet/blob ... /grid.html

Trippal
Участник
Сообщения: 89
Зарегистрирован: 16 май 2012, 21:29
Репутация: 0

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение Trippal » 14 авг 2015, 13:10

интересно, если подключить его как отдельный скрипт с версией .7...потом ведь будет не так больно на единичку переходить. Да и судя по ссылке все в целом не так сложно. А с грид тайлов можно получать цвет?))

parshin
Участник
Сообщения: 57
Зарегистрирован: 13 фев 2011, 10:34
Репутация: 26
Откуда: Moscow, Russia
Контактная информация:

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение parshin » 14 авг 2015, 13:47

Отдельно к старой версии подключить его не удастся - он просто не будет работать со старым L.Map.

Посмотрите мой плагин: https://github.com/aparshin/leaflet-boundary-canvas/. Интерфейс самого класса очень похож на L.TileLayer, но внутри картинки рисуются в canvas и обрезаются по контуру. Вам обрезать не нужно, а нужно вешать обработчики событий и т.д. Плагин работает и под старой, и под новой версией leaflet. Кстати, L.TileLayer.BoundaryCanvas.createFromLayer() - попытка оборачивать произвольный L.TileLayer в canvas-обёртку.

Trippal
Участник
Сообщения: 89
Зарегистрирован: 16 май 2012, 21:29
Репутация: 0

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение Trippal » 14 авг 2015, 13:58

parshin писал(а):Отдельно к старой версии подключить его не удастся - он просто не будет работать со старым L.Map.

Посмотрите мой плагин: https://github.com/aparshin/leaflet-boundary-canvas/. Интерфейс самого класса очень похож на L.TileLayer, но внутри картинки рисуются в canvas и обрезаются по контуру. Вам обрезать не нужно, а нужно вешать обработчики событий и т.д. Плагин работает и под старой, и под новой версией leaflet. Кстати, L.TileLayer.BoundaryCanvas.createFromLayer() - попытка оборачивать произвольный L.TileLayer в canvas-обёртку.
Вот это прямо то, что нужно!
Спасибо огромнейшее!

parshin
Участник
Сообщения: 57
Зарегистрирован: 13 фев 2011, 10:34
Репутация: 26
Откуда: Moscow, Russia
Контактная информация:

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение parshin » 14 авг 2015, 14:00

Да не за что. Удачи!

Trippal
Участник
Сообщения: 89
Зарегистрирован: 16 май 2012, 21:29
Репутация: 0

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение Trippal » 14 авг 2015, 14:23

parshin писал(а):Да не за что. Удачи!
<div id="map"></div>
<script src="../js/workinjs/BoundaryCanvas.js"></script>
<script>
var map = new L.Map('map', {center: new L.LatLng(72.446903, 99.029379), zoom: 5, maxZoom: 23, minZoom: 3,fullscreenControl: true,fullscreenControlOptions: { position: 'topleft'}, measureControl:true});
var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
var ggl = new L.Google();
var ggl2 = new L.Google('TERRAIN');
var yndx = new L.Yandex();
var botanyc = new L.TileLayer.BoundaryCanvas.createFromLayer('/js/tiles/rastitelnost/anotheroneone/{z}/{x}/{y}.png', {maxZoom: 15, tms:true})
map.addLayer(ggl);
map.addControl(new L.Control.Layers( {'OSM':osm, 'Google':ggl, 'Google Terrain':ggl2,'Yandex':yndx, 'Без карты':map}, {'Ботаническая карта': botanyc, 'GPS Треки': olen_l}));
</script>

что-то ботаническая карта не выпрыгивает... :(
я чувствую себя тупым)

parshin
Участник
Сообщения: 57
Зарегистрирован: 13 фев 2011, 10:34
Репутация: 26
Откуда: Moscow, Russia
Контактная информация:

Re: динамическая легенда к карте из тайлов в Leaflet

Сообщение parshin » 14 авг 2015, 14:32

createFromLayer - фабричная ф-ция. Её нужно использовать, если уже есть готовый растровый слой, который вы хотите попробовать превратить в BoundaryCanvas. В вашем же случае нужно инстанциировать просто L.TileLayer.BoundaryCanvas.

А ещё этот класс скорее всего не будет работать, если не задать параметр boundary (наверное, это нужно поправить).

Ответить

Вернуться в «Веб-картография»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость