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

geosolutions-it / MapStore2 / 17863671168

19 Sep 2025 04:09PM UTC coverage: 76.743% (-0.009%) from 76.752%
17863671168

Pull #11478

github

web-flow
Merge 3c4b169f2 into a3115d1ac
Pull Request #11478: #11437: Isochrone plugin implementation

31729 of 49420 branches covered (64.2%)

253 of 340 new or added lines in 14 files covered. (74.41%)

137 existing lines in 15 files now uncovered.

39512 of 51486 relevant lines covered (76.74%)

37.63 hits per line

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

74.79
/web/client/components/map/openlayers/VectorStyle.js
1
/*
2
 * Copyright 2019, 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 isNil from 'lodash/isNil';
9
import trim from 'lodash/trim';
10
import isString from 'lodash/isString';
11
import isArray from 'lodash/isArray';
12
import castArray from 'lodash/castArray';
13
import head from 'lodash/head';
14
import last from 'lodash/last';
15
import find from 'lodash/find';
16
import isObject from 'lodash/isObject';
17
import {getCenter} from 'ol/extent';
18
import {colorToRgbaStr} from '../../../utils/ColorUtils';
19
import {reproject, transformLineToArcs} from '../../../utils/CoordinatesUtils';
20
import Icons from '../../../utils/openlayers/Icons';
21
import {
22
    isMarkerStyle, isTextStyle, isStrokeStyle, isFillStyle, isCircleStyle, isSymbolStyle,
23
    registerGeometryFunctions, geometryFunctions, getStyleParser
24
} from '../../../utils/VectorStyleUtils';
25

26
import CircleStyle from 'ol/style/Circle';
27
import {Stroke, Fill, Text, Style} from 'ol/style';
28
import {Point, LineString} from 'ol/geom';
29

30
import axios from '../../../libs/ajax';
31

32
import {
33
    getStyle as getStyleLegacy, getMarkerStyle as getMarkerStyleLegacyFun,
34
    startEndPolylineStyle as startEndPolylineStyleLegacy, defaultStyles as defaultStylesLegacy} from './LegacyVectorStyle';
35

36
const selectedStyle = {
1✔
37
    white: [255, 255, 255, 1],
38
    blue: [0, 153, 255, 1],
39
    width: 3
40
};
41
/**
42
 * converts a style object into an ol.Style
43
 * @param {object} style to convert
44
 * @param {object} ol.Stroke object
45
 * @param {object} ol.Fill object
46
 * @return if a circle style is passed then return it available for ol.style.Image
47
*/
48
export const getCircleStyle = (style = {}, stroke = null, fill = null) => {
1✔
49
    return isCircleStyle(style) ? new CircleStyle({
10✔
50
        stroke,
51
        fill,
52
        radius: style.radius || 5
2!
53
    }) : null;
54
};
55
/**
56
 * converts a style object into an array of ol.Style. It uses the Icons library
57
 * if specified or the standard one if not.
58
 * @param {object} style to convert
59
 * @return array of ol.Style
60
*/
61
export const getMarkerStyle = (style) => {
1✔
62
    if (isMarkerStyle(style)) {
14✔
63
        if (style.iconUrl) {
12✔
64
            return Icons.standard.getIcon({style});
7✔
65
        }
66
        const iconLibrary = style.iconLibrary || 'extra';
5✔
67
        if (Icons[iconLibrary]) {
5!
68
            return Icons[iconLibrary].getIcon({style});
5✔
69
        }
70
    }
71
    return null;
2✔
72
};
73
/**
74
 * converts a style object
75
 * @param {object} style to convert
76
 * @return an Stroke style
77
*/
78
export const getStrokeStyle = (style = {}) => {
1!
79
    return isStrokeStyle(style) ? new Stroke(style.stroke && isObject(style.stroke) ? style.stroke : { // not sure about this ternary expr
12✔
80
        color: style.highlight ? selectedStyle.blue : colorToRgbaStr(style.color || style.stroke || "#0000FF", isNil(style.opacity) ? 1 : style.opacity),
24✔
81
        width: isNil(style.weight) ? 1 : style.weight,
8✔
82
        lineDash: isString(style.dashArray) && trim(style.dashArray).split(' ') || isArray(style.dashArray) && style.dashArray || [0],
24!
83
        lineCap: style.lineCap || 'round',
15✔
84
        lineJoin: style.lineJoin || 'round',
15✔
85
        lineDashOffset: style.dashOffset || 0
15✔
86
    }) : null;
87
};
88

89
/**
90
 * converts a style object
91
 * @param {object} style to convert
92
 * @return an Fill style
93
*/
94
export const getFillStyle = (style = {}) => {
1!
95
    return isFillStyle(style) ? new Fill(style.fill && isObject(style.fill) ? style.fill : { // not sure about this ternary expr
10!
96
        color: colorToRgbaStr(style.fillColor || "#0000FF", isNil(style.fillOpacity) ? 1 : style.fillOpacity)
13✔
97
    }) : null;
98
};
99

100
/**
101
 * converts a style object
102
 * @param {object} style to convert
103
 * @param {object} stroke Stroke ready to use
104
 * @param {object} fill Fill ready to use
105
 * @return an Text style
106
*/
107
export const getTextStyle = (style = {}, stroke = null, fill = null, feature) => {
1!
108
    return isTextStyle(style) ? new Text({
8✔
109
        fill,
110
        offsetY: style.offsetY || -( 4 * Math.sqrt(style.fontSize)), // TODO improve this for high font values > 100px
6✔
111
        rotation: style.textRotationDeg ? style.textRotationDeg / 180 * Math.PI : 0,
3✔
112
        textAlign: style.textAlign || "center",
6✔
113
        text: style.label || feature && feature.properties && feature.properties.valueText || "New",
5!
114
        font: style.font || "Arial",
5✔
115
        // halo
116
        stroke: style.highlight ? new Stroke({
3!
117
            color: [255, 255, 255, 1],
118
            width: 2
119
        }) : stroke,
120
        // this should be another rule for the small circle
121
        image: style.highlight ?
3!
122
            new CircleStyle({
123
                radius: 5,
124
                fill: null,
125
                stroke: new Stroke({
126
                    color: colorToRgbaStr(style.color || "#0000FF", style.opacity || 1),
×
127
                    width: style.weight || 1
×
128
                })
129
            }) : null
130
    }) : null;
131
};
132

133

134
/**
135
 * it creates a custom style for the first point of a polyline
136
 * @param {object} options possible configuration of start point
137
 * @param {number} options.radius radius of the circle
138
 * @param {string} options.fillColor ol color for the circle fill style
139
 * @param {boolean} options.applyToPolygon tells if this style can be applied to a polygon
140
 * @return {Style} style of the point
141
*/
142
export const firstPointOfPolylineStyle = ({radius = 5, fillColor = 'green', applyToPolygon = false} = {}) => new Style({
1!
143
    image: new CircleStyle({
144
        radius,
145
        fill: new Fill({
146
            color: fillColor
147
        })
148
    }),
149
    geometry: function(feature) {
UNCOV
150
        const geom = feature.getGeometry();
×
151
        const type = geom.getType();
×
152
        if (!applyToPolygon && type === "Polygon") {
×
153
            return null;
×
154
        }
UNCOV
155
        let coordinates = type === "Polygon" ? geom.getCoordinates()[0] : geom.getCoordinates();
×
156
        return coordinates.length > 1 ? new Point(head(coordinates)) : null;
×
157
    }
158
});
159

