Визуализация данных высокой плотности
Данные высокой плотности (High-density data) - это тип набора данных, который имеет большое количество точек данных в определенной пространственной или временной области.
Большие и плотные наборы данных могут быть сложны для эффективной визуализации. Существует несколько методов, которые можно использовать для визуализации данных высокой плотности более содержательным образом. Рассмотрим эти методы в следующих разделах.
Кластеризация
Кластеризация - это метод группировки объектов в слое на основе их близости друг к другу. Обычно размер каждого кластера пропорционален количеству объектов, содержащихся в нем. Это может быть полезно для отображения областей, где многие точки расположены близко друг к другу.
 
Кластеризация настраивается на источнике кластеров (cluster source), связанном с векторным слоем. Источник кластеров должен включать либо вложенный DataVectorSource, либо VectorSource.
Например, для кластеризации объектов из контейнера данных в XML выполните следующие шаги:
- 
Включите векторный слой в компонент geoMap. 
- 
Добавьте ClusterSourceв этот слой.
- 
Настройте DataVectorSourceдля кластера следующим образом:<maps:geoMap id="geoMap" width="100%" height="100%"> <maps:layers> <maps:tile> <maps:osmSource/> </maps:tile> <maps:vector id="vectorLayer"> <maps:cluster> <maps:dataVectorSource dataContainer="locationsDc" property="building"/> </maps:cluster> </maps:vector> </maps:layers> </maps:geoMap>
Ниже мы опишем ключевые аспекты кластеризации.
ClusterSource
ClusterSource - это источник, который обеспечивает кластеризацию объектов на карте. Он требует дополнительный источник для поставки объектов, которым может быть либо:
- 
DataVectorSource
- 
VectorSource
Для кластеризации объектов без привязки к данным используйте VectorSource:
<maps:geoMap id="map"
             width="100%"
             height="100%">
    <maps:layers>
        <maps:tile>
            <maps:osmSource/>
        </maps:tile>
        <maps:vector id="vector">
            <maps:cluster>
                <maps:vectorSource/>
            </maps:cluster>
        </maps:vector>
    </maps:layers>
</maps:geoMap>Атрибуты кластера
Кластер имеет следующие атрибуты:
- 
attributions- параметр, определяющий текстовую информацию об авторстве и источниках данных (credits).
- 
disableAtZoomустанавливает уровень масштабирования, при котором кластеризация отключается.
- 
distanceуказывает расстояние в пикселях, на котором объекты группируются в кластер.
- 
minDistanceобозначает минимальное расстояние в пикселях между кластерами (для получения более подробной информации обратитесь к документации OSM).
- 
showSinglePointAsClusterотображает одиночную точку как кластер размера 1.
- 
weightProperty- если указан, каждая точка в слое будет иметь целочисленное значение веса на основе свойстваweightгеообъекта. Это значение помогает рассчитать совокупное значение кластера (по умолчанию используется количество точек).
- 
wrapXопределяет, должна ли карта бесшовно повторяться по горизонтали.
Свойство веса
По умолчанию кластер вычисляет количество точек внутри себя, которое затем отображается на маркере кластера. weightProperty позволяет нам указать свойство, которое кластер должен использовать для расчета.
Например, проект включает JPA-сущность:
@JmixEntity
@Table(name = "LOCATION")
@Entity
public class Location {
    @Column(name = "BUILDING")
    private Point building; (1)
    @Column(name = "WEIGHT")
    private Integer weight; (2)
    // getters and setters
}| 1 | Атрибут buildingсодержит координаты, размещающие здание на карте. | 
| 2 | Атрибут weightсодержит числовое значение, которое предоставляет информацию о здании. | 
В XML-дескрипторе мы настраиваем конфигурацию для кластера:
<data>
    <collection id="locationsDc" class="com.company.mapssample.entity.Location">
        <loader id="locationsDl" readOnly="true">
            <query>
                <![CDATA[select e from Location e]]>
            </query>
        </loader>
        <fetchPlan extends="_base"/>
    </collection>
</data>
<facets>
    <dataLoadCoordinator auto="true"/>
</facets>
<layout>
    <maps:geoMap id="locationsMap"
                 width="100%"
                 height="100%">
        <maps:layers>
            <maps:tile>
                <maps:osmSource/>
            </maps:tile>
            <maps:vector id="vectorL">
                <maps:cluster weightProperty="weight">
                    <maps:dataVectorSource dataContainer="locationsDc"
                                           property="building"/>
                </maps:cluster>
            </maps:vector>
        </maps:layers>
    </maps:geoMap>
