Статьи

Анимированная стратегия маркеров кластеров для OpenLayers

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

Далее вы можете найти описание того, как работает реализация. Я старался быть максимально совместимым с OpenLayers, не используя никаких других фреймворков, библиотек или классов вне OpenLayers и не следуя его концепции.

Необходимо сделать краткое введение в понятия OpenLayers, такие как векторные слои и стратегии, чтобы читатель мог глубже понять реализацию.

ПРИМЕЧАНИЕ: он был протестирован только с OpenLayers 2.11 !!!

Некоторый фон

Много лет назад (в конце 2011 года) в stackoverflow я видел большой вопрос о том, как создавать маркеры анимированных кластеров в OpenLayers / Leaflet? и, также, похожий на OpenLayers, хорошая кластеризация маркеров .

Все знают, что картинка стоит тысячи слов, и при работе с визуализацией данных это не исключение. Важно не только внешний вид, но и ощущение приятной анимации функций при изменении уровня масштабирования, что визуально забавно и привлекательно.

В те дни я был полностью занят написанием книги « Поваренная книга  OpenLayers» . Вначале я думаю, что нужно приложить усилия и попытаться реализовать решение в OpenLayers, чтобы оно могло послужить еще одним рецептом для книги. К сожалению, у меня не было много времени, чтобы потратить на это испытание, и я забыл, чтобы сосредоточиться на написании книги с кучей более практичных рецептов.

Месяц назад Дейв Ливер отвечает на форуме, указывая на его удивительную кластерную реализацию маркера для Leaflet, которая, я должен сказать, потрясающая.

К счастью для сообщества, анимированный кластер для Leaflet был похож на мою задницу, это было то, что мне нужно, чтобы начать испытание :)

На прошлой неделе я начал работать над реализацией для OpenLayers. Потратив немного времени на это и после некоторых попыток и ошибок, сегодня я могу опубликовать исходный код и документацию в дополнение к этому посту в массы.

Краткое описание класса Vector Layer

В OpenLayers, когда вы хотите визуализировать любой объект на карте, вам необходимо предварительно создать векторный слой:

var vector = new OpenLayers.Layer.Vector("Features");

 

Векторные слои являются одним из самых сложных и мощных слоев в OpenLayers, и поэтому существует огромная экосистема классов, связанных с векторными слоями:

Приблизительная концептуальная модель

Особенности и геометрия

Векторный слой — это не что иное, как контейнер для объектов, которые являются наблюдениями в реальном мире: город, река, горная вершина, страна, континент, … все это объекты. Кроме того, функция может иметь набор атрибутов, таких как: население, длина, высота и т. Д.

Геометрия иерархии

Каждый объект может быть визуализирован по-своему, и этот способ определяется геометрией, связанной с объектом. Точки, LineString, Polygons, … — это различные виды геометрии, которые мы можем использовать для визуализации объекта.

// This code adds a point feature to a vector layer
var pointGeometry = new OpenLayers.Geometry.Point(px, py);
var pointFeature = new OpenLayers.Feature.Vector(pointGeometry);
vector.push([pointFeatures]);

Рендереры и стили

Браузеры могут поддерживать разные технологии: Canvas , SVG и т. Д. Поэтому, чтобы сделать процесс рисования геометрии на карте независимым от векторного слоя, в OpenLayers есть концепция рендерера. Рендерер — это особый класс, отвечающий за рисование геометрии с использованием конкретной технологии.

Следующий код создает новый векторный слой, в котором мы указываем, что OpenLayers пытается визуализировать объекты с использованием возможностей Canvas вначале или иным образом с использованием технологии SVG.

var vector = new OpenLayers.Layer.Vector("Features", {
    renderers: ['Canvas','SVG']
});

Кроме того, объект (или сам векторный слой) может иметь связанный стиль, который используется средством визуализации для рисования геометрии, представляющей объект. Таким образом, мы можем отобразить города в виде синих точек с радиусом 10px, а горные вершины — в виде коричневых точек с радиусом 7px.

Протоколы и форматы

С векторным уровнем может быть связан класс протокола. Протоколы — это специальные классы, которые знают, как читать / писать функции из разных источников, и используют классы Format для чтения / записи данных в определенном формате. Например: мы можем загрузить объекты, используя протокол HTTP из файла GeoJSON или KML.

Протоколы и классы форматирования делают векторный слой независимым от источника данных.

var vector = new OpenLayers.Layer.Vector("Features", {
    protocol: new OpenLayers.Protocol.HTTP({
        url: "world_cities.json",
        format: new OpenLayers.Format.GeoJSON()
    })
});

Предыдущий код создает новый векторный слой, который загружает данные через HTTP из файла формата GeoJSON.

Стратегии

К векторному слою может быть прикреплена одна или несколько стратегий. Стратегия — это своего рода класс, который после активации заставляет  вещи  на векторном слое без слоя знать, что происходит.

Примеры стратегий:

  • OpenLayers.Strategy.Fixed, который загружает содержимое источника данных, присоединенного к векторному слою, только один раз. Это обычно используется для загрузки файла данных в векторный слой, потому что его нужно загружать только один раз.
  • OpenLayers.Strategy.Box, который загружает контент из источника данных каждый раз, когда изменяется ограничивающая рамка области просмотра. Эта стратегия полезна для векторных слоев, которые загружают контент с сервера WFS и должны обновлять его каждый раз при изменении BBOX.
