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

geosolutions-it / MapStore2 / 12831531306

17 Jan 2025 03:01PM UTC coverage: 77.182% (+0.07%) from 77.115%
12831531306

Pull #10746

github

web-flow
Merge 501dbaeea into 4e4dabc03
Pull Request #10746: Fix #10739 Changing correctly resolutions limits when switching map CRS

30373 of 47156 branches covered (64.41%)

34 of 43 new or added lines in 2 files covered. (79.07%)

126 existing lines in 15 files now uncovered.

37769 of 48935 relevant lines covered (77.18%)

35.14 hits per line

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

97.47
/web/client/utils/MapViewsUtils.js
1
/*
2
 * Copyright 2022, 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 { isUndefined, omitBy, isNumber, isObject } from 'lodash';
9

10
const METERS_PER_DEGREES = 111194.87428468118;
1✔
11

12
export const MAP_VIEWS_CONFIG_KEY = 'mapViews';
1✔
13
export const MAP_VIEWS_LAYERS_OWNER = 'MAP_VIEWS_LAYERS_OWNER';
1✔
14
export const DefaultViewValues = {
1✔
15
    DURATION: 10,
16
    TRANSLUCENCY_NEAR_DISTANCE: 500,
17
    TRANSLUCENCY_FAR_DISTANCE: 5000,
18
    TRANSLUCENCY_OPACITY: 0.5,
19
    MASK_OFFSET: 10000
20
};
21
export const ViewSettingsTypes = {
1✔
22
    DESCRIPTION: 'description',
23
    POSITION: 'position',
24
    ANIMATION: 'animation',
25
    MASK: 'mask',
26
    GLOBE_TRANSLUCENCY: 'globeTranslucency',
27
    LAYERS_OPTIONS: 'layersOptions'
28
};
29

30
const getFeatureFromBbox = (bbox, offset = 0) => {
1!
31
    const [minx, miny, maxx, maxy] = bbox;
2✔
32
    const offsetDeg = offset / METERS_PER_DEGREES;
2✔
33
    const oMinx = minx - offsetDeg;
2✔
34
    const oMiny = miny - offsetDeg;
2✔
35
    const oMaxx = maxx + offsetDeg;
2✔
36
    const oMaxy = maxy + offsetDeg;
2✔
37
    return {
2✔
38
        type: 'Feature',
39
        geometry: {
40
            type: 'Polygon',
41
            coordinates: [
42
                [
43
                    [oMinx, oMiny],
44
                    [oMinx, oMaxy],
45
                    [oMaxx, oMaxy],
46
                    [oMaxx, oMiny],
47
                    [oMinx, oMiny]
48
                ]
49
            ]
50

51
        },
52
        properties: {}
53
    };
54
};
55
/**
56
 * create an inverse GeoJSON layer given a GeoJSON input with Polygon or MultiPolygon geometries
57
 * @param {object} collection feature collection
58
 * @param {object} options available options
59
 * @param {number} options.offset offset in meter
60
 */
61
export const createInverseMaskFromPolygonFeatureCollection = (collection, { offset = 0 } = {}) => {
1!
62
    return Promise.all([
2✔
63
        import('@turf/difference'),
64
        import('@turf/bbox')
65
    ])
66
        .then(([modDifference, modBbox]) => {
67
            const turfDifference = modDifference.default;
2✔
68
            const turfBbox = modBbox.default;
2✔
69
            const bbox = turfBbox(collection);
2✔
70
            const bboxPolygon = getFeatureFromBbox(bbox, offset);
2✔
71
            const features = [
2✔
72
                bboxPolygon,
73
                ...collection.features
74
                    .filter(({ geometry }) => geometry?.type === 'Polygon' || geometry?.type === 'MultiPolygon')
2!
75
            ];
76
            const difference = features.length > 1
2!
77
                ? features.reduce((previous, current) => turfDifference(previous, current))
2✔
78
                : bboxPolygon;
79
            return {
2✔
80
                type: 'FeatureCollection',
81
                features: [difference].filter(feature => !!feature)
2✔
82
            };
83
        });
84
};
85
/**
86
 * merge the configuration of view layers in the main layers array
87
 * @param {array} layers array of layer object
88
 * @param {object} view map view configuration
89
 */
90
export const mergeViewLayers = (layers, { layers: viewLayers = [] } = {}) => {
1✔
91
    if (viewLayers.length === 0) {
35✔
92
        return layers || [];
29✔
93
    }
94
    return (layers || []).map((layer) => {
6!
95
        const viewLayer = viewLayers.find(vLayer => vLayer.id === layer.id);
9✔
96
        if (viewLayer) {
9✔
97
            return { ...layer, ...viewLayer, changed: true };
6✔
98
        }
99
        return layer;
3✔
100
    });
101
};
102
/**
103
 * detect if a view layer is different from the map layers
104
 * @param {object} viewLayer layer object
105
 * @param {object} mapLayer layer object
106
 */
107
export const isViewLayerChanged = (viewLayer, mapLayer) => {
1✔
108
    return viewLayer.visibility !== mapLayer.visibility
9✔
109
    || viewLayer.opacity !== mapLayer.opacity
110
    || viewLayer.clippingLayerResourceId !== mapLayer.clippingLayerResourceId
111
    || viewLayer.clippingPolygonFeatureId !== mapLayer.clippingPolygonFeatureId
112
    || viewLayer.clippingPolygonUnion !== mapLayer.clippingPolygonUnion;
113
};
114
/**
115
 * pick view layer properties
116
 * @param {object} node layer object
117
 */