</layout>Стилизация значка кластера
Значки кластеров можно настраивать с помощью Java API. Существует два метода стилизации:
- 
Введение нового стиля, который перекрывает стиль по умолчанию. @ViewComponent("locationMap.vectorLr.cluster") public ClusterSource cluster; @Subscribe public void onInit(final InitEvent event) { cluster.addPointStyles(new Style() .withImage(new CircleStyle() .withRadius(15) .withFill(new Fill("#7F70D8")))); }  
- 
Удаление стиля по умолчанию и добавление полностью нового стиля. @ViewComponent("styledMap.vectLr.clusterS") public ClusterSource clusterS; @Subscribe public void onInit(final InitEvent event) { clusterS.removeAllPointStyles(); clusterS.addPointStyles( new Style() .withImage(new CircleStyle() .withRadius(20) .withFill(new Fill("rgba(163, 104, 213, 0.6)"))), new Style() .withImage(new CircleStyle() .withRadius(15) .withFill(new Fill("rgba(144, 64, 213, 1)")))); }  
Чтобы изменить стиль текста, используйте метод setPointTextStyle():
@ViewComponent("textStyleMap.vctLayer.clusterSource")
public ClusterSource clusterSource;
@Subscribe
public void onInit(final InitEvent event) {
    clusterSource.setPointTextStyle(new Style()
            .withText(new TextStyle()
                    .withFont("13px sans-serif")
                    .withOffsetY(1)
                    .withFill(new Fill("#FAAF22"))));
} 
Установка свойства для объекта
Объекты (Features) имеют возможность определять свойства, которые будут связаны с этими объектами.
Если кластер указывает свойство веса, он попытается получить его из свойств объектов.
Рассмотрим следующий пример:
<maps:geoMap id="featuresMap"
             height="100%"
             width="100%">
    <maps:layers>
        <maps:tile>
            <maps:osmSource/>
        </maps:tile>
        <maps:vector id="pointsLayer">
            <maps:cluster id="pointClusterSource"
                          weightProperty="weight" >
                <maps:vectorSource id="source"/>
            </maps:cluster>
        </maps:vector>
    </maps:layers>
</maps:geoMap>@ViewComponent("featuresMap.pointsLayer.pointClusterSource.source")
public VectorSource source;
@Subscribe
public void onInit(final InitEvent event) {
    source.addAllFeatures(generatePoints());
}
private List<Feature> generatePoints() {
    List<Feature> features = new ArrayList<>(10000);
    int e = 45;
    for (int i = 0; i < 10000; i++) {
        Point point = GeometryUtils.createPoint(2 * e * Math.random(),
                2 * e * Math.random() - e);
        features.add(
                new PointFeature(point)
                        .withProperty("weight",
                                Double.valueOf(Math.random() * 10).intValue()));
    }
    return Collections.unmodifiableList(features);
}Тепловая карта
Тепловая карта (Heatmap) отображает точечные объекты как растровую поверхность, выделяя области с более высокой концентрацией точек вдоль непрерывного цветового градиента.
Теплые цвета (такие как красный или оранжевый) указывают на области с более высокими значениями, в то время как холодные цвета (синий или зеленый) указывают на более низкие значения.
 
Тепловые карты настраиваются на слое HeatmapLayer. Этот слой отображает данные в виде тепловых точек. HeatmapLayer работает с HeatmapDataVectorSource и VectorSource.
- 
HeatmapDataVectorSourceиспользуется для привязки к контейнерам данных (сущностям или DTO). Атрибутpropertyдолжен ссылаться на свойство сущности, имеющее типPoint.
- 
VectorSourceследует использовать, когда нет необходимости в привязке к контейнерам данных.
Например, чтобы добавить тепловую карту, связанную с контейнером данных, выполните следующие шаги:
- 
Включите слой тепловой карты в компонент geoMap: выберите элемент geoMapна панели структуры Jmix UI или в XML-дескрипторе экрана, затем нажмите кнопку Add на панели инспектора. В выпадающем списке выберите Layers → HeatmapLayer.  
- 
Добавьте HeatmapDataVectorSourceв этот слой. Настройте этот источник следующим образом:<data> <collection id="locationsDc" class="com.company.mapssample.entity.Location"> <loader id="locationsDl" readOnly="true"> <query> <![CDATA[select e from Location e]]> </query> </loader> <fetchPlan extends="_base"/> </collection> </data> <layout> <maps:geoMap id="defaultMap" width="100%" height="100%"> <maps:layers> <maps:tile> <maps:osmSource/> </maps:tile> <maps:heatmap blur="20" radius="15"> <maps:heatmapDataVectorSource dataContainer="locationsDc" property="building"/> (1) </maps:heatmap> </maps:layers> </maps:geoMap> </layout>1 Определите свойство из сущности Location, имеющее типPoint.
Ниже мы опишем ключевые аспекты тепловых карт.
Атрибуты тепловой карты
Тепловая карта имеет следующие атрибуты:
- 
Атрибут weightProperty указывает свойство сущности, из которого следует извлекать вес (интенсивность), или свойство из Feature.Если свойство не указано, будет использовано максимальное доступное значение веса. 
- 
maxWeight- указывает максимальное значение интенсивности в диапазоне от0.0до1.0.
- 
blurзадает размер размытия точки в пикселях.
- 
radiusзадает радиус точки в пикселях.
- 
gradient- цветовой градиент точки, который должен состоять из CSS-цветов, разделенных запятыми.
Дополнительно включены базовые свойства слоя: className, maxZoom, minZoom, opacity, visible, zIndex
Взгляните на пример:
<maps:heatmap weightProperty="intensity"
              maxWeight="0.5"
              blur="20"
              radius="20"
              gradient="#32CD32, #00FF00, #7CFC00, #7FFF00, #ADFF2F">
    <maps:heatmapDataVectorSource dataContainer="locationsDc"
                                  property="building"/>
</maps:heatmap>Свойство веса
Свойство веса (weightProperty) - это свойство, определяющее интенсивность или вес точки, отображаемой на тепловой карте. Оно должно быть указано либо в Feature, либо в сущности. Значения этого свойства должны находиться в диапазоне от 0.0 до 1.0 и иметь тип Double.
Если значение weightProperty не задано явно, обычно используется значение по умолчанию 1 для представления максимального уровня интенсивности для точек тепловой карты.
Примеры
Тепловая карта без привязки к данным
VectorSource используется, когда нет необходимости в привязке к контейнерам данных.
В примере ниже карта создается и первоначально настраивается с помощью XML.
<maps:geoMap id="map"
             width="100%"
             height="100%">
    <maps:layers>
        <maps:tile>
            <maps:osmSource/>
        </maps:tile>
        <maps:heatmap id="heatmap"
                      blur="10"
                      radius="15"
                      weightProperty="weight"> (1)
            <maps:vectorSource id="vectorSource"/> (2)
        </maps:heatmap>
    </maps:layers>
</maps:geoMap>| 1 | Установите свойство, которое будет связано с Feature. | 
| 2 | Укажите idдля векторного источника, что позволит инжектировать его в контроллер. | 
После этого дополнительные свойства задаются программно в Java-контроллере:
@ViewComponent("map.heatmap.vectorSource") (1)
private VectorSource vectorSource;
@Subscribe
public void onInit(final InitEvent event) {
    PointFeature feature = new PointFeature(GeometryUtils.createPoint(10, 10));
    feature.withProperty("weight", 1); (2)
    vectorSource.addFeature(feature);
}| 1 | Инжектируйте vectorSourceв класс контроллера. | 
| 2 | Передайте значение weightв метод, которое мы ранее указали для элементаheatmapв XML-дескрипторе. | 
Тепловая карта, связанная с данными
Например, проект включает JPA-сущность:
@JmixEntity
@Table(name = "LOCATION")
@Entity
public class Location {
    @Column(name = "BUILDING")
    private Point building; (1)
    @Column(name = "INTENSITY")
    private Double intensity; (2)
    // getters and setters
}| 1 | Атрибут buildingсодержит координаты, размещающие здание на карте. | 
| 2 | Атрибут intensityсодержит числовое значение, которое предоставляет некоторую дополнительную информацию. | 
В XML-дескрипторе мы настраиваем конфигурацию для тепловой карты:
<data>
    <collection id="locationsDc"
                class="com.company.mapssample.entity.Location">
        <loader id="locationsDl" readOnly="true">
            <query>
                <![CDATA[select e from Location e]]>
            </query>
        </loader>
        <fetchPlan extends="_base"/>
    </collection>
</data>
<layout>
    <maps:geoMap id="geoMap"
                 width="100%"
                 height="100%">
        <maps:layers>
            <maps:tile>
                <maps:osmSource/>
            </maps:tile>
            <maps:heatmap blur="10"
                          radius="15"
                          weightProperty="intensity"> (1)
                <maps:heatmapDataVectorSource dataContainer="locationsDc"
                                              property="building"/>
            </maps:heatmap>
        </maps:layers>
    </maps:geoMap>
</layout>| 1 | Укажите в атрибуте weightPropertyзначение свойства сущности, содержащего значение интенсивности точки. | 
Тепловая карта с WeightProvider
HeatmapDataVectorSource имеет специальный метод для случаев, когда у сущности/DTO нет свойства веса или его необходимо вычислить каким-либо другим способом. В этих случаях не обязательно указывать weightProperty, так как вместо него будет использоваться провайдер.
В примере ниже карта создается и первоначально настраивается с помощью XML.
<maps:geoMap id="locationMap"
             width="100%"
             height="100%">
    <maps:layers>
        <maps:tile>
            <maps:osmSource/>
        </maps:tile>
        <maps:heatmap id="heatmapLayer"
                      weightProperty="intensity"
                      blur="15"
                      radius="20">
            <maps:heatmapDataVectorSource
                    id="heatmapDataVectorSource"
                    dataContainer="locationsDc"
                    property="building"/>
        </maps:heatmap>
    </maps:layers>
</maps:geoMap>После этого мы настроим провайдер веса для heatmapDataVectorSource в Java-контроллере:
@ViewComponent("locationMap.heatmapLayer.heatmapDataVectorSource")
private HeatmapDataVectorSource<Location> heatmapDataVectorSource; (1)
@Subscribe
public void onInit(final InitEvent event) {
    heatmapDataVectorSource.setWeightProvider(location -> { (2)
        int weight = location.getWeight();
        if (weight > 50) {
            return 0.7;
        }
        return 0.3;
    });
}| 1 | Обратите внимание: heatmapDataVectorSourceпараметризован. | 
| 2 | Провайдер вызывается для каждого элемента и должен возвращать вычисленное значение веса. |