Яндекс карты для angular.js

image

Приветствую всех, уважаемые харбажители!
В данном посте речь идет о том, как подружить карты яндекса и javascript framework angular.js для их совместной работы. Можно, конечно, использовать и google map, но для стран СНГ их качество оставляет желать лучшего.
Немного погуглив, и не найдя готового решения, пришлось писать свое. Кому интересно, добро пожаловать под кат.

Репозиторий git Demo
В результате публикации этой статьи в первый раз, мною были получены ценные конструктивные замечания, кои вы можете видеть снизу, в комментариях. Спасибо всем, кто готов был учить меня уму разуму, а что из этого получилось, сейчас мы и увидим.
Начнем с самого простого, стоит задача отобразить карту. Решение:
<ya-map ya-zoom="8" ya-center="[37.64,55.76]"></ya-map>

При этом, если не задать атрибут ya-center, центр карты будет выставлен в текущее местоположение пользователя.
Добавить гео. объекты на карту можно 2 способами, первый — непосредственное добавление, второй — добавление в коллекцию гео. объектов. Коллекции используются для объединения гео. объектов в группы, с целью дальнейших манипуляций с ними в сходной манере. Коллекции бывают двух видов: обычная коллекция и кластеризатор. Обычных коллекций на карте может быть сколько угодно, а вот кластеризатор может быть только один, и принимает в себя только точки. Ну хватит теории, перейдем к коду. Добавление гео. объекта непосредственно на карту:
<ya-map ya-zoom="10" ya-center="[37.64,55.76]">
        <ya-geo-object ya-source="geoObject"></ya-geo-object>
</ya-map>

$scope.geoObjects=
    {
        geometry: {
            type: 'Circle',
            coordinates: [37.60,55.76],
            radius: 10000
        },
        properties: {
            balloonContent: "Радиус круга - 10 км",
            hintContent: "Подвинь меня"
        }
    };

Добавление гео. объекта в коллекцию:
<ya-map ya-zoom="10" ya-center="[37.64,55.76]">
    <ya-collection>
        <ya-geo-object ya-source="geoObject"></ya-geo-object>
    <ya-collection>
</ya-map>

Добавление точки в кластеризатор:
<ya-map ya-zoom="10" ya-center="[37.64,55.76]">
    <ya-cluster>
        <ya-geo-object ya-source="geoObject"></ya-geo-object>
    <ya-cluster>
</ya-map>

Понятно, что geoObject теперь должен указывать на точку.
Реализована поддержка всех событий карты. Чтобы подписаться на событие используется конструкция ya-event[-target]-eventname. Здесь ya-event — префикс, определяющий что это подписка на событие, target — если задан, то свойство внутри элемента, на событие которого мы хотим подписаться, eventname — имя события, на которое подписываемся. В обработчик события передается родной объект событий яндекс карт, через параметр $event. Получить объект, возбудивший событие можно через $event.get('target'). Итак, давайте подпишемся на событие click карты и на событие add свойства карты geoObjects.
<ya-map ya-zoom="10" ya-event-click="click($event)" ya-event-geo-objects-add="added($event)"></ya-map>

$scope.click = function(e){
    //что-то делаем при клике на карте
};
$scope.added=function(e){
    //что-то делаем при добавлении объекта на карту
};

Добавление элементов управления на карту производится так же просто. Стандартные элементы управления добавляются с помощью директивы yaToolbar, а если вы хотите создать собственную панель (ну или модернизировать имеющуюся), тогда внутрь ее помещаются директивы yaControl. Пример:
<ya-map ya-zoom="8" ya-center="[37.64,55.76]">
        <!-- стандартные панели управления -->
        <ya-toolbar ya-name="zoomControl"></ya-toolbar>
        <!-- собственные элементы управления -->
        <ya-toolbar ya-name="toolBar">
            <ya-control ya-type="button" ya-params="Свойство: balloonHeader"
                    ya-event-select="balloonHeader($event)"
                    ya-event-deselect="balloonHeader($event)"></ya-control>
        </ya-toolbar>
    </ya-map>