160
/**
161
 * it creates a custom style for the last point of a polyline
162
 * @param {object} options possible configuration of start point
163
 * @param {number} options.radius radius of the circle
164
 * @param {string} options.fillColor ol color for the circle fill style
165
 * @param {boolean} options.applyToPolygon tells if this style can be applied to a polygon
166
 * @return {Style} style of the point
167
*/
168
export const lastPointOfPolylineStyle = ({radius = 5, fillColor = 'red', applyToPolygon = false} = {}) => new Style({
1!
169
    image: new CircleStyle({
170
        radius,
171
        fill: new Fill({
172
            color: fillColor
173
        })
174
    }),
175
    geometry: function(feature) {
UNCOV
176
        const geom = feature.getGeometry();
×
177
        const type = geom.getType();
×
178
        if (!applyToPolygon && type === "Polygon") {
×
179
            return null;
×
180
        }
UNCOV
181
        let coordinates = type === "Polygon" ? geom.getCoordinates()[0] : geom.getCoordinates();
×
182
        return new Point(coordinates.length > 3 ? coordinates[coordinates.length - (type === "Polygon" ? 2 : 1)] : last(coordinates));
×
183
    }
184
});
185

186
/**
187
    creates styles to highlight/customize start and end point of a polyline
188
*/
189
export const addDefaultStartEndPoints = (styles = [], startPointOptions = {radius: 3, fillColor: "green", applyToPolygon: true}, endPointOptions = {radius: 3, fillColor: "red", applyToPolygon: true}) => {
1!
UNCOV
190
    let points = [];
×
191
    if (!find(styles, s => s.geometry === "startPoint" && s.filtering)) {
×
192
        points.push(firstPointOfPolylineStyle({...startPointOptions}));
×
193
    }
UNCOV
194
    if (!find(styles, s => s.geometry === "endPoint" && s.filtering)) {
×
195
        points.push(lastPointOfPolylineStyle({...endPointOptions}));
×
196
    }
UNCOV
197
    return points;
×
198
};
199

200
export const centerPoint = (feature) => {
1✔
201
    const geometry = feature.getGeometry();
3✔
202
    const { isGeodesic = false } = feature.getProperties();
3✔
203
    const extent = geometry.getExtent();
3✔
204
    let center = isGeodesic ? getCenter(extent) : (geometry.getCenter && geometry.getCenter() || [extent[2] - extent[0], extent[3] - extent[1]]);
3!
205
    return new Point(center);
3✔
206
};
207
export const lineToArc = (feature) => {
1✔
208
    const type = feature.getGeometry().getType();
2✔
209
    if (type === "LineString" || type === "MultiPoint") {
2✔
210
        let coordinates = feature.getGeometry().getCoordinates();
1✔
211
        coordinates = transformLineToArcs(coordinates.map(c => {
1✔
212
            const point = reproject(c, "EPSG:3857", "EPSG:4326");
4✔
213
            return [point.x, point .y];
4✔
214
        }));
215
        return new LineString(coordinates.map(c => {
1✔
216
            const point = reproject(c, "EPSG:4326", "EPSG:3857");
300✔
217
            return [point.x, point .y];
300✔
218
        }));
219
    }
220
    return feature.getGeometry();
1✔
221
};
222
export const startPoint = (feature) => {
1✔
UNCOV
223
    const geom = feature.getGeometry();
×
224
    const type = geom.getType();
×
225
    let coordinates = type === "Polygon" ? geom.getCoordinates()[0] : geom.getCoordinates();
×
226
    return coordinates.length > 1 ? new Point(head(coordinates)) : null;
×
227
};
228
export const endPoint = (feature) => {
1✔
UNCOV
229
    const geom = feature.getGeometry();
×
230
    const type = geom.getType();
×
231

UNCOV
232
    let coordinates = type === "Polygon" ? geom.getCoordinates()[0] : geom.getCoordinates();
×
233
    return new Point(coordinates.length > 3 ? coordinates[coordinates.length - (type === "Polygon" ? 2 : 1)] : last(coordinates));
×
234
};
235

236
registerGeometryFunctions("centerPoint", centerPoint, "Point");
1✔
237
registerGeometryFunctions("lineToArc", lineToArc, "LineString");
1✔
238
registerGeometryFunctions("startPoint", startPoint, "Point");
1✔
239
registerGeometryFunctions("endPoint", endPoint, "Point");
1✔
240

241
/**
242
    if a geom expression is present then return the corresponding function
243
*/
244
export const getGeometryTrasformation = (style = {}) => {
1!
245
    return style.geometry ?
12✔
246
    // then parse the geom_expression and return true or false
247
        (feature) => {
248
            const geomFunction = style.geometry || "centerPoint";
3!
249
            return geometryFunctions[geomFunction].func(feature);
3✔
250
        } : (f) => f.getGeometry();
1✔
251
};
252

253
export const getFilter = (style = {}) => {
1!
254
    return !isNil(style.filtering) ?
9✔
255
    // then parse the filter_expression and return true or false
256
        style.filtering : true; // if no filter is defined, it returns true
257
};
258

259

260
export const parseStyleToOl = (feature = {properties: {}}, style = {}, tempStyles = []) => {
1!
261
    const filtering = getFilter(style, feature);
6✔
262
    if (filtering) {
6!
263
        const stroke = getStrokeStyle(style);
6✔
264
        const fill = getFillStyle(style);
6✔
265
        const image = getCircleStyle(style, stroke, fill);
6✔
266

267
        if (isMarkerStyle(style)) {
6✔
268
            return getMarkerStyle(style).map(s => {
2✔
269
                s.setGeometry(getGeometryTrasformation(style));
4✔
270
                return s;
4✔
271
            });
272
        }
273
        if (isSymbolStyle(style)) {
4!
UNCOV
274
            return Icons.standard.getIcon({style}).map(s => {
×
275
                s.setGeometry(getGeometryTrasformation(style));
×
276
                return s;
×
277
            });
278
        }
279
        const text = getTextStyle(style, stroke, fill, feature);
4✔
280
        const zIndex = style.zIndex;
4✔
281

282
        // if filter is defined and true (default value)
283
        const finalStyle = new Style({
4✔
284
            geometry: getGeometryTrasformation(style),
285
            image,
286
            text,
287
            stroke: !text && !image && stroke || null,
12!
288
            fill: !text && !image && fill || null,
12!
289
            zIndex
290
        });
291
        return [finalStyle].concat(feature && feature.properties && feature.properties.canEdit && !feature.properties.isCircle ? addDefaultStartEndPoints(tempStyles) : []);
4!
292
    }
UNCOV
293
    return new Style({});
×
294
    // if not do not return anything
295

296
};
297

298
export const parseStyles = (feature = {properties: {}}) => {
1✔
299
    let styles = feature.style;
6✔
300
    if (styles) {
6✔
301
        let tempStyles = isArray(styles) ? styles : castArray(styles);
5!
302
        return tempStyles.reduce((p, c) => {
5✔
303
            return p.concat(parseStyleToOl(feature, c, tempStyles));
6✔
304
        }, []);
305
    }
306
    return [];
1✔
307

308
};
309

310
export const getStyle = (options, isDrawing = false, textValues = []) => {
1✔
311
    if (options.style && options.style.url) {
78✔
312
        return axios.get(options.style.url).then(response => {
2✔
313
            return Promise.all([
2✔
314
                getStyleParser(options.style.format),
315
                getStyleParser('openlayers')
316
            ])
317
                .then(([parser, olStyleParser]) =>
318
                    parser
2✔
319
                        .readStyle(response.data)
320
                        .then(style =>
321
                            olStyleParser.writeStyle(style))
2✔
322
                );
323
        });
324
    }
325
    if (options.style && options.style.format === 'geostyler') {
76✔
326
        return getStyleParser('openlayers')
63✔
327
            .then((olStyleParser) => olStyleParser.writeStyle(options.style.styleObj || options.style.body, { features: options.features }));
63✔
328
    }
329
    const style = getStyleLegacy(options, isDrawing, textValues);
13✔
330
    if (options.asPromise) {
13✔
331
        return new Promise((resolve) => {
8✔
332
            resolve(style);
8✔
333
        });
334
    }
335
    return style;
5✔
336
};
337

338
export const getMarkerStyleLegacy = getMarkerStyleLegacyFun;
1✔
339
export const startEndPolylineStyle = startEndPolylineStyleLegacy;
1✔
340
export const defaultStyles = defaultStylesLegacy;
1✔
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