Source: map/filters/ui/BoundingBoxFilter.js

import { FilterUi } from "./FilterUi.js";
import { Map2D } from "../../Map2D.js";
import { debounceCallback, roundNumber } from "../../../utils/utils.js";
import "./BoundingBox.css";

export { BoundingBox };

/**
 * Geographical filter for features within a bounding box.
 * @memberof vef.map.filters.ui
 */
class BoundingBox extends FilterUi {
    classname = "bounding-box-filter";
    html = `
        <div class="bounding-box-area">
            <div class="bounding-box-west">West<br><input type="number"></div>
            <div class="bounding-box-south">South<br><input type="number"></div>
            <div class="bounding-box-east">East<br><input type="number"></div>
            <div class="bounding-box-north">North<br><input type="number"></div>
        </div>
        <div class="bounding-box-button"><button class="edit_bbox">Edit on Map</button></div>`
    ;

    /**
     * @param {HTMLElement | string} target
     * @param {object} options filter specific options
     * @param {LayerCache} layers Used for included/excluded layers
     * @param {Map} map
     */
    constructor(target, options, layers, map) {

        // apply default options
        options = Object.assign({
            title: "Bounding Box",
            applyGlobalFilters: false,
            showNullValues: true,
            values: [-10, -10, 10, 10],
            column: "",
        }, options || {});

        super(target, options, layers, map);

        this.setClass("bounding-box-filter");
        this.setContent(this.html);
        this.setValues(this.options_.values);

        // Buttons
        this.query(".edit_bbox").addEventListener("click", () => this.drawOnMap());
        this.queryAll("input").forEach(input => {
            input.addEventListener("input", debounceCallback(() => this.apply(), 1000));
        });

        // apply at end of event loop, to wait until all filters are added in the viewer
        setTimeout(() => this.apply(), 0);
    }

    drawOnMap() {
        // use leaflet map directly instead of Map class, because rectangle drawing is not handled in vef
        if (!(this.map instanceof Map2D)) return;
        if (this.rectangle) {
            this.rectangle.editing.disable();
            this.rectangle.off();
            this.rectangle.remove();
            this.rectangle = null;
            this.query(".edit_bbox").innerText = "Edit on Map";
            return;
        }

        const style = window.getComputedStyle(document.body);
        this.rectangle = L.rectangle(this.getLeafletBbox(), {
            color: style.getPropertyValue("--primary-color")
        });

        this.rectangle.on('edit', () => {
            const bounds = this.rectangle.getBounds();
            this.options_.values = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
            this.setValues(this.options_.values);
        });

        this.rectangle.editing.enable();
        const map = this.map.leafletMap_;
        map.addLayer(this.rectangle);
        this.query(".edit_bbox").innerText = "Stop Editing on Map";
    }

    getLeafletBbox() {
        const bbox = this.options_.values;
        return [[bbox[1], bbox[0]], [bbox[3], bbox[2]]];
    }

    /**
     * @param {number[]} values [West, South, East, North]
     */
    setValues(values) {
        const inputs = this.queryAll("input");
        for (let index = 0; index <= 3; index++) {
            inputs[index].value = roundNumber(values[index], 3, false);
        }
        this.fire("change", this);
    }

    getValuesChange() {
        const arr = [];
        const inputs = this.queryAll("input");
        for (let index = 0; index < inputs.length; index++) {
            arr[index] = inputs[index].value.trim();
            arr[index] = parseInt(arr[index])
        }
        return arr;
    }

    getActiveFilter() {
        const values = this.options_.values;
        return {
            type: "geometry",
            column: this.options_.column,
            values: [["bbox", values[1], values[0], values[3], values[2]]],
            excludedLayers: this.getExcludedLayers()
        }
    }

    apply() {
        this.options_.values = this.getValuesChange();

        if (this.rectangle) {
            this.rectangle.editing.disable();
            this.rectangle.setBounds(this.getLeafletBbox());
            this.rectangle.editing.enable();
        }

        this.fire("change", this);
    }
}