Кроме всего описанного, есть возможность создавать собственные карты, использовать шаблоны для нестандартных элементов управления, и других объектов на карте и еще много чего.
Больше информации вы можете получить на github проекта, а также на сайте с примерами.
Поделиться публикацией

Похожие публикации

Комментарии 26

    +1
    Приветствую всех, уважаемые харбожители!

    Харбахарб.ру :)
      –1
      Извиняйте, очепятки иногда случаются :). Отредактировал.
        +4
        Пёрлхабр =D
          0
          А мне так даже больше нравится — Харбо-жители. Практически небожители.
          Можно даже из пользователей небольшой пантеон набросать =)
          0
          В свое время релизоаывал прослойку для гуглокарт. У меня была такая структура:
          • общая директива ng-map создавала общий scope и принимала общие конфиги для всех объектов карты, все нижеперечисленные директивы должны быть вложены внутрь данной.
          • ng-map-canvas для полотна карты
          • ng-map-marker для позиционирования маркера. Для нескольких маркеров можно использовать ng-repeat :)
          • ng-map-route и ng-map-route-waypoint (в сочетании с ng-repeat) для отрисовки маршрутов
          • ng-markers-cloud — оптимизированный вариант для показа большого количества маркеров, в ng-model можно передать массив для вывода. Из минусов — наблюдение за массивом ведется только по ссылке, изменение отдельных элементов не приведет к перерисовке.
          • ng-map-overlay — универсальная директива, годится как для отображения кастомных маркеров, так и для модальных окон на карте, всплывающих облачков, тултипов. Создавала оверлэй для карты, куда помещалось переданное внутри директивы html-содержимое со всеми преимуществами дата-байндинга.


          Мне лично кажется, что такой подход — с описанием объектов карты в разметке более соответствует принципам фреймворка.
            0
            А по мне, так это дело вкуса. В чем разница между
            <div ya-map="..." ng-model="..." ... ></div>
            и
            <div ng-map>
            <ng-map-canvas>
            <ng-map-marker ng-repeat="...">
            </div>
            ???
            На функционал это не как не влияет, внутри происходит одно и тоже.
              +1
              Внутри — может быть и одно и то же, но при написании библиотеки важно все же как это снаружи, для пользователя библиотеки.
              В вашем случае описание элементов карты находится где-то в контроллере, в моем — во view, что правильнее, т.к. карта и ее элементы — это отображение. В моем подходе можно, например, применить фильтры для сериализации некой модели гео-объектов в удобный для директивы вид:

              <ng-map-marker ng-repeat="geoObject in geoObjects" source="geoObject|serializeGeoObject">

              Это, конечно, может быть медленнее в целом, но подход более чистый для разделения безнес-логики и вью-логики.
                0
                А что мешает применить фильтры, или любые функции для получения выходного набора прямо в контроллере?
                На счет отображения, параметры отображения карты в моем случае находятся не в контроллере, а в настройках провайдера. В контроллере просто существует возможность переопределить некоторые из них в случае необходимости (хотя сомнительно что это кому понадобится). Но это конечно можно делать и в представлении, но в этом случае оно здорово распухнет, и потеряет в производительности.
                Но чисто в концептуальном плане согласен с вами. Хотя любую концепцию тоже не стоит потреблять вслепую. Чтобы выяснить что лучше, по уму, нужно написать код по обоим вариантам и пропустить его через тесты на производительность. Возможно я попробую это сделать на досуге.
                  0
                  А что будет если в ng-model содержится массив и 100 маркеров, а потом вы удалили один? Модель изменится и вам придется либо обновлять все маркеры, либо портить пользовательские данные и ставить в них флаг, чтобы отличать новые маркеры от уже созданных.
                  Либо применять подход комментатора выше, где ng-repeat позаботится об этом самостоятельно
                    0
                    Все несколько проще. В этом случае мы просто определяем разность массивов, и удаляем с карты удаленные маркеры, обновляем обновленные и т.д. А ng-repeat при каждом изменении массива будет вызывать полную перерисовку карты.
                      +1
                      Нет, ngRepeat создает и убирает только изменившиеся элементы. В коде есть специальное условие. Так что в этом случае нужно заботиться только об удалении и создании элементов и не делать вот этого
                        0
                        Спасибо. Не знал этого. В будущем поправлю с учетом этого.
                          0
                          Имеем структуру:
                          <div map ... >
                              <point ng-repeat="...">
                          </div>
                          

                          И как потом эту структуру синхронизировать с картой? Все равно чтобы удалить, отредактировать и т.д. что-либо на карте нужно вызывать javascript, все тот же метод synchronize.
                          Или же вы предлагаете напрямую работать с html карты?
                            +2
                            У меня так:

                            $scope.on('$destroy", function () {
                                  map.removeMarker(marker);
                            });
                            


                            Когда ngRepeat удалит элемент (и его скоуп), вызовется соответствующий колбэк. У вас же самописный монстр synchronize, который дублирует фактически реализацию ngRepeat.

                            Кстати, не понимаю, почему не вынесут код из ngRepeat в сервис — функция непростая, а следить глубоко за массивами приходится иногда, писать свое решение как-то глупо.
                              0
                              Полностью с вами согласен. От всего этого уже потихоньку избавляюсь. Очень ваши советы с justboris-ом помогли. Действительно я там много сделал того, что можно реализовать встроенными средствами angular. Думаю сегодня в репозитарии появиться новая реализация. Она пока будет поддерживать только показ данных (без редактирования).
                                0
                                Лучше не сюда…
                    0
                    Такая вложенность еще дает прекрасную возможность для взаимодействия point-ов с map-ом. И вообще любого взаимодействия вложенных элементов с родителем. В данном случае можно опубликовать любой интерфейс (положить любые методы на this контроллера map-а), а потом в point просто потребовать этот контроллер (require: '^map') и взаимодействовать напрямую.
                      0
                      Бесспорно. И не нужно велосипед изобретать (хотя это уже свершилось :) )
                  0
                  Я всегда недооценивал Angular-разработчиков и очень хочу, чтобы кто-нибудь это исправил.
                  Вот, например, кусок из вашего демо-кода.
                  Расскажете, пожалуйста, что тут происходит.

                  $scope.changeCenter = function(){
                  	var currentCenter = $scope.mapProperties.params.center;
                  	if(!currentCenter){
                  		$scope.mapProperties.params.center = {x:37.64, y:55.76};
                  	}else if(currentCenter.x && currentCenter.y){
                  		$scope.mapProperties.params.center='Казахстан, город Алматы';
                  	}else if(currentCenter.length===2){
                  		$scope.mapProperties.params.center = null;
                  	}else if(currentCenter==='Казахстан, город Алматы'){
                  		$scope.mapProperties.params.center = [37.64,55.76];
                  	}
                  };
                  
                    –1
                    Это просто демонстрация программной установки центра карты. В зависимости от того, который раз нажимается кнопка, устанавливается центр карты.
                      +1
                      мне показалось что это была ирония на тему «else if»
                        0
                        В таком случае это то же, что и:
                        var centers = [...], counter = 0;
                        $scope.changeCenter = function(){
                        $scope.mapProperties.params.center = centers[counter++%4];
                        };
                    0
                    Как с API v.2.1.x работет? Не пробовали?
                      0
                      Сейчас доделываю. Будет доступно через день, максимум два.
                      0
                      А что, исходники закрытые?
                        0
                        Кому интересно, нашел исходник здесь view-source:http://tulov-alex.ru/2.1/

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

                      Самое читаемое