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

geosolutions-it / MapStore2 / 16435137171

22 Jul 2025 05:02AM UTC coverage: 76.892% (-0.03%) from 76.924%
16435137171

Pull #11331

github

web-flow
Merge a49c8f80b into 05d85f02d
Pull Request #11331: Fix #11103 Update cesium to latest stable 1.131.0 , reviewed all the cesium layers and cesium map.

31265 of 48660 branches covered (64.25%)

35 of 46 new or added lines in 8 files covered. (76.09%)

56 existing lines in 9 files now uncovered.

38806 of 50468 relevant lines covered (76.89%)

36.5 hits per line

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

61.62
/web/client/utils/styleparser/PrintStyleParser.js
1
/*
2
 * Copyright 2023, 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

9
import { flatten } from 'lodash';
10
import turfFlatten from '@turf/flatten';
11
import {
12
    resolveAttributeTemplate,
13
    geoStylerStyleFilter,
14
    drawWellKnownNameImageFromSymbolizer,
15
    parseSymbolizerExpressions,
16
    getCachedImageById
17
} from './StyleParserUtils';
18
import { drawIcons } from './IconUtils';
19

20
import { geometryFunctionsLibrary } from './GeometryFunctionsUtils';
21
import { circleToPolygon } from '../DrawGeometryUtils';
22

23
const getGeometryFunction = geometryFunctionsLibrary.geojson();
1✔
24

25
const anchorToGraphicOffset = (anchor, width, height) => {
1✔
26
    switch (anchor) {
1!
27
    case 'top-left':
28
        return [0, 0];
1✔
29
    case 'top':
30
        return [-(width / 2), 0];
×
31
    case 'top-right':
32
        return [-width, 0];
×
33
    case 'left':
34
        return [0, -(height / 2)];
×
35
    case 'center':
36
        return [-(width / 2), -(height / 2)];
×
37
    case 'right':
38
        return [-width, -(height / 2)];
×
39
    case 'bottom-left':
40
        return [0, -height];
×
41
    case 'bottom':
42
        return [-(width / 2), -height];
×
43
    case 'bottom-right':
44
        return [-width, -height];
×
45
    default:
46
        return [-(width / 2), -(height / 2)];
×
47
    }
48
};
49

50
const anchorToLabelAlign = (anchor) => {
1✔
51
    switch (anchor) {
2!
52
    case 'top-left':
53
        return 'lt';
×
54
    case 'top':
55
        return 'ct';
×
56
    case 'top-right':
57
        return 'rt';
×
58
    case 'left':
59
        return 'lm';
×
60
    case 'center':
61
        return 'cm';
×
62
    case 'right':
63
        return 'rm';
×
64
    case 'bottom-left':
65
        return 'lb';
×
66
    case 'bottom':
67
        return 'cb';
1✔
68
    case 'bottom-right':
69
        return 'rb';
×
70
    default:
71
        return 'cm';
1✔
72
    }
73
};
74

75
const symbolizerToPrintMSStyle = (symbolizer, feature, layer, originalSymbolizer) => {
1✔
76
    const globalOpacity = layer.opacity === undefined ? 1 : layer.opacity;
11!
77
    if (symbolizer.kind === 'Mark') {
11✔
78
        const { width, height, canvas }  = drawWellKnownNameImageFromSymbolizer(symbolizer);
1✔
79
        return {
1✔
80
            graphicWidth: width,
81
            graphicHeight: height,
82
            externalGraphic: canvas.toDataURL(),
83
            graphicXOffset: -width / 2,
84
            graphicYOffset: -height / 2,
85
            rotation: symbolizer.rotate || 0,
1!
86
            graphicOpacity: globalOpacity
87
        };
88
    }
89
    if (symbolizer.kind === 'Icon') {
10✔
90
        const { width = symbolizer.size, height = symbolizer.size }  = getCachedImageById(originalSymbolizer);
1!
91
        const aspect = width / height;
1✔
92
        let iconSizeW = symbolizer.size;
1✔
93
        let iconSizeH = iconSizeW / aspect;
1✔
94
        if (height > width) {
1!
95
            iconSizeH = symbolizer.size;
×
96
            iconSizeW = iconSizeH * aspect;
×
97
        }
98
        const [graphicXOffset, graphicYOffset] = anchorToGraphicOffset(symbolizer.anchor, iconSizeW, iconSizeH);
1✔
99
        return {
1✔
100
            graphicWidth: iconSizeW,
101
            graphicHeight: iconSizeH,
102
            externalGraphic: symbolizer.image,
103
            graphicXOffset,
104
            graphicYOffset,
105
            rotation: symbolizer.rotate || 0,
1!
106
            graphicOpacity: symbolizer.opacity * globalOpacity
107
        };
108
    }
109
    if (symbolizer.kind === 'Text') {
9✔
110
        return {
2✔
111
            // not supported
112
            // fontStyle: symbolizer.fontStyle,
113
            fontSize: symbolizer.size, // in mapfish is in px
114
            // Supported itext fonts: COURIER, HELVETICA, TIMES_ROMAN
115
            fontFamily: (symbolizer.font || ['TIMES_ROMAN'])[0],
3✔
116
            fontWeight: symbolizer.fontWeight,
117
            labelAlign: anchorToLabelAlign(symbolizer.anchor),
118
            labelXOffset: symbolizer?.offset?.[0] || 0,
3✔
119
            labelYOffset: -(symbolizer?.offset?.[1] || 0),
3✔
120
            rotation: -(symbolizer.rotate || 0),
3✔
121
            fontColor: symbolizer.color,
122
            fontOpacity: 1 * globalOpacity,
123
            label: resolveAttributeTemplate(feature, symbolizer.label, ''),
124
            // Halo information
125
            ...(symbolizer.haloWidth > 0 && {
4✔
126
                labelOutlineColor: symbolizer.haloColor,
127
                labelOutlineOpacity: 1 * globalOpacity,
128
                labelOutlineWidth: symbolizer.haloWidth,
129
                labelOutlineMode: 'halo'
130
            }),
131
            // hide default point
132
            fillOpacity: 0,
133
            pointRadius: 0,
134
            strokeOpacity: 0,
135
            strokeWidth: 0
136
        };
137
    }
138
    if (symbolizer.kind === 'Line') {
7✔
139
        return {
4✔
140
            strokeColor: symbolizer.color,
141
            strokeOpacity: symbolizer.opacity * globalOpacity,
142
            strokeWidth: symbolizer.width,
143
            ...(symbolizer.dasharray && { strokeDashstyle: symbolizer.dasharray.join(" ") })
5✔
144
        };
145
    }
146
    if (symbolizer.kind === 'Fill') {
3✔
147
        return {
2✔
148
            strokeColor: symbolizer.outlineColor,
149
            strokeOpacity: (symbolizer.outlineOpacity ?? 0) * globalOpacity,
2!
150
            strokeWidth: symbolizer.outlineWidth ?? 0,
2!
151
            ...(symbolizer.outlineDasharray && { strokeDashstyle: symbolizer.outlineDasharray.join(" ") }),
4✔
152
            fillColor: symbolizer.color,
153
            fillOpacity: symbolizer.fillOpacity * globalOpacity
154
        };
155
    }
156
    if (symbolizer.kind === 'Circle') {
1!
157
        return {
1✔
158
            strokeColor: symbolizer.outlineColor,
159
            strokeOpacity: (symbolizer.outlineOpacity ?? 0) * globalOpacity,
1!
160
            strokeWidth: symbolizer.outlineWidth ?? 0,
1!
161
            ...(symbolizer.outlineDasharray && { strokeDashstyle: symbolizer.outlineDasharray.join(" ") }),
2✔
162
            fillColor: symbolizer.color,
163
            fillOpacity: symbolizer.opacity * globalOpacity
164
        };
165
    }
UNCOV
166
    return {
×
167
        display: 'none'
168
    };
169
};
170

171
export const getPrintStyleFuncFromRules = (geoStylerStyle) => {
1✔
172
    return ({
11✔
173
        layer,
174
        spec = { projection: 'EPSG:3857' }
9✔
175
    }) => {
176
        if (!layer?.features) {
11!
UNCOV
177
            return [];
×
178
        }
179
        const collection = turfFlatten({ type: 'FeatureCollection', features: layer.features});
11✔
180
        return flatten(collection.features
11✔
181
            .map((feature) => {
182
                const validRules = geoStylerStyle?.rules?.filter((rule) => !rule.filter || geoStylerStyleFilter(feature, rule.filter));
11✔
183
                if (validRules.length > 0) {
11!
184
                    const geometryType = feature.geometry.type;
11✔
185
                    const symbolizers = validRules.reduce((acc, rule) => [...acc, ...rule?.symbolizers], []);
11✔
186
                    const pointGeometrySymbolizers = symbolizers.filter((symbolizer) =>
11✔
187
                        ['Mark', 'Icon', 'Text', 'Model'].includes(symbolizer.kind) && ['Point'].includes(geometryType)
11✔
188
                    );
189
                    const polylineGeometrySymbolizers = symbolizers.filter((symbolizer) =>
11✔
190
                        symbolizer.kind === 'Line' && ['LineString'].includes(geometryType)
11✔
191
                    );
192
                    const polygonGeometrySymbolizers = symbolizers.filter((symbolizer) =>
11✔
193
                        symbolizer.kind === 'Fill' && ['Polygon'].includes(geometryType)
11✔
194
                    );
195

196
                    const circleGeometrySymbolizers = symbolizers.filter((symbolizer) =>
11✔
197
                        symbolizer.kind === 'Circle' && ['Point'].includes(geometryType)
11✔
198
                    );
199

200
                    const additionalPointSymbolizers = symbolizers.filter((symbolizer, idx) =>
11✔
201
                        ['Mark', 'Icon', 'Text', 'Model'].includes(symbolizer.kind)
11✔
202
                        && (
203
                            ['Polygon'].includes(geometryType)
204
                            || ['LineString'].includes(geometryType)
205
                            || ['Point'].includes(geometryType) && (circleGeometrySymbolizers.length === 0
4!
206
                                ? idx < pointGeometrySymbolizers.length - 1
207
                                : true)
208
                        )
209
                    );
210
                    const originalSymbolizer = circleGeometrySymbolizers[circleGeometrySymbolizers.length - 1]
11✔
211
                    || pointGeometrySymbolizers[pointGeometrySymbolizers.length - 1]
212
                    || polylineGeometrySymbolizers[polylineGeometrySymbolizers.length - 1]
213
                    || polygonGeometrySymbolizers[polygonGeometrySymbolizers.length - 1];
214

215
                    const symbolizer = parseSymbolizerExpressions(originalSymbolizer, feature);
11✔
216

217
                    let geometry = feature.geometry;
11✔
218
                    const geometryFunction = getGeometryFunction(symbolizer);
11✔
219
                    if (geometryFunction && (geometryType === 'LineString' || geometryType === 'Polygon')) {
11!
UNCOV
220
                        geometry = {
×
221
                            type: geometryType,
222
                            coordinates: geometryFunction(feature)
223
                        };
224
                    }
225
                    if (geometryType === 'Point' && circleGeometrySymbolizers.length) {
11✔
226
                        geometry = circleToPolygon(feature.geometry.coordinates, symbolizer.radius, symbolizer.geodesic, {
1✔
227
                            projection: spec.projection
228
                        });
229
                    }
230

231
                    return [
11✔
232
                        {
233
                            ...feature,
234
                            geometry,
235
                            properties: {
236
                                ...feature?.properties,
237
                                ms_style: symbolizerToPrintMSStyle(symbolizer, feature, layer, originalSymbolizer)
238
                            }
239
                        },
240
                        ...additionalPointSymbolizers.map((_additionalSymbolizer) => {
UNCOV
241
                            const additionalSymbolizer = parseSymbolizerExpressions(_additionalSymbolizer, feature);
×
UNCOV
242
                            const geomFunction = getGeometryFunction({ msGeometry: { name: 'centerPoint' }, ...additionalSymbolizer});
×
243
                            if (geomFunction) {
×
244
                                const coordinates = geomFunction(feature);
×
245
                                if (coordinates) {
×
246
                                    return {
×
247
                                        ...feature,
248
                                        geometry: {
249
                                            type: 'Point',
250
                                            coordinates
251
                                        },
252
                                        properties: {
253
                                            ...feature?.properties,
254
                                            ms_style: symbolizerToPrintMSStyle(additionalSymbolizer, feature, layer, _additionalSymbolizer)
255
                                        }
256
                                    };
257
                                }
258
                            }
UNCOV
259
                            return null;
×
UNCOV
260
                        }).filter((feat) => !!feat)
×
261
                    ];
262
                }
UNCOV
263
                return [];
×
264
            }));
265
    };
266
};
267

268
class PrintStyleParser {
269

270
    readStyle() {
271
        return new Promise((resolve, reject) => {
1✔
272
            try {
1✔
273
                resolve(null);
1✔
274
            } catch (error) {
UNCOV
275
                reject(error);
×
276
            }
277
        });
278
    }
279

280
    writeStyle(geoStylerStyle, sync) {
281
        if (sync) {
11!
282
            return getPrintStyleFuncFromRules(geoStylerStyle);
11✔
283
        }
UNCOV
284
        return new Promise((resolve, reject) => {
×
UNCOV
285
            try {
×
286
                const styleFunc = (options) => drawIcons(geoStylerStyle)
×
287
                    .then((images = []) => {
×
288
                        return getPrintStyleFuncFromRules(geoStylerStyle, { images })(options);
×
289
                    });
290
                resolve(styleFunc);
×
291
            } catch (error) {
292
                reject(error);
×
293
            }
294
        });
295
    }
296
}
297

298
export default PrintStyleParser;
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

© 2026 Coveralls, Inc