118
export const pickViewLayerProperties = (node) => {
1✔
119
    return omitBy({
3✔
120
        id: node.id,
121
        visibility: node.visibility,
122
        opacity: node.opacity,
123
        clippingLayerResourceId: node.clippingLayerResourceId,
124
        clippingPolygonFeatureId: node.clippingPolygonFeatureId,
125
        clippingPolygonUnion: node.clippingPolygonUnion
126
    }, isUndefined);
127
};
128
/**
129
 * pick view group properties
130
 * @param {object} node group object
131
 */
132
export const pickViewGroupProperties = (node) => {
1✔
133
    return omitBy({
3✔
134
        id: node.id,
135
        visibility: node.visibility
136
    }, isUndefined);
137
};
138
/**
139
 * merge the configuration of view groups in the main groups array
140
 * @param {array} rawGroups array of group object
141
 * @param {object} view map view configuration
142
 * @param {boolean} recursive apply recursive merge instead of flat one
143
 */
144
export const mergeViewGroups = (groups, { groups: viewGroups = [] } = {}, recursive) => {
1✔
145
    if (viewGroups.length === 0) {
18✔
146
        return groups || [];
11!
147
    }
148
    if (recursive) {
7✔
149
        const recursiveMerge = (nodes) => {
1✔
150
            return nodes.map((node) => {
1✔
151
                if (isObject(node)) {
2!
152
                    const viewGroup = viewGroups.find(vGroup => vGroup.id === node.id);
2✔
153
                    return {
2✔
154
                        ...node,
155
                        ...viewGroup,
156
                        ...(node.nodes && { nodes: recursiveMerge(node.nodes) }),
2!
157
                        changed: true
158
                    };
159
                }
UNCOV
160
                return node;
×
161
            });
162
        };
163
        return recursiveMerge(groups || []);
1!
164
    }
165
    return (groups || []).map((group) => {
6!
166
        const viewGroup = viewGroups.find(vGroup => vGroup.id === group.id);
8✔
167
        if (viewGroup) {
8✔
168
            return { ...group, ...viewGroup, changed: true };
5✔
169
        }
170
        return group;
3✔
171
    });
172
};
173
/**
174
 * Exclude all geometry but polygons and ensure each feature has an identifier
175
 * @param {array} features array of GeoJSON features
176
 */
177
export const formatClippingFeatures = (features) => {
1✔
178
    return features
18✔
179
        ? features
180
            .filter(({ geometry }) => geometry.type === 'Polygon')
5✔
181
            .map((feature, idx) => ({
5✔
182
                ...feature,
183
                geometry: {
184
                    type: 'Polygon',
185
                    // remove height because is not needed for clipping
186
                    coordinates: feature.geometry.coordinates.map((rings) => rings.map(([lng, lat]) => [lng, lat]))
25✔
187
                },
188
                id: isNumber(feature?.id) ? `Feature ${feature?.id}` : feature?.id || `Feature ${idx + 1}`
10✔
189
            }))
190
        : undefined;
191
};
192

193
export const ZOOM_TO_HEIGHT = 80000000;
1✔
194
/**
195
 * convert height to zoom level
196
 * @param {number} height height in meter
197
 */
198
export const getZoomFromHeight = (height) => Math.log2(ZOOM_TO_HEIGHT / height) + 1;
1✔
199
/**
200
 * convert zoom level to height
201
 * @param {number} zoom zoom level
202
 */
203
export const getHeightFromZoom = (zoom) => ZOOM_TO_HEIGHT / Math.pow(2, zoom - 1);
1✔
204
/**
205
 * clean the current state of the map views before using it in the saving process
206
 * @param {object} payload current map views state
207
 * @param {array} layers layers available inside a map
208
 */
209
export const cleanMapViewSavedPayload = ({ views, resources, ...payload }, layers = []) => {
1✔
210
    const newViews = views?.map((view) => {
5✔
211
        if (view?.layers?.length > 0) {
4!
212
            return {
4✔
213
                ...view,
214
                layers: view.layers.filter(viewLayer => !!layers.find(layer => layer.id === viewLayer.id))
4✔
215
            };
216
        }
UNCOV
217
        return view;
×
218
    });
219
    const newResources = resources?.filter((resource) => {
5✔
220
        const isUsedByView = !!newViews?.find((view) =>
5✔
221
            view?.mask?.resourceId === resource.id
4✔
222
            || view?.terrain?.clippingLayerResourceId === resource.id
223
            || view?.layers?.find(layer => layer?.clippingLayerResourceId === resource.id));
2✔
224
        return isUsedByView;
5✔
225
    }).map((resource) => {
226
        // we should store the feature collection
227
        // if the layer is vector and is not possible to find the original layer in map config
228
        // so we persist the mask or clipping resource
229
        if (resource?.data?.type === 'vector' && !layers.find(layer => layer.id === resource?.data?.id)) {
4✔
230
            return resource;
1✔
231
        }
232
        const { collection, ...data } = resource?.data;
3✔
233
        return {
3✔
234
            ...resource,
235
            data
236
        };
237
    });
238
    return {
5✔
239
        ...payload,
240
        views: newViews,
241
        resources: newResources
242
    };
243
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc