HTML5 Canvas Map — реализация картографического движка

В рамках большого интерактивного веб-ориентированного проекта (подробнее о котором возможно в другом посте) я занимаюсь разработкой картографического движка, реализованного на HTML5 CANVAS. Его разработка дошла до стадии беты и, с одобрения моего руководства, появилось желание продемонстрировать данные карты широкой публике.

image

Общие сведения


Движок разрабатывался без использования каких-либо специализированных библиотек или фреймворков. Единственная используемая библиотека – jQuery.

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

Все отрисовывается на CANVAS’e, за исключением таких элементов как панель дополнительных инструментов и popup’ов меток (хотя в демо по ссылке ниже их все равно нет).

Реализация


Реализация модульная и состоит из следующих основных частей, назначение которых думаю понятно из их названий: CanvasDragger, CanvasEventer, CanvasImgLoader, CanvasMapper, CanvasMarker, CanvasMiniMapper, CanvasResizer, CanvasTools, CanvasZoomer.

Для того чтобы подключить карты достаточно в нужном месте html’a написать следующую строчку:
<canvas id="map2d"></canvas>

Далее в коде JS производим инициализацию (как пример):
$(function() {
    mWrap = new MapsWrapper({
        mapDivId: "map2d" // тут указываем ID canvas’a, в котором будет рисоваться карта
    });
});

MapsWrapper = function(properties) {
    this.initialize(properties);
};
$.extend(MapsWrapper.prototype, {
    v2DMapDiv                : null,
    v2DMapComponent   : null,

    initialize: function(prop){
        this.v2DMapDiv = prop.mapDivId;
        this.initMap();
    },

    initMap: function(){
        var GlobalParams = {
            staticMapUrl: ["http://gate.looxity.ru:8088/map.html", "http://zain.looxity.ru:8088/map.html", "http://kaph.looxity.ru:8088/map.html"],
            initCrd     : {x: 7445, y: 9925},
            initZoom    : 0.25,
            zoomList    : [1, 0.5, 0.25, 0.1, 0.05, 0.025],
            miniMap     : true,
            tools       : {scaler: true, polygoner: true}
        };
        this.v2DMapComponent = new CanvasMapper (this.v2DMapDiv);
        this.v2DMapComponent.initialize(GlobalParams);
    }
});


Остановимся поподробнее на параметрах:
  • staticMapUrl – хосты, с которых подгружаются тайлы карты
  • initCrd – начальные координаты в проекции Гаусса-Крюгера, в данном случае примерно соответствуют нулевому километру автодорог, что рядом с Манежной площадью.
  • miniMap – подключение модуля миникарты
  • tools – подключение модуля дополнительных инструментов

Внутренняя механика


Или что скрывается за тем или иным действием пользователя. Пройдемся по основным событиям.

Стартуем

При инициализации карт рассчитывается количество тайлов, которое нужно показать, чтобы полностью покрыть canvas. Зная размеры canvas’a, при заданном размере тайлов в 256х256, проделываем данную операцию.

Двигаемся

Далее когда происходит движение карты – dragg – проверяем ситуацию если мы передвинули карту на такое расстояние, что нужно подгрузить новый тайл. Так же проверяем все ли тайлы находятся в области видимости, если нет, то запускается «сборщик мусора»:
unVisibleTilesCollector: function() {
        for(var cnt = 0; cnt < this.__TILES__.length; cnt++) {
            if( (this.__TILES__[cnt].canvX + this.tileSize) < 0
                || this.__TILES__[cnt].canvX > this.canvas.width
                || this.__TILES__[cnt].canvY > this.canvas.height
                || (this.__TILES__[cnt].canvY + this.tileSize) < 0
                ) {
                this.__TILES__.splice(cnt, 1);
                cnt--;
            }
        }
    }


Масштабируем (zoomIn, zoomOut)

При срабатывании события “mousewheel” последовательно происходят следующие основные действия:
  • копируется текущее положение всех тайлов