var vector = new OpenLayers.Layer.Vector("Features", {
    protocol: new OpenLayers.Protocol.HTTP({
        url: "world_cities.json",
        format: new OpenLayers.Format.GeoJSON()
    }),
    strategies: [new OpenLayers.Strategy.Fixed()]
});

 

Предыдущий код создает векторный слой, который загружает данные из файла GeoJSON. Стратегия Fixed позволяет загружать содержимое файла один раз и только один раз, без изменений, если изменяется область просмотра BBOX.

Кластерная стратегия

Когда векторный слой содержит множество объектов и они отображаются в виде точек, на нашей карте может появиться безобразный эффект. В зависимости от уровня масштабирования и радиуса точки, точки могут быть наложены друг на друга.

Одной из наиболее полезных стратегий, предлагаемых OpenLayers, является OpenLayers.Strategy.Cluster. Учитывая набор объектов в слое и уровень масштабирования, эта стратегия вычисляет расстояние между объектами и группирует их, чтобы ни один кластер не перекрывал другие.

На следующих рисунках показан пример без применения кластерной стратегии и для нее с набором объектов в векторном слое:

Без кластерной стратегии С кластерной стратегией

Код, необходимый для второго рисунка:

var vector = new OpenLayers.Layer.Vector("Features", {
    protocol: new OpenLayers.Protocol.HTTP({
        url: "world_cities.json",
        format: new OpenLayers.Format.GeoJSON()
    }),
    strategies: [
        new OpenLayers.Strategy.Fixed(),
        new OpenLayers.Strategy.Cluster()
    ]
});

How the cluster strategy works?

The cluster strategy instance holds a reference to the vector layers features. In addition each time the zoom level changes, it computes a set of clusters each one containing some number of features.

The clusters are nothing more than point geometry features with a new count attribute (an holding references to the features it contains).

In addition the cluster strategy, removes the “original” set of features from the vector layers and adds the computed clusters to it.

So (IMO)… What is the problem with the cluster strategy?

Really there is no problem with cluster strategy, it works like a charm. From my point of view the problem is related with the user experience, more concretely to the look and feel.

IMO, the ugly effect with cluster strategy is done when the user changes the zoom level of the map. The clusters are computed again and they are drawn fine but without a nice animation that indicates the user the new cluster division or merge.

Once can think no matter the look and feel of a bunch features on the screen, but I think if we can have a good functionality with a nice experience that is twice better than only as good functionality.

How to use the AnimatedCluster

The OpenLayers.Strategy.AnimatedCluster class is a subclass of OpenLayers.Strategy.Cluster class so it inherits all the properties and extends some features.
The usage is as easy as the cluster strategy. When you create a vector layer you need to pass an instance of OpenLayers.Strategy.AnimatedCluster in the strategies property:

 

var vector = new OpenLayers.Layer.Vector("Features", {
    ...
    strategies: [
        new OpenLayers.Strategy.Fixed(),
        new OpenLayers.Strategy.AnimatedCluster({
            distance: 45,
            animationMethod: OpenLayers.Easing.Expo.easeOut,
            animationDuration: 10
        })
    ],
    ...
});

Properties

In addition to the inherited properties distance and threshold the AnimatedCluster offers a two new ones:

  • animationMethod: The tweening method to use. By default if is OpenLayers.Easing.Expo.easeOut.
  • animationDuration: The duration of the tween in number of steps. By default it is 20.

How the AnimatedCluster works?

Like its parent class the animated cluster strategy holds a reference to the features of the vector layer and each the zoom level changes the next steps takes place:

  • Store a reference to the previous computed cluster (that related with the previous zoom level we come from)
  • Compute the new cluster
  • Start a tween to animate from the previous to the new cluster positions.

These means animated cluster makes use of an extra array of clustered features (the previous one) but its size is relatively small, so there is no performance problem. In addition and, in the same way the cluster strategy does, the animated cluster only the computes the new cluster

The animation is a bit cumbersome. If we zoom in the view, it means we go to a level with  more clusters than the previous one, that is, a cluster at level N becomes M clusters at level N+1.

The vice-versa occurs for zoom out actions, we go to a new level with less clusters than the previous one, that is, M clusters at level N becomes 1 cluster at level N-1.

Things to take into account with the AnimatedCluster

There are two important things to understand and take into account when working with the AnimateCluster.

First, the animation is made using the OpenLayers.Tween class OpenLayers offers. This class is used to make animation when map panning and offers basic actions to make animations.

Unfortunately, the class does not allows specify the animation duration in second but in steps. You must know that the OpenLayers.Tween wait 10 milliseconds between steps (see documentation for the OpenLayers.Tween.INTERVAL property), so a duration of 50 steps means the animation is made in 500 milliseconds.

Second, each animation steps implies to redraw the vector layer again, to refresh the new position of the clusters. As you can see this has a direct performance impact.

Conclusions

To be the first implementation I proudly enough of the work but I am aware of the improvements it can be made.
Any help will be appreciated.