Source: layers/heat.js

let AbstractLayer = require('./abstract');

let ol = require('openlayers');
let identifyResult = require('../../modules/tasks/identifyResult');
let searchResult = require('../../modules/tasks/searchResult');
let fuse = require('fuse.js');
let DataSource = require('./../dataSources/dataSource');
let Interaction = require('./../interactions/interaction');
let axios = require('axios');

/**
 * Класс слоя типа HeatLayer - рендеринг производится на стороне клиента на основе сведений о геометрии и атрибутах объектов,
 * а также назначенной функции стилизации (рендерера)
 * Класс предназначен для создания слоев "тепловых карт"
 * @extends AbstractLayer
 */
class HeatLayer extends AbstractLayer {
    /**
     * Создание экземляра слоя типа VectorLayer
     * @param params
     * @param params.url - адрес геосервера
     * @param params.className - имя слоя
     * @param params.radius - радиус окружности от точки
     * @param params.blur - размытие
     * @param params.shadow - размер тени
     * @param params.weight - поле веса (атрибут) или функция
     * @returns {ol.layer.HeatLayer}
     */
    constructor(params) {
        super(params);
        this.initStore(params);
    }

    /**
     * @param params
     * @param params.className - имя класса
     * @param params.mappings - объект, содержащий трансляцию полей (подстановка)
     */
    initStore(params) {
        this.store = {};
        this.store.id = params.id;
        this.store.alias = params.alias;
        this.store.url = params.url;
        this.store.className = params.className;
        this.store.alias = params.alias;
        this.store.layerType = "opengis.layers.vector";
        this.store.mappings = params.mappings || {};
        this.interactions = {};
        this.layer = create(params);
        this.store.layer = this.layer;
    }

    identify(params) {
        let geometry = params.geometry || params.coordinate;
        let radius = params.radius || 20;
        let extent = [geometry[0] - radius, geometry[1] - radius, geometry[0] + radius, geometry[1] + radius];
        let store = this.store;
        let source = store.layer.getSource();

        let features = source.getFeaturesInExtent(extent);
        features = features.map(function (item) {
            let featureAttributes = item.getProperties();
            let featureGeometry = item.getGeometry();
            delete featureAttributes.geometry;

            let resultAttributes = generateField(featureAttributes, store);

            return new identifyResult({
                layerId: store.id,
                layerName: store.alias,
                queryGeometry: geometry,
                attributes: featureAttributes,
                preparedAttributes: resultAttributes,
                geometry: featureGeometry,
                dirtyData: item
            });
        });
        return features;
    }

    /**
     * Создание нового объекта и сохранение в dataSource
     * @param params
     */
    draw(params) {
        let self = this;
        let interactions = self.interactions;

        if (!params) params = {};
        params.source = self.layer.getSource();

        interactions.draw = new Interaction.Draw(params);

        for (let key in interactions) {
            self.map.removeInteraction(interactions[key]);
        }

        self.map.addInteraction(interactions.draw);
        if (params.snap === true) self.snap({active: true});
    }

    modify(params) {
        let self = this;
        let interactions = self.interactions;

        if (!params) params = {};
        params.source = self.layer.getSource();

        interactions.modify = new Interaction.Modify(params);

        for (let key in interactions) {
            self.map.removeInteraction(interactions[key]);
        }

        self.map.addInteraction(interactions.modify);
        if (params.snap === true) self.snap({active: true});
    }

    select(params) {
        let self = this;
        let interactions = self.interactions;

        if (!params) params = {};
        params.source = self.layer.getSource();

        interactions.select = new Interaction.Select(params);

        for (let key in interactions) {
            self.map.removeInteraction(interactions[key]);
        }

        self.map.addInteraction(interactions.select);
        if (params.snap === true) self.snap({active: true});
    }

    remove(params) {
        let self = this;
        let feature = params.feature;
        let mode = params.mode;
        self.layer.getSource().removeFeature(feature);
    }

    /**
     * Включает или выключает привязку к другим объектам слоя
     * @param params
     */
    snap(params) {
        let self = this;

        if (!params) params = {};
        params.source = self.layer.getSource();

        let interaction = new Interaction.Snap(params);

        if (params.active) {
            self.map.addInteraction(interaction);
            self.interactions.snap = interaction;
        }
        if (!params.active) {
            self.map.removeInteraction(self.interactions.snap);
            self.interactions.snap = undefined;
        }
    }

    query(params) {
    }

    /**
     * @param style - объект типа style
     */
    setStyle(style) {
        this.layer.setStyle(style.getStyle());
    }

    getStyle() {
        return this.layer.getStyle();
    }

    /**
     * Выполняет поисковый запрос к слою
     * @param {object} params
     * @param {string} params.query - поисковый запрос, например, ПС-12
     * @param {number} params.minLength - минимальная длина запроса, по умолчанию - 1 символ
     * @returns {*}
     */
    search(params) {
        let query = params.query;
        let minQueryLength = params.minLength === undefined ? 1 : params.minLength;
        let maxResultCount = params.maxResultCount === undefined ? 5 : params.maxResultCount;
        if (query.length < minQueryLength) {
            return [];
        }
        let layer = this.layer;
        let store = this.store;
        let source = layer.getSource();
        let features = source.getFeatures().map(function (item) {
            return item.getProperties();
        });
        let keys = features.length > 0 ? Object.keys(features[0]) : {};
        let task = new fuse(features, {
            caseSensitive: true,
            includeScore: true,
            shouldSort: true,
            threshold: 0.2,
            minMatchCharLength: 1,
            keys: keys
        });
        let result = task.search(query);
        result = result.map(function (element) {
            let geometry = element.item.geometry;
            let resultAttributes = generateField(element.item, store);

            return new searchResult({
                layerId: store.id,
                layerName: store.alias,
                queryGeometry: geometry,
                attributes: element.item,
                preparedAttributes: resultAttributes,
                geometry: geometry,
                dirtyData: new ol.Feature(element.item),
                score: element.score
            })
        });
        result = result.filter(function (item, index) {
            return index < maxResultCount;
        });
        return result;
    }
}

function create(params) {
    let sourceFabric = new DataSource(params);

    //создание векторного cлоя
    let layer = new ol.layer.Heatmap({
        source: sourceFabric.getSource(),
        visible: params.visible || true,
        opacity: params.opacity || 1,
        minResolution: params.minResolution || 0,
        maxResolution: params.maxResolution || 0,
        renderBuffer: params.renderBuffer || 10,
        style: params.style || undefined,
        updateWhileAnimating: params.updateWhileAnimating || false,
        updateWhileInteracting: params.updateWhileInteracting || false,
        blur: params.blur || 10,
        radius: params.radius || 10,
        gradient: params.gradient || ['#00f', '#0ff', '#0f0', '#ff0', '#f00'],
        weight: params.weight || "weight",
        shadow: params.shadow || 250
    });

    layer.setProperties({
        id: params.id,
        className: params.className
    });

    return layer;
}

function generateEmptyMapping(name, value) {
    let mapping = {
        name: name,
        alias: name,
        group: "none",
        value: value
    };
    return mapping;
}

function generateField(featureAttributes, store) {
    let resultAttributes = [];
    for (let key in featureAttributes) {
        let localMapping = store.mappings[key] === undefined ? generateEmptyMapping(key, featureAttributes[key]) : store.mappings[key];
        resultAttributes.push({
            name: key,
            alias: localMapping.alias,
            group: localMapping.group,
            value: featureAttributes[key]
        });
    }
    return resultAttributes;
}

module.exports = HeatLayer;