• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 30619262065

31 Jul 2026 09:15AM UTC coverage: 75.769% (-0.01%) from 75.781%
30619262065

push

github

web-flow
#12650: Zoom to filtered layer from the filter widget (#12707)


---------

Co-authored-by: allyoucanmap <stefano.bovio@geosolutionsgroup.com>

36174 of 56920 branches covered (63.55%)

188 of 279 new or added lines in 19 files covered. (67.38%)

3 existing lines in 2 files now uncovered.

44946 of 59320 relevant lines covered (75.77%)

123.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.85
/web/client/utils/MapWidgetUtils.js
1
/*
2
 * Copyright 2026, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
 */
8
import { MAIN_MAP_CONTAINER_ID } from './MapUtils';
9

10
export const WIDGET_CARD_SELECTOR = '[data-widget-card]';
3✔
11

12
/**
13
 * Returns true when the element is rendered and visible in the DOM.
14
 * @param {HTMLElement} element
15
 * @returns {boolean}
16
 */
17
export const isElementVisible = (element) => {
3✔
18
    if (!element || typeof element.getBoundingClientRect !== 'function') {
30✔
19
        return false;
6✔
20
    }
21
    const rect = element.getBoundingClientRect();
24✔
22
    if (rect.width <= 0 || rect.height <= 0) {
24✔
23
        return false;
6✔
24
    }
25
    const style = window.getComputedStyle(element);
18✔
26
    if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) {
18!
NEW
27
        return false;
×
28
    }
29
    return true;
18✔
30
};
31

32
/**
33
 * Returns true when two bounding rectangles overlap.
34
 * @param {DOMRect} a
35
 * @param {DOMRect} b
36
 * @returns {boolean}
37
 */
38
export const elementsOverlap = (a, b) =>
3✔
39
    a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
18✔
40

41
/**
42
 * Collect visible widget cards overlapping the map container, excluding the
43
 * widget card that contains the map itself.
44
 * @param {HTMLElement} mapContainer
45
 * @param {Document} doc
46
 * @returns {HTMLElement[]}
47
 */
48
export const getVisibleOverlappingWidgetElements = (mapContainer, doc = document) => {
3✔
49
    if (!mapContainer || typeof mapContainer.getBoundingClientRect !== 'function') {
21✔
50
        return [];
3✔
51
    }
52
    return Array.from(doc.querySelectorAll(WIDGET_CARD_SELECTOR))
18✔
53
        .filter((element) => !element.contains(mapContainer))
15✔
54
        .filter(isElementVisible);
55
};
56

57
/**
58
 * Compute dynamic padding from visible widgets obstructing the map.
59
 * Returns the best {top, right, bottom, left} padding by finding the largest
60
 * rectangle free of widget cards.
61
 * @param {HTMLElement} mapContainer
62
 * @param {Document} doc
63
 * @returns {{top: number, right: number, bottom: number, left: number}}
64
 */