$.extend(this.__ANIM_TILES__, this.mapper.__TILES__)

  • средствами canvas’a и с помощью математики происходит уменьшение или увеличение тайлов (в зависимости от того как мы крутим колесико мыши) из копии, сделанной в пп1
for(cnt; cnt < this.animSteps; cnt++){
	            setTimeout(function(){
	                _this.ctx.clearRect(0,0,_this.canvas.width,_this.canvas.height);
	               _this.ctxMarker.clearRect(0,0,_this.canvas.width,_this.canvas.height);
	                animScale += scale*stepScale;
	                _this.drawAllAnimTiles(evt, {
	                    animScale: animScale,
	                    stepCurrNum: Math.round(Math.abs(animScaleStart-animScale)/stepScale),
	                    stepScale: stepScale
	                });
	             }, delay*cnt);
	        }

  • сверху, по мере подгрузки, накладываются новые тайлы, в соответствии с новым масштабом
MapsWrapper.v2DMapComponent.update()


Работа в браузерах


Работа проверялась в FireFox, Chrome, Safari, Opera и IE последних версий.
Для тех кто все еще не в курсе лишний раз подчеркну следующее. Так как используется canvas, автоматически отпадают все браузеры, не поддерживающие данную технологию, а это — IE версии 8 и ниже и совсем уж старые версии вышеперечисленных браузеров.

TODO List по картам


1. Уменьшение размеров тайлов карты (должно дать ощутимый прирост скорости работы);
2. Слайдер изменения масштаба;
3. Инструмент получения информации по точке на карте (адрес здания, координаты и тп);
4. ???

Демо: share.arkada-sw.ru/canvasmap

ps все права на программный код и карты принадлежат компании, в которой я работаю
pps если данная статья возымеет интерес, то следующим моим постом будет описание примера реального использования данных карт
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 54

    +2
    весьма шустро
    есть ещё kothic.org/js
      0
      когда оптимизируем тайлы, должно стать еще шустрее
      сейчас они весят 50-120Kb, а надо бы как минимум раза в два-три уменьшить
        +5
        Нет, это другое. Кothic — это именно Canvas-рендер, который по координатам генерит картинку прямо в браузере.
        Т.е. сервер разгружается, но на клиенте мы получаем дикие тормоза и плюс пока ещё не совсем совершенное качество картинки.

        А тут движок, который на канвас лишь кладет предварительно отрендеренные тайлы.
        Это, безусловно, по-своему интересно… но практическую пользу я не очень уловил.

        Вопрос к автору топика: в чем конкретный практический профит? Из статьи это неясно.
          –1
          Самый главный профит — то что это canvas, который, в отличие от dom-элементов, дает широкий инструментарий и в перспективе можно делать много интересных интерактивных вещей, как-то: повороты, вращения, рисование и тп визуализации, при этом без безумных подтормаживаний.

          Согласитесь, что надо двигаться в ногу со временем и переходить на новые технологии. Не зря же вон даже Adobe отказывается от flash в пользу html5.

          Что же касаемо компании, в которой я работаю, то тут чисто корпоративная политика, предполагающая независимость и использование исключительно своих сервисов, так как даже у гугл-карт и яндекс-карт есть ограничения в их использовании.
            +3
            Соглашусь лишь частично.
            Двигаться ногу со временем и переходить на новое только лишь потому что оно новое? Не думаю.
            Поймите правильно, я совсем не против Canvas вообще — даже наоборот, сам активно интересуюсь этой темой — но в данном конкретном случае особой пользы не вижу.

            Рисовать что-то поверх можно и на отдельном холсте, можно при помощи SVG… Ну вот разве что поворот карты — это да :) Хотя пока не знаю, зачем это может понадобиться.
            И вот ещё я сейчас побродил по вашей карте с Файрбагом — тайлы как-то очень непонятно кэшируются. И кэшируются ли вообще…
              0
              Польза в данном конкретном случае в том, что на разработку было потрачено совсем немного времени и был задействован всего лишь один разработчик. При этом есть хорошие перспективы для дальнейшего развития, которые так же будут внедряться в кратчайшие сроки. Поставьте себя на место заказчика и эти факторы, уверен, сыграют решающую роль.

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

                Кэширование — нет-нет, я не имел в виду специальный код для этого. Речь именно о «совести браузера» :) Что при таком подходе она как-то странно и малопонятно работает.
                  0
                  Попробуйте погонять в chrome и посмотреть в его отладчике, там хорошо видно что таки кешируется.
                  Да и в firebug'e вроде все ок (ver 1.9.0b2).
                  0
                  Побродил по карте, тайтлы не кэшируются и это плохо, при возврате на предыдущую позицию, даже если я сдигал совсем немного в сторону все заново перерисовывается, неужели еще и подгружается? Я считаю это серезным недостатком, так как это сильно снижает удобство пользования. Таск манагер хрома показывает использование памяти на около 150 метров. Врядли этот проект вменяемо будет работать на мобильных устройствах. Кешируйте! Пусть лучше все будет плавненько.
                0
                К сожалению, на данный момент Canvas в таких случаях очень сильно проигрывает по быстродействию по сравнению с обычными DOM-элементами — в частности, поэтому такой подход я использовал в Leaflet. Повороты и вращения осуществляются и без канваса с помощью CSS3 Transforms, анимация — с помощью CSS3 Transitions (которые работают намного быстрее ручной перерисовки в канвасе). Canvas используется пока только там, где это выгодно (отрисовка векторных данных).
                  0
                  В случае с картами использование Canvas'a вполне оправдано. Так как в некоторых браузерах некоторые карты просто бессовестно тормозят.
                  К тому же, как я уже отмечал, у нас есть что оптимизировать дабы увеличить производительность.
                +1
                посмотрел Кothic и соглашусь, что это совсем другой случай
              0
              Интересно, почему canvas, а не SVG? Векторная графика лучше подходит для таких целей, как мне кажется.
                +1
                Векторная больше тормозит при большом количестве элементов. Это уже давно проверенный факт, поэтому классические движки остаются тайловыми (при малой детализации вектор обходит, с этим экспериментируют).

                В реализации на Canvas — хороший опыт практической обкатки, в будущем позволит выявить достоинства и узкие места. Плата — неподдержка старых браузеров (хотя, возможно, вы реализуете простые тайлы для них). Можно узнать компанию, которая делает этот заслуживающий внимания эксперимент? (Можно в ЛС, я этим занимался как разработчик.)
                  0
                  Спасибо что уже ответили за меня )
                  Я бы еще добавил к минусам svg преемственность от xml и довольно ощутимо разнящиеся реализации в разных браузерах.

                  Плюс к этому выбор в пользу canvas'a пал потому-то у нас есть проект с использованием WebGL и еще на этапе выбора у нас уже существовали растровые тайлы.

                  По поводу названия компании ответил в ЛС.
                +1
                Возможно накрывает хабраэффектом, но у меня ооочень долго грузится.

                Leaflet — мой выбор. Очень шустрая библиотека для отображения тайловых карт. Еще чуть-чуть и монструозный OpenLayers уйдет в небытие :)
                  0
                  Загрузка сервера 0.3%. Возможно вы просто находитесь далеко от нашего сервера.

                  Leaflet — все же немного не по теме. Это обычная карта с использованием dom-элементов.
                    0
                    Вроде в доках заявлена поддержка канваса:
                    leaflet.cloudmade.com/reference.html#tilelayer-canvas
                    Used to create Canvas-based tile layers where tiles get drawn on the browser side. Extends TileLayer, implements ILayer interface.


                    Сам правда ещё не пробовал либу, спасибо Ахреджи за наводку!
                      0
                      Канвас там пока что используется только как один из движков для отрисовки векторных данных (в дополнение к SVG) — один или другой вариант могут быть производительнее в зависимости от характера данных и браузера. А упомянутый выше класс — просто для удобства реализации кастомных слоёв, рисующих канвасными тайлами.
                    0
                    Спасибо за упоминание Leaflet, стараемся :)
                    0
                    Круто выглядит с тенями, как будто настоящая бумажная карта лежит.
                      0
                      Это не тени. Это рельеф )
                        0
                        Больше всего похоже на скан обычной карты из-за этих постоянных сладок и перепадов, не совпадающих с самой картой.
                          0
                          Не сразу понял, что имелось в виду под «рельефом».
                          Смотрится отлично :)
                      +3
                      Нескромный вопрос: а почему вы выбрали именно jquery? Ну, и если прямо — то почему не мутулс? Решено исходя из поставленных задач, или потому что привычнее?

                      Спрашиваю потому, что сам недавно мигрировал в мутулс из jquery и нашел для себя много крутых плюшек. В частности для ситуаций, когда работа с DOM сведена к минимуму (как у вас) и нужно делать грамотное, приятное JS API.

                        0
                        Признаться да, jQuery был выбран именно потому что он привычнее. Хотя по Вашей наводке надо будет посмотреть мутулс. Глядишь и мигрируем )
                          +1
                          Когда меня посадили на мутулс, я пришел в шок и совсем не думал, что буду в дальнейшем кому-то советовать этот фреймворк) Но потом понял — акценты в нем смещены на более низкий уровень, чем у jquery, работа с элементами — всего лишь частный случай. Появляется честное ООП, удобный функционал для работы со стандартными объектами и полная свобода его расширения. Сейчас используем mootools в связке с node.js, где вообще отсутствует объект window и прочие.

                          Пример:
                          var Animal = new Class({
                          initialize: function(age){
                          this.age = age;
                          }
                          });
                          var Cat = new Class({
                          Extends: Animal,
                          initialize: function(name, age){
                          this.parent(age); // calls initalize method of Animal class
                          this.name = name;
                          }
                          });
                          var myCat = new Cat('Micia', 20);
                          console.log(myCat.name); // 'Micia'.
                          console.log(myCat.age); // 20.
                            0
                            Mootools — замечательный фреймворк, но к сожалению совершенно не подходит в качестве основых для других библиотек, т.к. в отличие от jQuery модифицирует нативные прототипы и добавляет кучу глобальных переменных в код, что может конфликтовать с другими библиотеками.
                            0
                            Не совсем понятно, зачем вам строго завязываться на jQuery, если библиотека всё равно в основном работает с канвасом, а не DOM-элементами.
                              0
                              Да, Вы правы, особой необходимости в jQuery нет. Он был использован для ускорения разработки и со временем от него можно легко отказаться.
                            0
                            Точно, jQuery хорошо подходит для тех, кто мало что понимает в JS и хочет вставить в сайт пару свистелок, чтобы было по-современней. Когда же разрабатываются столь сложные штуки, эффективно использовать библиотеку которая только и делает что сглаживает шероховатости стандартного апи и проблемы несовместимости.
                            +1
                            Может быть, имеет смысл при обработке операции drag-n-drop перемещать не тайлы в одном канвасе, а весь сам канвас, а после производить амнистию и оптимизацию.
                              0
                              Да, это Вы хорошо подметили. Я уже думал над тем, чтобы перемещать всю картинку уже сгенерированную в canvas'e, а полную перерисовку делать при необходимости удаления невидимых тайлов, добавлении новых, зуме и тому подобных ситуаций.
                              Будет задел к оптимизации.
                              +2

                              Косяк карты или отрисовки?
                                +1
                                косяк утилиты генерации тайлов
                                и таких моментов довольно много…
                                я уже отмечал, что это дело еще предстоит плотно оптимизировать
                                  0
                                  У вас свой рендер тайлов? :)
                                    0
                                    Да, ниже немного подробнее о нем написано.
                                0
                                При хорошем канале Интернет не хватает предзагрузки для невидимых областей. Если клиент двигает карту по чуть-чуть, то 1 тайла, если резче, то нескольких тайлов. Тогда казалось бы, что вся карта уже есть. И соответственно делать прорисовку большей области и двигать канву, как выше написали.
                                  0
                                  > не хватает предзагрузки для невидимых областей

                                  Тут я с вами не соглашусь. В самом простом случае тогда придется дополнительно подгружать 8 тайлов, а в реальных условиях много больше (> 15). Это практически в два раза повышает нагрузку на сервера и на браузер клиента. В перспективе, когда размер тайлов будет уменьшен в 2-3 раза, их подгрузка при перемещении будет происходить так же быстро как и у, например, яндекс-карт (они к слову предзагрузку тоже не делают, думаю по схожим соображениям).
                                    0
                                    Я говорю об удобстве пользователя, которого обычно не интересует нагрузка на сервер :) На браузер — готов поставить галочку «работать с предзагрузкой».
                                      +1
                                      Занес в вишлист )
                                  +3
                                  Карта, как я понимаю, Геоцентр-Консалтинг и она очень старая с кучей ошибок. Даже если это просто для теста, лучше OSM.
                                    0
                                    Работает на удивление хорошо.
                                    Но механизм плавного зуминга у вас просто фееричен :)
                                      0
                                      Довольно шустро работает, но инструменты лучше пофиксить.
                                        0
                                        А что с ними не так? Нашли какой-то баг?
                                          +1
                                          Может так задумано конечно же, но как мне нарисовать два разных полигона например? Я рисую один полигон, выключаю инструмент, хочу нарисовать полигон в другом месте, но он продолжает рисовать предыдущий. Ну еще выключение инструментов довольно не интуитивное на мой взгляд.
                                            0
                                            Да, насчет выключения инструмента соглашусь, надо еще подумать над этим моментом.

                                            По поводу рисования множества полигонов: возможно в будущем, сейчас они сделаны по большей части для демонстрации.
                                        0
                                        Еще бы тайтлы кэшировались, и было бы вообще замечательно!
                                          0
                                          Расскажите подробнее про утилиту генерации тайлов, где она живет, как прикручина к бэкэнду, какова ее производительность.
                                          А кэширование тайлов на стороне сервера есть?
                                          Почему тайлы PNG а не GIF, их размер 50-100kb у яндекса и гугла 10-20Kb.
                                          Подгрузка тайлов происходит только после отпускания кнопки мыши, это недоработка или фича?
                                            0
                                            Наверняка просто не прогоняют через pngcrush/optipng. В остальном с PNG только плюс: гифками гибрид не нарисовать.
                                              0
                                              Нет, наша утилита умеет сохранять в любом формате.
                                              У нас есть проект, для которого нужен формат с альфа-каналом для отрисовки текста, поэтому для него в PNG генерировали. А чтобы не плодить сущностей на данном этапе разработки для canvas карты испульзуем те же самые тайлы.
                                            0
                                            > Расскажите подробнее про утилиту генерации тайлов, где она живет, как прикручина к бэкэнду, какова ее производительность.
                                            А кэширование тайлов на стороне сервера есть?


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

                                            > Почему тайлы PNG а не GIF, их размер 50-100kb у яндекса и гугла 10-20Kb.

                                            Для теста решили сделать в PNG, после оптимизации конечно же будем менять формат.
                                            Такой большой размер из-за, опять таки, формата и отображения рельефа.

                                            > Подгрузка тайлов происходит только после отпускания кнопки мыши, это недоработка или фича?

                                            Сейчас попробовал в Firefox, Chrome и Opera — везде подгрузка происходит во время нажатой кнопки мыши. Каким браузером пользуетесь? Возможно просто был лаг и не дождались подгрузки?
                                              0
                                              Разыскивается человек, знающий HTML5, canvas, возможно Adobe EDGE. Для работы в офисе над созданием интерактивного контента. Кому интересно — стучите.
                                                +2
                                                Кстати говоря, уже есть библиотека для онлайн-карт, которая полностью построена на работе с канвасом — tile5.org. Посмотрите на досуге, может, почерпнёте что-нибудь полезное. :)

                                                Only users with full accounts can post comments. Log in, please.