Source: modules/Popup.js

/**
 * @module modules/Popup.js
 * @name Popup
 * @copyright 2023 3Liz
 * @license MPL-2.0
 */


import Overlay from 'ol/Overlay.js';
import WMS from '../modules/WMS.js';
import Utils from '../modules/Utils.js';

/**
 * @class
 * @name Popup
 */
export default class Popup {

    /**
     * Create a popup instance
     *
     * @param {Config} initialConfig - The lizmap initial config instance
     * @param {State}  lizmapState   - The lizmap user interface state
     * @param {Map}    map           - OpenLayers map
     * @param {Digitizing} digitizing - The Lizmap digitizing instance
     */
    constructor(initialConfig, lizmapState, map, digitizing) {

        this._initialConfig = initialConfig;
        this._lizmapState = lizmapState;
        this._map = map;
        this._digitizing = digitizing;

        this._pointTolerance = initialConfig.options?.pointTolerance || 25;
        this._lineTolerance = initialConfig.options?.lineTolerance || 10;
        this._polygonTolerance = initialConfig.options?.polygonTolerance || 5;

        // Allow toggling of active state
        this.active = true;

        // OL2
        OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
            defaultHandlerOptions: {
                'single': true,
                'double': false,
                'pixelTolerance': 10,
                'stopSingle': false,
                'stopDouble': false
            },
            initialize: function () {
                this.handlerOptions = OpenLayers.Util.extend(
                    {}, this.defaultHandlerOptions
                );
                OpenLayers.Control.prototype.initialize.apply(
                    this, arguments
                );
                this.handler = new OpenLayers.Handler.Click(
                    this, {
                        'click': this.trigger
                    }, this.handlerOptions
                );
            },
            trigger: evt => {
                this.handleClickOnMap(evt);
            }
        });

        var click = new OpenLayers.Control.Click();
        lizMap.map.addControl(click);
        click.activate();

        // OL8
        /**
        * Create an overlay to anchor the popup to the map.
        */
        document.getElementById('liz_layer_popup_closer').onclick = () => {
            this._overlay.setPosition(undefined);
            this._map.clearHighlightFeatures();
            return false;
        };
        this._overlay = new Overlay({
            element: document.getElementById('liz_layer_popup'),
            autoPan: {
                animation: {
                    duration: 250,
                },
            },
        });

        this._map.addOverlay(this._overlay);
        this._map.on('singleclick', evt => this.handleClickOnMap(evt));
    }

    get mapPopup() {
        return this._overlay;
    }

    handleClickOnMap(evt) {
        const pointTolerance = this._pointTolerance;
        const lineTolerance = this._lineTolerance;
        const polygonTolerance = this._polygonTolerance;

        if (!this.active || lizMap.editionPending || this._digitizing.toolSelected != 'deactivate' || this._digitizing.isEdited || this._digitizing.isErasing) {
            return;
        }

        const xCoord = evt?.xy?.x || evt?.pixel?.[0];
        const yCoord = evt?.xy?.y || evt?.pixel?.[1];

        // Order popups following layers order
        let candidateLayers = this._lizmapState.rootMapGroup.findMapLayers().toSorted((a, b) => b.layerOrder - a.layerOrder);

        // Only request visible layers
        candidateLayers = candidateLayers.filter(layer => layer.visibility);

        // Only request layers with 'popup' checked in plugin
        // Or some edition capabilities
        candidateLayers = candidateLayers.filter(layer => {
            const layerCfg = layer.layerConfig;

            let editionLayerCapabilities;

            if (this._initialConfig?.editionLayers?.layerNames.includes(layer.name)) {
                editionLayerCapabilities = this._initialConfig?.editionLayers?.getLayerConfigByLayerName(layer.name)?.capabilities;
            }
            return layerCfg.popup || editionLayerCapabilities?.modifyAttribute || editionLayerCapabilities?.modifyGeometry || editionLayerCapabilities?.deleteFeature;
        });

        if(!candidateLayers.length){
            return;
        }

        //const layersWMS = candidateLayers.map(layer => layer.wmsName).join();
        //const layersStyles = candidateLayers.map(layer => layer.wmsSelectedStyleName || "").join()
        const layersNames = [];
        const layersStyles = [];
        const filterTokens = [];
        const legendOn = [];
        const legendOff = [];
        let popupMaxFeatures = 10;
        for (const layer of candidateLayers) {
            const layerWmsParams = layer.wmsParameters;
            // Add layer to the list of layers
            layersNames.push(layerWmsParams['LAYERS']);
            // Optionally add layer style if needed (same order as layers )
            layersStyles.push(layerWmsParams['STYLES']);
            if ('FILTERTOKEN' in layerWmsParams) {
                filterTokens.push(layerWmsParams['FILTERTOKEN']);
            }
            if ('LEGEND_ON' in layerWmsParams) {
                legendOn.push(layerWmsParams['LEGEND_ON']);
            }
            if ('LEGEND_OFF' in layerWmsParams) {
                legendOff.push(layerWmsParams['LEGEND_OFF']);
            }
            if (layer.layerConfig.popupMaxFeatures > popupMaxFeatures) {
                popupMaxFeatures = layer.layerConfig.popupMaxFeatures;
            }
        }

        const wms = new WMS();

        const [width, height] = this._map.getSize();

        let bbox = this._map.getView().calculateExtent();

        if (this._map.getView().getProjection().getAxisOrientation().substring(0, 2) === 'ne') {
            bbox = [bbox[1], bbox[0], bbox[3], bbox[2]];
        }

        const wmsParams = {
            QUERY_LAYERS: layersNames.join(','),
            LAYERS: layersNames.join(','),
            STYLE: layersStyles.join(','),
            CRS: this._map.getView().getProjection().getCode(),
            BBOX: bbox,
            WIDTH: width,
            HEIGHT: height,
            FEATURE_COUNT: popupMaxFeatures,
            I: Math.round(xCoord),
            J: Math.round(yCoord),
            FI_POINT_TOLERANCE: pointTolerance,
            FI_LINE_TOLERANCE: lineTolerance,
            FI_POLYGON_TOLERANCE: polygonTolerance
        };

        if (filterTokens.length) {
            wmsParams.FILTERTOKEN = filterTokens.join(';');
        }
        if (legendOn.length) {
            wmsParams.LEGEND_ON = legendOn.join(';');
        }
        if (legendOff.length) {
            wmsParams.LEGEND_OFF = legendOff.join(';');
        }

        document.getElementById('newOlMap').style.cursor = 'wait';

        wms.getFeatureInfo(wmsParams).then(response => {
            lizMap.displayGetFeatureInfo(Utils.sanitizeGFIContent(response), { x: xCoord, y: yCoord }, evt?.coordinate);
        }).finally(() => {
            document.getElementById('newOlMap').style.cursor = 'auto';
        });
    }
}