65
export const getWidgetPadding = (mapContainer, doc = document) => {
3!
66
    const widgets = getVisibleOverlappingWidgetElements(mapContainer, doc);
15✔
67
    if (!widgets.length) {
15✔
68
        return { top: 0, right: 0, bottom: 0, left: 0 };
3✔
69
    }
70
    const mapRect = mapContainer.getBoundingClientRect();
12✔
71

72
    let regions = [{
12✔
73
        left: mapRect.left,
74
        right: mapRect.right,
75
        top: mapRect.top,
76
        bottom: mapRect.bottom,
77
        width: mapRect.width,
78
        height: mapRect.height
79
    }];
80

81
    widgets.forEach(widget => {
12✔
82
        const widgetRect = widget.getBoundingClientRect();
12✔
83
        const nextRegions = [];
12✔
84

85
        regions.forEach(region => {
12✔
86
            if (elementsOverlap(region, widgetRect)) {
12!
87
                if (widgetRect.left > region.left) {
12✔
88
                    nextRegions.push({ ...region, right: widgetRect.left, width: widgetRect.left - region.left });
6✔
89
                }
90
                if (widgetRect.right < region.right) {
12✔
91
                    nextRegions.push({ ...region, left: widgetRect.right, width: region.right - widgetRect.right });
3✔
92
                }
93
                if (widgetRect.top > region.top) {
12!
NEW
94
                    nextRegions.push({ ...region, bottom: widgetRect.top, height: widgetRect.top - region.top });
×
95
                }
96
                if (widgetRect.bottom < region.bottom) {
12!
NEW
97
                    nextRegions.push({ ...region, top: widgetRect.bottom, height: region.bottom - widgetRect.bottom });
×
98
                }
99
            } else {
NEW
100
                nextRegions.push(region);
×
101
            }
102
        });
103

104
        const filteredRegions = [];
12✔
105
        nextRegions.forEach((nextRegion, i) => {
12✔
106
            let isCovered = false;
9✔
107
            for (let j = 0; j < nextRegions.length; j++) {
9✔
108
                if (i !== j) {
9!
NEW
109
                    const otherRegion = nextRegions[j];
×
NEW
110
                    if (
×
111
                        nextRegion.left >= otherRegion.left
×
112
                        && nextRegion.right <= otherRegion.right
113
                        && nextRegion.top >= otherRegion.top
114
                        && nextRegion.bottom <= otherRegion.bottom
115
                    ) {
NEW
116
                        isCovered = true;
×
NEW
117
                        break;
×
118
                    }
119
                }
120
            }
121
            if (!isCovered) {
9!
122
                filteredRegions.push(nextRegion);
9✔
123
            }
124
        });
125
        regions = filteredRegions;
12✔
126
    });
127

128
    let largestRegion = null;
12✔
129
    let maxArea = -1;
12✔
130

131
    regions.forEach(region => {
12✔
132
        const area = region.width * region.height;
9✔
133
        if (area > maxArea) {
9!
134
            maxArea = area;
9✔
135
            largestRegion = region;
9✔
136
        }
137
    });
138

139
    if (!largestRegion || maxArea <= 0) {
12✔
140
        return { top: 0, right: 0, bottom: 0, left: 0 };
3✔
141
    }
142

143
    return {
9✔
144
        top: Math.max(0, Math.round(largestRegion.top - mapRect.top)),
145
        right: Math.max(0, Math.round(mapRect.right - largestRegion.right)),
146
        bottom: Math.max(0, Math.round(mapRect.bottom - largestRegion.bottom)),
147
        left: Math.max(0, Math.round(largestRegion.left - mapRect.left))
148
    };
149
};
150

151
/**
152
 * Resolve padding for map fit/zoom operations.
153
 * Applies layout and widget padding to determine best zoom bounds.
154
 * @param {HTMLElement} mapContainer
155
 * @param {object} layoutPadding padding from map layout selectors (top/bottom)
156
 * @param {Document} doc
157
 * @returns {{top: number, right: number, bottom: number, left: number}}
158
 */
159
export const resolveZoomToExtentPadding = (mapContainer, layoutPadding, doc = document) => {
3!
160
    const widgetPadding = getWidgetPadding(mapContainer, doc);
3✔
161
    return {
3✔
162
        top: Math.max(layoutPadding?.top || 0, widgetPadding.top),
3!
163
        bottom: Math.max(layoutPadding?.bottom || 0, widgetPadding.bottom),
3!
164
        left: Math.max(layoutPadding?.left || 0, widgetPadding.left),
3!
165
        right: Math.max(layoutPadding?.right || 0, widgetPadding.right)
3!
166
    };
167
};
168

169
/**
170
 * Resolve the zoom padding for the main map.
171
 * @param {object} layoutPadding padding from map layout selectors
172
 * @param {Document} doc
173
 * @returns {object} padding for the main map
174
 */
175
export const getMainMapZoomToPadding = (layoutPadding, doc = document) => {
3!
NEW
176
    const mapContainer = doc.getElementById(MAIN_MAP_CONTAINER_ID);
×
NEW
177
    return mapContainer
×
178
        ? resolveZoomToExtentPadding(mapContainer, layoutPadding, doc)
179
        : layoutPadding;
180
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc