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

geostyler / geostyler-openlayers-parser / 24454821586

15 Apr 2026 12:36PM UTC coverage: 72.046% (+1.7%) from 70.339%
24454821586

push

github

web-flow
feat(LineSymbolizer): support graphicStroke incl. dasharray (#918)

883 of 1262 branches covered (69.97%)

Branch coverage included in aggregate %.

167 of 187 new or added lines in 4 files covered. (89.3%)

1 existing line in 1 file now uncovered.

1001 of 1353 relevant lines covered (73.98%)

32.44 hits per line

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

72.79
/src/OlStyleParser.ts
1
import { parseFont } from 'css-font-parser';
2

3
import {
4
  CapType,
5
  FillSymbolizer,
6
  Filter,
7
  IconSymbolizer,
8
  JoinType,
9
  LineSymbolizer,
10
  MarkSymbolizer,
11
  Operator,
12
  PointSymbolizer,
13
  ReadStyleResult,
14
  Rule,
15
  Style,
16
  StyleParser,
17
  StyleType,
18
  Symbolizer,
19
  TextSymbolizer,
20
  UnsupportedProperties,
21
  WriteStyleResult,
22
  isGeoStylerBooleanFunction,
23
  isGeoStylerFunction,
24
  isGeoStylerStringFunction,
25
  isIconSymbolizer,
26
  isMarkSymbolizer,
27
  isSprite
28
} from 'geostyler-style';
29

30
import OlImageState from 'ol/ImageState';
31
import OlGeomPoint from 'ol/geom/Point';
32

33
import OlStyle, { StyleFunction as OlStyleFunction, StyleLike as OlStyleLike} from 'ol/style/Style';
34
import OlStyleImage from 'ol/style/Image';
35
import OlStyleStroke from 'ol/style/Stroke';
36
import OlStyleText, { Options as OlStyleTextOptions }  from 'ol/style/Text';
37
import OlStyleCircle from 'ol/style/Circle';
38
import OlStyleFill from 'ol/style/Fill';
39
import OlStyleIcon, { Options as OlStyleIconOptions }  from 'ol/style/Icon';
40
import OlStyleRegularshape from 'ol/style/RegularShape';
41
import OlLineString from 'ol/geom/LineString';
42
import OlMultiLineString from 'ol/geom/MultiLineString';
43
import { METERS_PER_UNIT } from 'ol/proj/Units';
44

45
import OlStyleUtil, { DEGREES_TO_RADIANS } from './Util/OlStyleUtil';
46
import { cleanWellKnownName, getPointSvg, isPointDefinedAsSvg } from './Util/OlSvgPoints';
47
import { toContext } from 'ol/render';
48
import OlFeature from 'ol/Feature';
49
import { fillPatternSvg } from './Util/OlSvgPatterns';
50
import {
51
  drawSvgToCanvas,
52
  getDecodedSvg,
53
  getEncodedSvg,
54
  getSvgProperties,
55
  LINE_WELLKNOWNNAMES,
56
  NOFILL_WELLKNOWNNAMES
57
} from './Util/OlSvgUtil';
58
import OlGraphicStrokeUtil from './Util/OlGraphicStrokeUtil';
59

60
export interface OlParserStyleFct {
61
  (feature?: any, resolution?: number): any;
62
  __geoStylerStyle: Style;
63
}
64

65
type SymbolizerKeyType = keyof UnsupportedProperties['Symbolizer'];
66

67
/**
68
 * This parser can be used with the GeoStyler.
69
 * It implements the GeoStyler-Style Parser interface to work with OpenLayers styles.
70
 *
71
 * @class OlStyleParser
72
 * @implements StyleParser
73
 */
74
export class OlStyleParser implements StyleParser<OlStyleLike> {
75

76
  /**
77
   * The name of the OlStyleParser.
78
   */
79
  public static title = 'OpenLayers Style Parser';
2✔
80

81
  unsupportedProperties: UnsupportedProperties = {
204✔
82
    Symbolizer: {
83
      MarkSymbolizer: {
84
        avoidEdges: 'none',
85
        blur: 'none',
86
        offsetAnchor: 'none',
87
        pitchAlignment: 'none',
88
        pitchScale: 'none'
89
      },
90
      FillSymbolizer: {
91
        antialias: 'none',
92
        opacity: {
93
          support: 'none',
94
          info: 'Use fillOpacity instead.'
95
        }
96
      },
97
      IconSymbolizer: {
98
        allowOverlap: 'none',
99
        anchor: 'none',
100
        avoidEdges: 'none',
101
        color: 'none',
102
        haloBlur: 'none',
103
        haloColor: 'none',
104
        haloWidth: 'none',
105
        keepUpright: 'none',
106
        offsetAnchor: 'none',
107
        size: {
108
          support: 'partial',
109
          info: 'Will set/get the width of the ol Icon.'
110
        },
111
        optional: 'none',
112
        padding: 'none',
113
        pitchAlignment: 'none',
114
        rotationAlignment: 'none',
115
        textFit: 'none',
116
        textFitPadding: 'none'
117
      },
118
      LineSymbolizer: {
119
        blur: 'none',
120
        gapWidth: 'none',
121
        gradient: 'none',
122
        miterLimit: 'none',
123
        roundLimit: 'none',
124
        spacing: 'none',
125
        graphicFill: 'none',
126
        graphicStroke: 'none',
127
        perpendicularOffset: 'none'
128
      },
129
      RasterSymbolizer: 'none',
130
      TextSymbolizer: {
131
        anchor: 'none',
132
        placement: {
133
          support:'partial',
134
          info: 'point and line supported. line-center will be mapped to line.'
135
        }
136
      }
137
    },
138
    Function: {
139
      double2bool: {
140
        support: 'none',
141
        info: 'Always returns false'
142
      },
143
      atan2: {
144
        support: 'none',
145
        info: 'Currently returns the first argument'
146
      },
147
      rint: {
148
        support: 'none',
149
        info: 'Currently returns the first argument'
150
      },
151
      numberFormat: {
152
        support: 'none',
153
        info: 'Currently returns the first argument'
154
      },
155
      strAbbreviate: {
156
        support: 'none',
157
        info: 'Currently returns the first argument'
158
      }
159
    }
160
  };
161

162
  title = 'OpenLayers Style Parser';
204✔
163
  olIconStyleCache: any = {};
204✔
164

165
  OlStyleConstructor = OlStyle;
204✔
166
  OlStyleImageConstructor = OlStyleImage;
204✔
167
  OlStyleFillConstructor = OlStyleFill;
204✔
168
  OlStyleStrokeConstructor = OlStyleStroke;
204✔
169
  OlStyleTextConstructor = OlStyleText;
204✔
170
  OlStyleCircleConstructor = OlStyleCircle;
204✔
171
  OlStyleIconConstructor = OlStyleIcon;
204✔
172
  OlStyleRegularshapeConstructor = OlStyleRegularshape;
204✔
173
  OlLineStringContructor = OlLineString;
204✔
174
  OlMultiLineStringConstructor = OlMultiLineString;
204✔
175
  OlPointConstructor = OlGeomPoint;
204✔
176

177
  constructor(ol?: any) {
178
    if (ol) {
204!
179
      this.OlStyleConstructor = ol.style.Style;
×
180
      this.OlStyleImageConstructor = ol.style.Image;
×
181
      this.OlStyleFillConstructor = ol.style.Fill;
×
182
      this.OlStyleStrokeConstructor = ol.style.Stroke;
×
183
      this.OlStyleTextConstructor = ol.style.Text;
×
184
      this.OlStyleCircleConstructor = ol.style.Circle;
×
185
      this.OlStyleIconConstructor = ol.style.Icon;
×
186
      this.OlStyleRegularshapeConstructor = ol.style.RegularShape;
×
NEW
187
      this.OlLineStringContructor = ol.geom.LineString;
×
NEW
188
      this.OlMultiLineStringConstructor = ol.geom.MultiLineString;
×
NEW
189
      this.OlPointConstructor = ol.geom.Point;
×
190
    }
191
  }
192

193
  isOlParserStyleFct = (x: any): x is OlParserStyleFct => {
204✔
194
    return typeof x === 'function';
60✔
195
  };
196

197
  /**
198
   * Get the GeoStyler-Style PointSymbolizer from an OpenLayers Style object.
199
   *
200
   * @param olStyle The OpenLayers Style object
201
   * @return The GeoStyler-Style PointSymbolizer
202
   */
203
  getPointSymbolizerFromOlStyle(olStyle: OlStyle): PointSymbolizer {
204
    let pointSymbolizer: PointSymbolizer;
205
    if (olStyle.getImage() instanceof this.OlStyleCircleConstructor) {
46!
206
      // circle
207
      const olCircleStyle: OlStyleCircle = olStyle.getImage() as OlStyleCircle;
×
208
      const olFillStyle = olCircleStyle.getFill();
×
209
      const olStrokeStyle = olCircleStyle.getStroke();
×
210
      const offset = olCircleStyle.getDisplacement() as [number, number];
×
211

212
      const circleSymbolizer: MarkSymbolizer = {
×
213
        kind: 'Mark',
214
        wellKnownName: 'circle',
215
        color: olFillStyle ? OlStyleUtil.getHexColor(olFillStyle.getColor() as string) : undefined,
×
216
        opacity: olCircleStyle.getOpacity() !== 1 ? olCircleStyle.getOpacity() : undefined,
×
217
        fillOpacity: olFillStyle ? OlStyleUtil.getOpacity(olFillStyle.getColor() as string) : undefined,
×
218
        radius: (olCircleStyle.getRadius() !== 0) ? olCircleStyle.getRadius() : 5,
×
219
        strokeColor: olStrokeStyle ? olStrokeStyle.getColor() as string : undefined,
×
220
        strokeOpacity: olStrokeStyle ? OlStyleUtil.getOpacity(olStrokeStyle.getColor() as string) : undefined,
×
221
        strokeWidth: olStrokeStyle ? olStrokeStyle.getWidth() : undefined,
×
222
        offset: offset[0] || offset[1] ? offset : undefined
×
223
      };
224
      pointSymbolizer = circleSymbolizer;
×
225
    } else if (olStyle.getImage() instanceof this.OlStyleRegularshapeConstructor) {
46!
226
      const olRegularStyle: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape;
×
227
      const olFillStyle = olRegularStyle.getFill();
×
228
      const olStrokeStyle = olRegularStyle.getStroke();
×
229
      const radius = olRegularStyle.getRadius();
×
230
      const radius2 = olRegularStyle.getRadius2();
×
231
      const points = olRegularStyle.getPoints();
×
232
      const angle = olRegularStyle.getAngle();
×
233
      const offset = olRegularStyle.getDisplacement() as [number, number];
×
234

235
      const markSymbolizer: MarkSymbolizer = {
×
236
        kind: 'Mark',
237
        color: olFillStyle ? OlStyleUtil.getHexColor(olFillStyle.getColor() as string) : undefined,
×
238
        opacity: olRegularStyle.getOpacity() !== 1 ? olRegularStyle.getOpacity() : undefined,
×
239
        fillOpacity: olFillStyle ? OlStyleUtil.getOpacity(olFillStyle.getColor() as string) : undefined,
×
240
        strokeColor: olStrokeStyle ? olStrokeStyle.getColor() as string : undefined,
×
241
        strokeOpacity: olStrokeStyle ? OlStyleUtil.getOpacity(olStrokeStyle.getColor() as string) : undefined,
×
242
        strokeWidth: olStrokeStyle ? olStrokeStyle.getWidth() : undefined,
×
243
        radius: (radius !== 0) ? radius : 5,
×
244
        // Rotation in openlayers is radians while we use degree
245
        rotate: olRegularStyle.getRotation() / DEGREES_TO_RADIANS,
246
        offset: offset[0] || offset[1] ? offset : undefined
×
247
      } as MarkSymbolizer;
248

249
      switch (points) {
×
250
        case 2:
251
          switch (angle) {
×
252
            case 0:
253
              markSymbolizer.wellKnownName = 'shape://vertline';
×
254
              break;
×
255
            case Math.PI / 2:
256
              markSymbolizer.wellKnownName = 'shape://horline';
×
257
              break;
×
258
            case Math.PI / 4:
259
              markSymbolizer.wellKnownName = 'shape://slash';
×
260
              break;
×
261
            case 2 * Math.PI - (Math.PI / 4):
262
              markSymbolizer.wellKnownName = 'shape://backslash';
×
263
              break;
×
264
            default:
265
              break;
×
266
          }
267
          break;
×
268
        case 3:
269
          switch (angle) {
×
270
            case 0:
271
              markSymbolizer.wellKnownName = 'triangle';
×
272
              break;
×
273
            case Math.PI / 2:
274
              markSymbolizer.wellKnownName = 'shape://carrow';
×
275
              break;
×
276
            default:
277
              break;
×
278
          }
279
          break;
×
280
        case 4:
281
          if (Number.isFinite(radius2)) {
×
282
            if (olRegularStyle.getAngle() === 0) {
×
283
              if (olRegularStyle.getRadius2() === 0) {
×
284
                markSymbolizer.wellKnownName = 'shape://plus';
×
285
              } else {
286
                markSymbolizer.wellKnownName = 'star_diamond';
×
287
              }
288
            } else {
289
              markSymbolizer.wellKnownName = 'x';
×
290
            }
291
          } else {
292
            if (olRegularStyle.getAngle() === 0) {
×
293
              markSymbolizer.wellKnownName = 'diamond';
×
294
            } else {
295
              markSymbolizer.wellKnownName = 'square';
×
296
            }
297
          }
298
          break;
×
299
        case 5:
300
          if (Number.isFinite(radius2)) {
×
301
            markSymbolizer.wellKnownName = 'star';
×
302
          } else {
303
            markSymbolizer.wellKnownName = 'pentagon';
×
304
          }
305
          break;
×
306
        case 6:
307
          markSymbolizer.wellKnownName = 'hexagon';
×
308
          break;
×
309
        case 8:
310
          markSymbolizer.wellKnownName = 'octagon';
×
311
          break;
×
312
        case 10:
313
          markSymbolizer.wellKnownName = 'decagon';
×
314
          break;
×
315
        default:
316
          throw new Error('Could not parse OlStyle.');
×
317
      }
318
      pointSymbolizer = markSymbolizer;
×
319
    } else if (olStyle.getText() instanceof this.OlStyleTextConstructor) {
46✔
320
      const olTextStyle: OlStyleText = olStyle.getText() as OlStyleText;
2✔
321
      const olFillStyle = olTextStyle.getFill();
2✔
322
      const olStrokeStyle = olTextStyle.getStroke();
2✔
323
      const rotation = olTextStyle.getRotation();
2✔
324
      let char = olTextStyle.getText() || 'a';
2!
325
      const font = olTextStyle.getFont() || '10px sans-serif';
2!
326
      const fontName = OlStyleUtil.getFontNameFromOlFont(font);
2✔
327
      const radius = OlStyleUtil.getSizeFromOlFont(font);
2✔
328
      const offset = [olTextStyle.getOffsetX(), olTextStyle.getOffsetY()];
2✔
329

330
      if (Array.isArray(char)) {
2!
331
        char = char[0];
×
332
      }
333

334
      pointSymbolizer = {
2✔
335
        kind: 'Mark',
336
        wellKnownName: `ttf://${fontName}#0x${char.charCodeAt(0).toString(16)}`,
337
        color: olFillStyle ? OlStyleUtil.getHexColor(olFillStyle.getColor() as string) : undefined,
1!
338
        opacity: olFillStyle ? OlStyleUtil.getOpacity(olFillStyle.getColor() as string) : undefined,
1!
339
        strokeColor: olStrokeStyle ? olStrokeStyle.getColor() as string : undefined,
1!
340
        strokeOpacity: olStrokeStyle ? OlStyleUtil.getOpacity(olStrokeStyle.getColor() as string) : undefined,
1!
341
        strokeWidth: olStrokeStyle ? olStrokeStyle.getWidth() : undefined,
1!
342
        radius: (radius !== 0) ? radius : 5,
1!
343
        // Rotation in openlayers is radians while we use degree
344
        rotate: rotation ? rotation / DEGREES_TO_RADIANS : 0,
1!
345
        offset: offset[0] || offset[1] ? offset : undefined
2!
346
      } as MarkSymbolizer;
347
    } else {
348
      // icon
349
      const olIconStyle = olStyle.getImage() as OlStyleIcon;
44✔
350
      const displacement = olIconStyle.getDisplacement() as [number, number];
44✔
351
      // initialOptions_ as fallback when image is not yet loaded
352
      // this always gets calculated from ol so this might not have been set initially
353
      const size = olIconStyle.getWidth();
44✔
354
      const rotation = olIconStyle.getRotation() / DEGREES_TO_RADIANS;
44✔
355
      const opacity = olIconStyle.getOpacity();
44✔
356

357
      const iconSrc = olIconStyle.getSrc() as string;
44✔
358
      let svgString;
359
      if (iconSrc && iconSrc.startsWith('data:image/svg+xml')) {
44✔
360
        svgString = getDecodedSvg(iconSrc);
40✔
361
      } else if (iconSrc && iconSrc.trim().startsWith('<svg')) {
4!
362
        svgString = iconSrc.trim();
×
363
      }
364

365
      if (svgString) {
44✔
366
        pointSymbolizer = {
40✔
367
          ...getSvgProperties(svgString),
368
          ...OlStyleUtil.checkOpacity(opacity) && { opacity },
20!
369
          ...rotation && { rotate: rotation },
20!
370
          ...(displacement[0] || displacement[1]) && { offset: displacement },
40✔
371
        } as MarkSymbolizer;
372
      } else {
373
        // initialOptions_ as fallback when image is not yet loaded
374
        const image = this.getImageFromIconStyle(olIconStyle);
4✔
375

376
        pointSymbolizer = {
4✔
377
          kind: 'Icon',
378
          image,
379
          size,
380
          ...OlStyleUtil.checkOpacity(opacity) && { opacity },
3✔
381
          ...rotation && { rotate: rotation },
3✔
382
          ...(displacement[0] || displacement[1]) && { offset: displacement },
4✔
383
        } as IconSymbolizer;
384
      }
385

386
    }
387
    return pointSymbolizer;
46✔
388
  }
389

390
  /**
391
   *
392
   * @param olIconStyle An ol style Icon representation
393
   * @returns A string or Sprite configuration
394
   */
395
  getImageFromIconStyle(olIconStyle: OlStyleIcon): IconSymbolizer['image'] {
396
    const size = olIconStyle.getSize();
4✔
397
    if (Array.isArray(size)) {
4✔
398
      // TODO: create getters (and setters?) in openlayers
399
      // @ts-expect-error offset_ is private
400
      const position = olIconStyle.offset_ as [number, number];
2✔
401
      // @ts-expect-error offsetOrigin_ is private
402
      const offsetOrigin = olIconStyle.offsetOrigin_ as string;
2✔
403
      if (offsetOrigin && offsetOrigin !== 'top-left') {
2!
404
        throw new Error(`Offset origin ${offsetOrigin} not supported`);
×
405
      }
406

407
      return {
2✔
408
        source: olIconStyle.getSrc()!,
409
        position,
410
        size: size as [number, number]
411
      };
412
    } else {
413
      return olIconStyle.getSrc() ? olIconStyle.getSrc() : undefined;
2!
414
    }
415

416
  }
417

418
  /**
419
   * Get the GeoStyler-Style LineSymbolizer from an OpenLayers Style object.
420
   *
421
   * @param olStyle The OpenLayers Style object
422
   * @return The GeoStyler-Style LineSymbolizer
423
   */
424
  getLineSymbolizerFromOlStyle(olStyle: OlStyle): LineSymbolizer {
425
    const olStrokeStyle = olStyle.getStroke();
4✔
426
    // getLineDash returns null not undefined. So we have to double check
427
    const dashArray = olStrokeStyle ? olStrokeStyle.getLineDash() : undefined;
4!
428

429
    return {
4✔
430
      kind: 'Line',
431
      color: olStrokeStyle ? OlStyleUtil.getHexColor(olStrokeStyle.getColor() as string) as string : undefined,
2!
432
      opacity: olStrokeStyle ? OlStyleUtil.getOpacity(olStrokeStyle.getColor() as string) : undefined,
2!
433
      width: olStrokeStyle ? olStrokeStyle.getWidth() : undefined,
2!
434
      cap: olStrokeStyle ? olStrokeStyle.getLineCap() as LineSymbolizer['cap'] : 'butt',
2!
435
      join: olStrokeStyle ? olStrokeStyle.getLineJoin() as LineSymbolizer['join'] : 'miter',
2!
436
      dasharray: dashArray ? dashArray : undefined,
2✔
437
      dashOffset: olStrokeStyle ? olStrokeStyle.getLineDashOffset() : undefined
2!
438
    };
439
  }
440

441
  /**
442
   * Get the GeoStyler-Style FillSymbolizer from an OpenLayers Style object.
443
   *
444
   * PolygonSymbolizer Stroke is just partially supported.
445
   *
446
   * @param olStyle The OpenLayers Style object
447
   * @return The GeoStyler-Style FillSymbolizer
448
   */
449
  getFillSymbolizerFromOlStyle(olStyle: OlStyle): FillSymbolizer {
450
    const olFillStyle = olStyle.getFill();
6✔
451
    const olStrokeStyle = olStyle.getStroke();
6✔
452
    // getLineDash returns null not undefined. So we have to double check
453
    const outlineDashArray = olStrokeStyle ? olStrokeStyle.getLineDash() : undefined;
6✔
454

455
    const symbolizer: FillSymbolizer = {
6✔
456
      kind: 'Fill'
457
    };
458

459
    if (olFillStyle) {
6!
460
      symbolizer.color = OlStyleUtil.getHexColor(olFillStyle.getColor() as string);
6✔
461
    }
462
    if (olFillStyle) {
6!
463
      symbolizer.fillOpacity = OlStyleUtil.getOpacity(olFillStyle.getColor() as string);
6✔
464
    }
465
    if (olStrokeStyle) {
6✔
466
      symbolizer.outlineColor = OlStyleUtil.getHexColor(olStrokeStyle.getColor() as string);
2✔
467
    }
468
    if (outlineDashArray) {
6✔
469
      symbolizer.outlineDasharray = outlineDashArray;
2✔
470
    }
471
    if (olStrokeStyle) {
6✔
472
      symbolizer.outlineOpacity = OlStyleUtil.getOpacity(olStrokeStyle.getColor() as string);
2✔
473
    }
474
    if (olStrokeStyle && olStrokeStyle.getWidth()) {
6✔
475
      symbolizer.outlineWidth = olStrokeStyle.getWidth();
2✔
476
    }
477
    return symbolizer;
6✔
478

479
  }
480

481
  /**
482
   * Get the GeoStyler-Style TextSymbolizer from an OpenLayers Style object.
483
   *
484
   *
485
   * @param olStyle The OpenLayers Style object
486
   * @return The GeoStyler-Style TextSymbolizer
487
   */
488
  getTextSymbolizerFromOlStyle(olStyle: OlStyle): TextSymbolizer {
489
    const olTextStyle = olStyle.getText();
14✔
490
    if (!olTextStyle) {
14!
491
      throw new Error('Could not get text from olStyle.');
×
492
    }
493
    const olFillStyle = olTextStyle.getFill();
14✔
494
    const olStrokeStyle = olTextStyle.getStroke();
14✔
495
    const offsetX = olTextStyle.getOffsetX();
14✔
496
    const offsetY = olTextStyle.getOffsetY();
14✔
497
    const font = olTextStyle.getFont();
14✔
498
    const rotation = olTextStyle.getRotation();
14✔
499
    const allowOverlap = olTextStyle.getOverflow() ? olTextStyle.getOverflow() : undefined;
14!
500
    const placement = olTextStyle.getPlacement();
14✔
501
    const repeat = olTextStyle.getRepeat();
14✔
502
    const text = olTextStyle.getText();
14✔
503
    const label = Array.isArray(text) ? text[0] : text;
14!
504
    let fontSize = Infinity;
14✔
505
    let fontFamily: string[]|undefined = undefined;
14✔
506
    let fontWeight: 'normal' | 'bold' | undefined = undefined;
14✔
507
    let fontStyle: 'normal' | 'italic' | 'oblique' | undefined = undefined;
14✔
508
    if (font) {
14✔
509
      const fontObj = parseFont(font);
10✔
510
      if (fontObj['font-weight']) {
10✔
511
        fontWeight = fontObj['font-weight'];
4✔
512
      }
513
      if (fontObj['font-size']) {
10!
514
        fontSize = parseInt(fontObj['font-size'], 10);
10✔
515
      }
516
      if (fontObj['font-family']) {
10!
517
        const fontFamilies = fontObj['font-family'];
10✔
518
        fontFamily = fontFamilies?.map((f: string) => f.includes(' ') ? '"' + f + '"' : f);
18✔
519
      }
520
      if (fontObj['font-style']) {
10✔
521
        fontStyle = fontObj['font-style'];
2✔
522
      }
523
    }
524

525
    return {
14✔
526
      kind: 'Text',
527
      label,
528
      placement,
529
      repeat,
530
      allowOverlap,
531
      color: olFillStyle ? OlStyleUtil.getHexColor(olFillStyle.getColor() as string) : undefined,
7!
532
      size: isFinite(fontSize) ? fontSize : undefined,
7✔
533
      font: fontFamily,
534
      fontWeight: fontWeight || undefined,
12✔
535
      fontStyle: fontStyle || undefined,
13✔
536
      offset: (offsetX !== undefined) && (offsetY !== undefined) ? [offsetX, offsetY] : [0, 0],
21!
537
      haloColor: olStrokeStyle && olStrokeStyle.getColor() ?
17✔
538
        OlStyleUtil.getHexColor(olStrokeStyle.getColor() as string) : undefined,
539
      haloWidth: olStrokeStyle ? olStrokeStyle.getWidth() : undefined,
7✔
540
      rotate: (rotation !== undefined) ? rotation / DEGREES_TO_RADIANS : undefined
7✔
541
    };
542
  }
543

544
  /**
545
   * Get the GeoStyler-Style Symbolizer from an OpenLayers Style object.
546
   *
547
   * @param olStyles The OpenLayers Style object
548
   * @return The GeoStyler-Style Symbolizer array
549
   */
550
  getSymbolizersFromOlStyle(olStyles: OlStyle[]): Symbolizer[] {
551
    const symbolizers: Symbolizer[] = [];
60✔
552
    olStyles.forEach(olStyle => {
60✔
553
      let symbolizer: Symbolizer;
554
      const styleType: StyleType = this.getStyleTypeFromOlStyle(olStyle);
62✔
555
      switch (styleType) {
62!
556
        case 'Point':
557
          if (olStyle.getText() && !OlStyleUtil.getIsMarkSymbolizerFont((olStyle as any).getText().getFont())) {
52✔
558
            symbolizer = this.getTextSymbolizerFromOlStyle(olStyle);
6✔
559
          } else {
560
            symbolizer = this.getPointSymbolizerFromOlStyle(olStyle);
46✔
561
          }
562
          break;
52✔
563
        case 'Line':
564
          symbolizer = this.getLineSymbolizerFromOlStyle(olStyle);
4✔
565
          break;
4✔
566
        case 'Fill':
567
          symbolizer = this.getFillSymbolizerFromOlStyle(olStyle);
6✔
568
          break;
6✔
569
        default:
570
          throw new Error('Failed to parse SymbolizerKind from OpenLayers Style');
×
571
      }
572
      symbolizers.push(symbolizer);
62✔
573
    });
574

575
    return symbolizers;
60✔
576
  }
577

578
  /**
579
   * Get the GeoStyler-Style Rule from an OpenLayers Style object.
580
   *
581
   * @param olStyles The OpenLayers Style object
582
   * @return The GeoStyler-Style Rule
583
   */
584
  getRuleFromOlStyle(olStyles: OlStyle | OlStyle[]): Rule {
585
    let symbolizers: Symbolizer[];
586
    const name = 'OL Style Rule 0';
60✔
587

588
    if (Array.isArray(olStyles)) {
60✔
589
      symbolizers = this.getSymbolizersFromOlStyle(olStyles);
4✔
590
    } else {
591
      symbolizers = this.getSymbolizersFromOlStyle([olStyles]);
56✔
592
    }
593

594
    return {
60✔
595
      name, symbolizers
596
    };
597
  }
598

599
  /**
600
   * Get the GeoStyler-Style Symbolizer from an OpenLayers Style object.
601
   *
602
   * @param olStyle The OpenLayers Style object
603
   * @return The GeoStyler-Style Symbolizer
604
   */
605
  getStyleTypeFromOlStyle(olStyle: OlStyle): StyleType {
606
    let styleType: StyleType;
607

608
    if (olStyle.getImage() instanceof this.OlStyleImageConstructor) {
62✔
609
      styleType = 'Point';
44✔
610
    } else if (olStyle.getText() instanceof this.OlStyleTextConstructor) {
18✔
611
      styleType = 'Point';
8✔
612
    } else if (olStyle.getFill() instanceof this.OlStyleFillConstructor) {
10✔
613
      styleType = 'Fill';
6✔
614
    } else if (olStyle.getStroke() && !olStyle.getFill()) {
4!
615
      styleType = 'Line';
4✔
616
    } else {
617
      throw new Error('StyleType could not be detected');
×
618
    }
619

620
    return styleType;
62✔
621
  }
622

623
  /**
624
   * Get the GeoStyler-Style Style from an OpenLayers Style object.
625
   *
626
   * @param olStyle The OpenLayers Style object
627
   * @return The GeoStyler-Style Style
628
   */
629
  olStyleToGeoStylerStyle(olStyle: OlStyle | OlStyle[]): Style {
630
    const name = 'OL Style';
60✔
631
    const rule = this.getRuleFromOlStyle(olStyle);
60✔
632
    return {
60✔
633
      name,
634
      rules: [rule]
635
    };
636
  }
637

638
  /**
639
   * The readStyle implementation of the GeoStyler-Style StyleParser interface.
640
   * It reads an OpenLayers Style, an array of OpenLayers Styles or an olParserStyleFct and returns a Promise.
641
   *
642
   * The Promise itself resolves with a GeoStyler-Style Style.
643
   *
644
   * @param olStyle The style to be parsed
645
   * @return The Promise resolving with the GeoStyler-Style Style
646
   */
647
  readStyle(olStyle: OlStyleLike): Promise<ReadStyleResult> {
648
    return new Promise<ReadStyleResult>((resolve) => {
60✔
649
      try {
60✔
650
        if (this.isOlParserStyleFct(olStyle)) {
60!
651
          resolve({
×
652
            output: olStyle.__geoStylerStyle
653
          });
654
        } else {
655
          olStyle = olStyle as OlStyle | OlStyle[];
60✔
656
          const geoStylerStyle: Style = this.olStyleToGeoStylerStyle(olStyle);
60✔
657
          const unsupportedProperties = this.checkForUnsupportedProperties(geoStylerStyle);
60✔
658
          resolve({
60✔
659
            output: geoStylerStyle,
660
            unsupportedProperties
661
          });
662
        }
663
      } catch (error) {
664
        resolve({
×
665
          errors: [error]
666
        });
667
      }
668
    });
669
  }
670

671
  /**
672
   * The writeStyle implementation of the GeoStyler-Style StyleParser interface.
673
   * It reads a GeoStyler-Style Style and returns a Promise.
674
   * The Promise itself resolves one of three types
675
   *
676
   * 1. OlStyle if input Style consists of
677
   *    one rule with one symbolizer, no filter, no scaleDenominator, no TextSymbolizer
678
   * 2. OlStyle[] if input Style consists of
679
   *    one rule with multiple symbolizers, no filter, no scaleDenominator, no TextSymbolizer
680
   * 3. OlParserStyleFct for everything else
681
   *
682
   * @param geoStylerStyle A GeoStyler-Style Style.
683
   * @return The Promise resolving with one of above mentioned style types.
684
   */
685
  writeStyle(geoStylerStyle: Style): Promise<WriteStyleResult<OlStyle | OlStyle[] | OlParserStyleFct>> {
686
    return new Promise<WriteStyleResult>((resolve) => {
102✔
687
      const clonedStyle = structuredClone(geoStylerStyle);
102✔
688
      const unsupportedProperties = this.checkForUnsupportedProperties(clonedStyle);
102✔
689
      try {
102✔
690
        const olStyle = this.getOlStyleTypeFromGeoStylerStyle(clonedStyle);
102✔
691
        resolve({
102✔
692
          output: olStyle,
693
          unsupportedProperties,
694
          warnings: unsupportedProperties && ['Your style contains unsupportedProperties!']
63✔
695
        });
696
      } catch (error) {
697
        resolve({
×
698
          errors: [error]
699
        });
700
      }
701
    });
702
  }
703

704
  checkForUnsupportedProperties(geoStylerStyle: Style): UnsupportedProperties | undefined {
705
    const capitalizeFirstLetter = (a: string) => a[0].toUpperCase() + a.slice(1);
190✔
706
    const unsupportedProperties: UnsupportedProperties = {};
162✔
707
    geoStylerStyle.rules.forEach(rule => {
162✔
708
      // ScaleDenominator and Filters are completly supported so we just check for symbolizers
709
      rule.symbolizers.forEach(symbolizer => {
182✔
710
        const key = capitalizeFirstLetter(`${symbolizer.kind}Symbolizer`);
190✔
711
        const value = this.unsupportedProperties?.Symbolizer?.[key as SymbolizerKeyType];
190✔
712
        if (value) {
190!
713
          if (typeof value === 'string') {
190!
714
            if (!unsupportedProperties.Symbolizer) {
×
715
              unsupportedProperties.Symbolizer = {};
×
716
            }
717
            unsupportedProperties.Symbolizer[key as SymbolizerKeyType] = value;
×
718
          } else {
719
            Object.keys(symbolizer).forEach(property => {
190✔
720
              if (value[property]) {
1,010✔
721
                if (!unsupportedProperties.Symbolizer) {
36✔
722
                  unsupportedProperties.Symbolizer = {};
34✔
723
                }
724
                if (!unsupportedProperties.Symbolizer[key as SymbolizerKeyType]) {
36!
725
                  (unsupportedProperties.Symbolizer as any)[key] = {};
36✔
726
                }
727
                unsupportedProperties.Symbolizer[key as SymbolizerKeyType][property] = value[property];
36✔
728
              }
729
            });
730
          }
731
        }
732
      });
733
    });
734
    if (Object.keys(unsupportedProperties).length > 0) {
162✔
735
      return unsupportedProperties;
34✔
736
    }
737
    return undefined;
128✔
738
  }
739

740
  /**
741
   * Decides which OlStyleType should be returned depending on given geoStylerStyle.
742
   * Three OlStyleTypes are possible:
743
   *
744
   * 1. OlStyle if input Style consists of
745
   *    one rule with one symbolizer, no filter, no scaleDenominator, no TextSymbolizer
746
   * 2. OlStyle[] if input Style consists of
747
   *    one rule with multiple symbolizers, no filter, no scaleDenominator, no TextSymbolizer
748
   * 3. OlParserStyleFct for everything else
749
   *
750
   * @param geoStylerStyle A GeoStyler-Style Style
751
   */
752
  getOlStyleTypeFromGeoStylerStyle(geoStylerStyle: Style): OlStyle | OlStyle[] | OlParserStyleFct {
753
    const rules = geoStylerStyle.rules;
102✔
754
    const nrRules = rules.length;
102✔
755
    if (nrRules === 1) {
102✔
756
      const hasFilter = geoStylerStyle?.rules?.[0]?.filter !== undefined ? true : false;
84!
757
      const hasMinScale = geoStylerStyle?.rules?.[0]?.scaleDenominator?.min !== undefined ? true : false;
84✔
758
      const hasMaxScale = geoStylerStyle?.rules?.[0]?.scaleDenominator?.max !== undefined ? true : false;
84✔
759
      const hasScaleDenominator = hasMinScale || hasMaxScale ? true : false;
84✔
760
      const hasFunctions = OlStyleUtil.containsGeoStylerFunctions(geoStylerStyle);
84✔
761
      const hasGraphicStroke = OlGraphicStrokeUtil.containsGraphicStroke(geoStylerStyle);
84✔
762

763
      const nrSymbolizers = geoStylerStyle.rules[0].symbolizers.length;
84✔
764
      const hasTextSymbolizer = rules[0].symbolizers.some((symbolizer: Symbolizer) => {
84✔
765
        return symbolizer.kind === 'Text';
90✔
766
      });
767
      const hasDynamicIconSymbolizer = rules[0].symbolizers.some((symbolizer: Symbolizer) => {
84✔
768
        return symbolizer.kind === 'Icon' && typeof(symbolizer.image) === 'string' && symbolizer.image.includes('{{');
90✔
769
      });
770
      if (
84✔
771
        !hasFilter
222✔
772
        && !hasScaleDenominator
773
        && !hasTextSymbolizer
774
        && !hasDynamicIconSymbolizer
775
        && !hasFunctions
776
        && !hasGraphicStroke
777
      ) {
778
        if (nrSymbolizers === 1) {
56✔
779
          return this.geoStylerStyleToOlStyle(geoStylerStyle);
50✔
780
        } else {
781
          return this.geoStylerStyleToOlStyleArray(geoStylerStyle);
6✔
782
        }
783
      } else {
784
        return this.geoStylerStyleToOlParserStyleFct(geoStylerStyle);
28✔
785
      }
786
    } else {
787
      return this.geoStylerStyleToOlParserStyleFct(geoStylerStyle);
18✔
788
    }
789
  }
790

791
  /**
792
   * Parses the first symbolizer of the first rule of a GeoStyler-Style Style.
793
   *
794
   * @param geoStylerStyle GeoStyler-Style Style
795
   * @return An OpenLayers Style Object
796
   */
797
  geoStylerStyleToOlStyle(geoStylerStyle: Style): OlStyle {
798
    const rule = geoStylerStyle.rules[0];
50✔
799
    const symbolizer = rule.symbolizers[0];
50✔
800
    const olSymbolizer = this.getOlSymbolizerFromSymbolizer(symbolizer);
50✔
801
    return olSymbolizer;
50✔
802
  }
803

804
  /**
805
   * Parses all symbolizers of the first rule of a GeoStyler-Style Style.
806
   *
807
   * @param geoStylerStyle GeoStyler-Style Style
808
   * @return An array of OpenLayers Style Objects
809
   */
810
  geoStylerStyleToOlStyleArray(geoStylerStyle: Style): OlStyle[] {
811
    const rule = geoStylerStyle.rules[0];
6✔
812
    const olStyles: any[] = [];
6✔
813
    rule.symbolizers.forEach((symbolizer: Symbolizer) => {
6✔
814
      const olSymbolizer: any = this.getOlSymbolizerFromSymbolizer(symbolizer);
12✔
815
      olStyles.push(olSymbolizer);
12✔
816
    });
817
    return olStyles;
6✔
818
  }
819

820
  /**
821
   * Get the OpenLayers Style object from an GeoStyler-Style Style
822
   *
823
   * @param geoStylerStyle A GeoStyler-Style Style.
824
   * @return An OlParserStyleFct
825
   */
826
  geoStylerStyleToOlParserStyleFct(geoStylerStyle: Style): OlParserStyleFct {
827
    const rules = structuredClone(geoStylerStyle.rules);
46✔
828
    const olStyle = (feature: any, resolution: number): any[] => {
46✔
829
      const styles: any[] = [];
70✔
830

831
      // calculate scale for resolution (from ol-util MapUtil)
832
      const dpi = 25.4 / 0.28;
70✔
833
      const mpu = METERS_PER_UNIT.m;
70✔
834
      const inchesPerMeter = 39.37;
70✔
835
      const scale = resolution * mpu * inchesPerMeter * dpi;
70✔
836

837
      rules.forEach((rule: Rule) => {
70✔
838
        // handling scale denominator
839
        let minScale = rule?.scaleDenominator?.min;
112✔
840
        let maxScale = rule?.scaleDenominator?.max;
112✔
841
        let isWithinScale = true;
112✔
842
        if (minScale || maxScale) {
112✔
843
          minScale = isGeoStylerFunction(minScale) ? OlStyleUtil.evaluateNumberFunction(minScale) : minScale;
28!
844
          maxScale = isGeoStylerFunction(maxScale) ? OlStyleUtil.evaluateNumberFunction(maxScale) : maxScale;
28!
845
          if (minScale && scale < minScale) {
28✔
846
            isWithinScale = false;
4✔
847
          }
848
          if (maxScale && scale >= maxScale) {
28✔
849
            isWithinScale = false;
10✔
850
          }
851
        }
852

853
        // handling filter
854
        let matchesFilter = false;
112✔
855
        if (!rule.filter) {
112✔
856
          matchesFilter = true;
78✔
857
        } else {
858
          try {
34✔
859
            matchesFilter = this.geoStylerFilterToOlParserFilter(feature, rule.filter);
34✔
860
          } catch {
861
            matchesFilter = false;
2✔
862
          }
863
        }
864

865
        if (isWithinScale && matchesFilter) {
112✔
866
          rule.symbolizers.forEach((symb: Symbolizer) => {
76✔
867
            if (symb.visibility === false) {
76!
868
              styles.push(null);
×
869
            }
870

871
            if (isGeoStylerBooleanFunction(symb.visibility)) {
76!
872
              const visibility = OlStyleUtil.evaluateBooleanFunction(symb.visibility);
×
873
              if (!visibility) {
×
874
                styles.push(null);
×
875
              }
876
            }
877

878
            const olSymbolizer: any = this.getOlSymbolizerFromSymbolizer(symb, feature, resolution);
76✔
879
            // either an OlStyle, OlStyle[] or an ol.StyleFunction. OpenLayers only accepts an array
880
            // of OlStyles, not ol.StyleFunctions.
881
            // So we have to check it and in case of an ol.StyleFunction call that function
882
            // and add the returned style to const styles.
883
            if (typeof olSymbolizer === 'function') {
76✔
884
              const styleFromFct: any = olSymbolizer(feature, resolution);
6✔
885
              styles.push(styleFromFct);
6✔
886
            } else if (Array.isArray(olSymbolizer)) {
70✔
887
              olSymbolizer.forEach((s) => {
4✔
888
                if (typeof s === 'function') {
10!
NEW
889
                  const styleFromFct: any = s(feature, resolution);
×
NEW
890
                  styles.push(styleFromFct);
×
891
                } else {
892
                  styles.push(s);
10✔
893
                }
894
              });
895
            } else {
896
              styles.push(olSymbolizer);
66✔
897
            }
898
          });
899
        }
900
      });
901
      return styles;
70✔
902
    };
903
    const olStyleFct: OlParserStyleFct = olStyle as OlParserStyleFct;
46✔
904
    olStyleFct.__geoStylerStyle = geoStylerStyle;
46✔
905
    return olStyleFct;
46✔
906
  }
907

908
  /**
909
   * Checks if a feature matches given filter expression(s)
910
   * @param feature ol.Feature
911
   * @param filter Filter
912
   * @return boolean true if feature matches filter expression
913
   */
914
  geoStylerFilterToOlParserFilter(feature: any, filter: Filter): boolean {
915
    const operatorMapping: any = {
84✔
916
      '&&': true,
917
      '||': true,
918
      '!': true
919
    };
920

921
    let matchesFilter = true;
84✔
922
    if (isGeoStylerBooleanFunction(filter)) {
84!
923
      return OlStyleUtil.evaluateBooleanFunction(filter, feature);
×
924
    }
925
    if (filter === true || filter === false) {
84!
926
      return filter;
×
927
    }
928
    const operator: Operator = filter[0];
84✔
929
    let isNestedFilter = false;
84✔
930
    if (operatorMapping[operator]) {
84✔
931
      isNestedFilter = true;
26✔
932
    }
933
    try {
84✔
934
      if (isNestedFilter) {
84✔
935
        let intermediate: boolean;
936
        let restFilter: any;
937
        switch (filter[0]) {
26!
938
          case '&&':
939
            intermediate = true;
14✔
940
            restFilter = filter.slice(1);
14✔
941
            restFilter.forEach((f: Filter) => {
14✔
942
              if (!this.geoStylerFilterToOlParserFilter(feature, f)) {
32✔
943
                intermediate = false;
8✔
944
              }
945
            });
946
            matchesFilter = intermediate;
12✔
947
            break;
12✔
948
          case '||':
949
            intermediate = false;
6✔
950
            restFilter = filter.slice(1);
6✔
951
            restFilter.forEach((f: Filter) => {
6✔
952
              if (this.geoStylerFilterToOlParserFilter(feature, f)) {
12!
953
                intermediate = true;
12✔
954
              }
955
            });
956
            matchesFilter = intermediate;
6✔
957
            break;
6✔
958
          case '!':
959
            matchesFilter = !this.geoStylerFilterToOlParserFilter(feature, filter[1]);
6✔
960
            break;
6✔
961
          default:
962
            throw new Error('Cannot parse Filter. Unknown combination or negation operator.');
×
963
        }
964
      } else {
965
        let arg1: any;
966
        if (isGeoStylerFunction(filter[1])) {
58✔
967
          arg1 = OlStyleUtil.evaluateFunction(filter[1], feature);
28✔
968
        } else {
969
          arg1 = feature.get(filter[1]);
30✔
970
        }
971
        let arg2: any;
972
        if (isGeoStylerFunction(filter[2])) {
58✔
973
          arg2 = OlStyleUtil.evaluateFunction(filter[2], feature);
24✔
974
        } else {
975
          arg2 = filter[2];
34✔
976
        }
977
        switch (filter[0]) {
58!
978
          case '==':
979
            matchesFilter = ('' + arg1) === ('' + arg2);
20✔
980
            break;
20✔
981
          case '*=':
982
            // inspired by
983
            // https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/String/includes#Polyfill
984
            if (typeof arg2 === 'string' && typeof arg1 === 'string') {
×
985
              if (arg2.length > arg1.length) {
×
986
                matchesFilter = false;
×
987
              } else {
988
                matchesFilter = arg1.indexOf(arg2) !== -1;
×
989
              }
990
            }
991
            break;
×
992
          case '!=':
993
            matchesFilter = ('' + arg1) !== ('' + arg2);
×
994
            break;
×
995
          case '<':
996
            matchesFilter = Number(arg1) < Number(arg2);
12✔
997
            break;
12✔
998
          case '<=':
999
            matchesFilter = Number(arg1) <= Number(arg2);
6✔
1000
            break;
6✔
1001
          case '>':
1002
            matchesFilter = Number(arg1) > Number(arg2);
6✔
1003
            break;
6✔
1004
          case '>=':
1005
            matchesFilter = Number(arg1) >= Number(arg2);
12✔
1006
            break;
12✔
1007
          default:
1008
            throw new Error('Cannot parse Filter. Unknown comparison operator.');
2✔
1009
        }
1010
      }
1011
    } catch {
1012
      throw new Error('Cannot parse Filter. Invalid structure.');
4✔
1013
    }
1014
    return matchesFilter;
80✔
1015
  }
1016

1017
  /**
1018
   * Get the OpenLayers Style object or an OL StyleFunction from an
1019
   * GeoStyler-Style Symbolizer.
1020
   *
1021
   * @param symbolizer A GeoStyler-Style Symbolizer.
1022
   * @return The OpenLayers Style object or a StyleFunction
1023
   */
1024
  getOlSymbolizerFromSymbolizer(symbolizer: Symbolizer, feature?: OlFeature, resolution?: number): OlStyle {
1025
    let olSymbolizer: any;
1026
    symbolizer = structuredClone(symbolizer);
142✔
1027

1028
    switch (symbolizer.kind) {
142!
1029
      case 'Mark':
1030
        olSymbolizer = this.getOlPointSymbolizerFromMarkSymbolizer(symbolizer, feature);
82✔
1031
        break;
82✔
1032
      case 'Icon':
1033
        olSymbolizer = this.getOlIconSymbolizerFromIconSymbolizer(symbolizer, feature);
8✔
1034
        break;
8✔
1035
      case 'Text':
1036
        olSymbolizer = this.getOlTextSymbolizerFromTextSymbolizer(symbolizer, feature);
20✔
1037
        break;
20✔
1038
      case 'Line':
1039
        olSymbolizer = this.getOlLineSymbolizerFromLineSymbolizer(symbolizer, feature, resolution);
18✔
1040
        break;
18✔
1041
      case 'Fill':
1042
        olSymbolizer = this.getOlPolygonSymbolizerFromFillSymbolizer(symbolizer, feature);
14✔
1043
        break;
14✔
1044
      default: {
1045
        // Return the OL default style since the TS type binding does not allow
1046
        // us to set olSymbolizer to undefined
1047
        const fill = new this.OlStyleFillConstructor({
×
1048
          color: 'rgba(255,255,255,0.4)'
1049
        });
1050
        const stroke = new this.OlStyleStrokeConstructor({
×
1051
          color: '#3399CC',
1052
          width: 1.25
1053
        });
1054
        olSymbolizer = new this.OlStyleConstructor({
×
1055
          image: new this.OlStyleCircleConstructor({
1056
            fill: fill,
1057
            stroke: stroke,
1058
            radius: 5
1059
          }),
1060
          fill: fill,
1061
          stroke: stroke
1062
        });
1063
        break;
×
1064
      }
1065
    }
1066

1067
    return olSymbolizer;
142✔
1068
  }
1069

1070
  /**
1071
   * Get the OL Style object from an GeoStyler-Style MarkSymbolizer.
1072
   *
1073
   * @param markSymbolizer A GeoStyler-Style MarkSymbolizer.
1074
   * @return The OL Style object
1075
   */
1076
  getOlPointSymbolizerFromMarkSymbolizer(
1077
    markSymbolizer: MarkSymbolizer,
1078
    feature?: OlFeature
1079
  ): OlStyle {
1080
    for (const key of Object.keys(markSymbolizer)) {
82✔
1081
      if (isGeoStylerFunction(markSymbolizer[key as keyof MarkSymbolizer])) {
348✔
1082
        (markSymbolizer as any)[key] = OlStyleUtil.evaluateFunction((markSymbolizer as any)[key], feature);
8✔
1083
      }
1084
    }
1085

1086
    const strokeColor = markSymbolizer.strokeColor as string;
82✔
1087
    const strokeOpacity = markSymbolizer.strokeOpacity as number;
82✔
1088
    const strokeWidth = markSymbolizer.strokeWidth as number;
82✔
1089
    const fillColor = markSymbolizer.color as string;
82✔
1090
    const fillOpacity = markSymbolizer.fillOpacity as number;
82✔
1091
    const rotation = Number(markSymbolizer.rotate) * DEGREES_TO_RADIANS;
82✔
1092
    const displacement = markSymbolizer.offset as [number, number];
82✔
1093

1094
    let olStyle: any;
1095

1096
    if (isPointDefinedAsSvg(markSymbolizer.wellKnownName)) {
82✔
1097
      const opacity = markSymbolizer.opacity as number;
80✔
1098

1099
      const svg = getPointSvg(markSymbolizer);
80✔
1100
      olStyle = new this.OlStyleConstructor({
80✔
1101
        image: new this.OlStyleIconConstructor({
1102
          crossOrigin: 'anonymous',
1103
          ...displacement && { displacement },
42✔
1104
          ...OlStyleUtil.checkOpacity(opacity) && { opacity },
40!
1105
          ...rotation && { rotation },
42✔
1106
          scale: 1,
1107
          src: getEncodedSvg(svg)
1108
        })
1109
      });
1110
    } else if (OlStyleUtil.getIsFontGlyphBased(markSymbolizer)) {
2!
1111
      const strokeRgbaColor = (strokeColor && OlStyleUtil.checkOpacity(strokeOpacity) ?
2!
1112
        OlStyleUtil.getRgbaColor(strokeColor, strokeOpacity) :
1113
        strokeColor) as string;
1114
      const fillRgbaColor = (fillColor && OlStyleUtil.checkOpacity(fillOpacity) ?
2!
1115
        OlStyleUtil.getRgbaColor(fillColor, fillOpacity) :
1116
        fillColor) as string;
1117

1118
      const stroke = new this.OlStyleStrokeConstructor({
2✔
1119
        ...strokeRgbaColor && { color: strokeRgbaColor as string },
2✔
1120
        ...strokeWidth && { width: strokeWidth }
1!
1121
      });
1122

1123
      const fill = new this.OlStyleFillConstructor({
2✔
1124
        ...fillRgbaColor && { color: fillRgbaColor as string }
2✔
1125
      });
1126

1127
      olStyle = new this.OlStyleConstructor({
2✔
1128
        text: new this.OlStyleTextConstructor({
1129
          text: OlStyleUtil.getCharacterForMarkSymbolizer(markSymbolizer),
1130
          font: OlStyleUtil.getTextFontForMarkSymbolizer(markSymbolizer),
1131
          ...fill && { fill },
2✔
1132
          ...displacement && { offsetX: displacement[0] },
2✔
1133
          ...displacement && { offsetY: displacement[1] },
2✔
1134
          ...stroke && { stroke },
2✔
1135
          ...rotation && { rotation },
1!
1136
        })
1137
      });
1138
    } else {
1139
      throw new Error('MarkSymbolizer cannot be parsed. Unsupported WellKnownName.');
×
1140
    }
1141

1142
    return olStyle;
82✔
1143
  }
1144

1145
  /**
1146
   * Get the OL Style object  from an GeoStyler-Style IconSymbolizer.
1147
   *
1148
   * @param symbolizer  A GeoStyler-Style IconSymbolizer.
1149
   * @return The OL Style object
1150
   */
1151
  getOlIconSymbolizerFromIconSymbolizer(
1152
    symbolizer: IconSymbolizer,
1153
    feat?: OlFeature
1154
  ): OlStyle | OlStyleIcon | OlStyleFunction {
1155
    for (const key of Object.keys(symbolizer)) {
8✔
1156
      if (isGeoStylerFunction(symbolizer[key as keyof IconSymbolizer])) {
28!
1157
        (symbolizer as any)[key] = OlStyleUtil.evaluateFunction((symbolizer as any)[key], feat);
×
1158
      }
1159
    }
1160

1161
    const baseProps: OlStyleIconOptions = {
8✔
1162
      src: isSprite(symbolizer.image) ? symbolizer.image.source as string : symbolizer.image as string,
4✔
1163
      crossOrigin: 'anonymous',
1164
      opacity: symbolizer.opacity as number,
1165
      width: symbolizer.size as number,
1166
      // Rotation in openlayers is radians while we use degree
1167
      rotation: (typeof(symbolizer.rotate) === 'number' ? symbolizer.rotate * DEGREES_TO_RADIANS : undefined) as number,
4✔
1168
      displacement: symbolizer.offset as [number, number],
1169
      size: isSprite(symbolizer.image) ? symbolizer.image.size as [number, number] : undefined,
4✔
1170
      offset: isSprite(symbolizer.image) ? symbolizer.image.position as [number, number] : undefined,
4✔
1171
    };
1172

1173
    // check if IconSymbolizer.image contains a placeholder
1174
    const prefix = '\\{\\{';
8✔
1175
    const suffix = '\\}\\}';
8✔
1176
    const regExp = new RegExp(prefix + '.*?' + suffix, 'g');
8✔
1177
    const regExpRes = typeof(symbolizer.image) === 'string' ? symbolizer.image.match(regExp) : null;
8✔
1178
    if (regExpRes) {
8✔
1179
      // if it contains a placeholder
1180
      // return olStyleFunction
1181
      const olPointStyledIconFn = (feature: any) => {
2✔
1182
        let src: string = OlStyleUtil.resolveAttributeTemplate(feature, symbolizer.image as string, '');
2✔
1183
        // src can't be blank, would trigger ol errors
1184
        if (!src) {
2!
1185
          src = symbolizer.image + '';
×
1186
        }
1187
        let image;
1188
        if (this.olIconStyleCache[src]) {
2!
1189
          image = this.olIconStyleCache[src];
×
1190
          if (baseProps.rotation !== undefined) {
×
1191
            image.setRotation(baseProps.rotation);
×
1192
          }
1193
          if (baseProps.opacity !== undefined) {
×
1194
            image.setOpacity(baseProps.opacity);
×
1195
          }
1196
        } else {
1197
          image = new this.OlStyleIconConstructor({
2✔
1198
            ...baseProps,
1199
            src // order is important
1200
          });
1201
          this.olIconStyleCache[src] = image;
2✔
1202
        }
1203
        const style = new this.OlStyleConstructor({
2✔
1204
          image
1205
        });
1206
        return style;
2✔
1207
      };
1208
      return olPointStyledIconFn;
2✔
1209
    } else {
1210
      return new this.OlStyleConstructor({
6✔
1211
        image: new this.OlStyleIconConstructor({
1212
          ...baseProps
1213
        })
1214
      });
1215
    }
1216
  }
1217

1218
  /**
1219
   * Get the OL Style object from an GeoStyler-Style LineSymbolizer.
1220
   *
1221
   * @param symbolizer A GeoStyler-Style LineSymbolizer.
1222
   * @return The OL Style object
1223
   */
1224
  getOlLineSymbolizerFromLineSymbolizer(
1225
    symbolizer: LineSymbolizer, feat?: OlFeature, resolution?: number
1226
  ): OlStyle | OlStyleStroke | OlStyle[] {
1227
    // graphicStroke will always be evaluated in a function, so we
1228
    // can assume that the feature is available.
1229
    if (symbolizer.graphicStroke) {
18✔
1230
      // If graphicStroke is set, we ignore unrelated stroke properties
1231
      return this.getOlGraphicStrokeFromGraphicStroke(symbolizer, feat!, resolution!);
4✔
1232
    }
1233
    for (const key of Object.keys(symbolizer)) {
14✔
1234
      if (isGeoStylerFunction(symbolizer[key as keyof LineSymbolizer])) {
90!
1235
        (symbolizer as any)[key] = OlStyleUtil.evaluateFunction((symbolizer as any)[key], feat);
×
1236
      }
1237
    }
1238
    const color = symbolizer.color as string;
14✔
1239
    const opacity = symbolizer.opacity as number;
14✔
1240
    const sColor = (color && opacity !== null && opacity !== undefined) ?
14!
1241
      OlStyleUtil.getRgbaColor(color, opacity) : color;
1242

1243
    return new this.OlStyleConstructor({
14✔
1244
      stroke: new this.OlStyleStrokeConstructor({
1245
        color: sColor,
1246
        width: symbolizer.width as number,
1247
        lineCap: symbolizer.cap as CapType,
1248
        lineJoin: symbolizer.join as JoinType,
1249
        lineDash: symbolizer.dasharray as number[],
1250
        lineDashOffset: symbolizer.dashOffset as number
1251
      })
1252
    });
1253
  }
1254

1255
  getOlGraphicStrokeFromGraphicStroke(
1256
    symbolizer: LineSymbolizer, feat: OlFeature, resolution: number
1257
  ) {
1258
    const geom = feat.getGeometry();
4✔
1259
    if (!geom || !(geom instanceof this.OlLineStringContructor || geom instanceof this.OlMultiLineStringConstructor)) {
4!
NEW
1260
      throw new Error(
×
1261
        'GraphicStroke can only be applied to (Multi-)LineString geometries'
1262
      );
1263
    }
1264

1265
    const graphicStroke = symbolizer.graphicStroke!;
4✔
1266
    const symbolSize = this.getSymbolSizeFromGraphicStroke(graphicStroke, feat);
4✔
1267
    if (symbolSize <= 0) {
4!
NEW
1268
      console.warn('Symbol size must be greater than zero for graphic stroke. No graphic will be drawn.');
×
NEW
1269
      return [];
×
1270
    }
1271
    const symbolRotation = graphicStroke.rotate;
4✔
1272
    const evaluatedSymbolRotation = isGeoStylerFunction(symbolRotation)
4!
1273
      ? OlStyleUtil.evaluateNumberFunction(symbolRotation, feat)
1274
      : symbolRotation ?? 0;
4✔
1275
    // We currently do not support expressions for dasharrays
1276
    const dashArray = symbolizer.dasharray as number[] | undefined;
4✔
1277
    const dashOffset = symbolizer.dashOffset || 0;
4✔
1278
    const evaluatedDashOffset = isGeoStylerFunction(dashOffset)
4!
1279
      ? OlStyleUtil.evaluateNumberFunction(dashOffset, feat)
1280
      : dashOffset;
1281

1282
    const symbolizerGenerator = (modifiedGraphicStroke: any) => {
4✔
1283
      return this.getOlSymbolizerFromSymbolizer(modifiedGraphicStroke, feat, resolution);
4✔
1284
    };
1285

1286
    if (geom instanceof this.OlLineStringContructor) {
4!
1287
      return OlGraphicStrokeUtil.processLineStringGraphicStroke(
4✔
1288
        geom,
1289
        symbolSize,
1290
        resolution,
1291
        dashArray,
1292
        evaluatedDashOffset,
1293
        evaluatedSymbolRotation,
1294
        graphicStroke,
1295
        symbolizerGenerator,
1296
        this.OlPointConstructor
1297
      );
1298
    } else {
1299
      // For every line in the MultiLineString, we start the pattern at the
1300
      // beginning of the line. This means that the pattern can be
1301
      // discontinuous at vertices where the lines connect.
NEW
1302
      return geom.getLineStrings().flatMap(line =>
×
NEW
1303
        OlGraphicStrokeUtil.processLineStringGraphicStroke(
×
1304
          line,
1305
          symbolSize,
1306
          resolution,
1307
          dashArray,
1308
          evaluatedDashOffset,
1309
          evaluatedSymbolRotation,
1310
          graphicStroke,
1311
          symbolizerGenerator,
1312
          this.OlPointConstructor
1313
        )
1314
      );
1315
    }
1316
  }
1317

1318
  /**
1319
   * Get the size of a symbol from graphicStroke.
1320
   * @param graphicStroke The graphicStroke from the LineSymbolizer.
1321
   * @param feat The feature.
1322
   * @returns The size of the symbol in pixels.
1323
   */
1324
  getSymbolSizeFromGraphicStroke(
1325
    graphicStroke: LineSymbolizer['graphicStroke'], feat: OlFeature
1326
  ) {
1327
    let size = 0;
4✔
1328
    if (graphicStroke!.kind === 'Mark') {
4!
1329
      const radius = graphicStroke!.radius;
4✔
1330
      if (isGeoStylerFunction(radius)) {
4!
NEW
1331
        size = OlStyleUtil.evaluateNumberFunction(radius, feat) * 2;
×
1332
      } else {
1333
        size = (radius ?? 0) * 2;
4!
1334
      }
1335
      if (size <= 0) {
4!
NEW
1336
        return size;
×
1337
      }
1338
      const strokeWidth = graphicStroke!.strokeWidth;
4✔
1339
      if (isGeoStylerFunction(strokeWidth)) {
4!
NEW
1340
        size += OlStyleUtil.evaluateNumberFunction(strokeWidth, feat);
×
1341
      } else {
1342
        size += strokeWidth ?? 0;
4✔
1343
      }
NEW
1344
    } else if (graphicStroke!.kind === 'Icon') {
×
NEW
1345
      const iconSize = graphicStroke!.size;
×
NEW
1346
      if (isGeoStylerFunction(iconSize)) {
×
NEW
1347
        size = OlStyleUtil.evaluateNumberFunction(iconSize, feat);
×
1348
      } else {
NEW
1349
        size = iconSize ?? 0;
×
1350
      }
1351
    }
1352
    return size;
4✔
1353
  }
1354

1355
  /**
1356
   * Get the OL Style object from an GeoStyler-Style FillSymbolizer.
1357
   *
1358
   * @param symbolizer A GeoStyler-Style FillSymbolizer.
1359
   * @return The OL Style object
1360
   */
1361
  getOlPolygonSymbolizerFromFillSymbolizer(symbolizer: FillSymbolizer, feat?: OlFeature): OlStyle | OlStyleFill {
1362
    for (const key of Object.keys(symbolizer)) {
14✔
1363
      if (isGeoStylerFunction(symbolizer[key as keyof FillSymbolizer])) {
50✔
1364
        (symbolizer as any)[key] = OlStyleUtil.evaluateFunction((symbolizer as any)[key], feat);
2✔
1365
      }
1366
    }
1367

1368
    const color = symbolizer.color as string;
14✔
1369
    const opacity = symbolizer.fillOpacity as number;
14✔
1370
    const fColor = color && Number.isFinite(opacity)
14✔
1371
      ? OlStyleUtil.getRgbaColor(color, opacity)
1372
      : color;
1373

1374
    let fill = color
14✔
1375
      ? new this.OlStyleFillConstructor({color: fColor})
1376
      : undefined;
1377

1378
    const outlineColor = symbolizer.outlineColor as string;
14✔
1379
    const outlineOpacity = symbolizer.outlineOpacity as number;
14✔
1380
    const oColor = (outlineColor && Number.isFinite(outlineOpacity))
14✔
1381
      ? OlStyleUtil.getRgbaColor(outlineColor, outlineOpacity)
1382
      : outlineColor;
1383

1384
    const stroke = outlineColor || symbolizer.outlineWidth ? new this.OlStyleStrokeConstructor({
14✔
1385
      color: oColor,
1386
      width: symbolizer.outlineWidth as number,
1387
      lineDash: symbolizer.outlineDasharray as number[],
1388
    }) : undefined;
1389

1390
    const olStyle = new OlStyle({
14✔
1391
      fill,
1392
      stroke
1393
    });
1394

1395
    if (symbolizer.graphicFill) {
14✔
1396
      const pattern = this.getOlPatternFromGraphicFill(symbolizer.graphicFill);
2✔
1397
      if (!fill) {
2!
1398
        fill = new OlStyleFill({});
2✔
1399
      }
1400
      if (pattern) {
2!
1401
        fill.setColor(pattern);
2✔
1402
      }
1403
      olStyle.setFill(fill);
2✔
1404
    }
1405

1406
    return olStyle;
14✔
1407
  }
1408

1409
  /**
1410
   * Get the pattern for a graphicFill.
1411
   *
1412
   * This creates a CanvasPattern based on the
1413
   * properties of the given PointSymbolizer. Currently,
1414
   * only IconSymbolizer and MarkSymbolizer are supported.
1415
   *
1416
   * @param graphicFill The Symbolizer that holds the pattern config.
1417
   * @returns The created CanvasPattern, or null.
1418
   */
1419
  getOlPatternFromGraphicFill(graphicFill: PointSymbolizer): CanvasPattern | null {
1420
    if (!isIconSymbolizer(graphicFill) && !isMarkSymbolizer(graphicFill)) {
2!
1421
      return null;
×
1422
    }
1423

1424
    // Temporary canvas.
1425
    // TODO: Can/should we reuse an pre-existing one for efficiency?
1426
    const tmpCanvas: HTMLCanvasElement = document.createElement('canvas');
2✔
1427
    const tmpContext = tmpCanvas.getContext('2d') as CanvasRenderingContext2D;
2✔
1428

1429
    let canvasSize;
1430
    let iconSize: [number, number] = [8, 8];
2✔
1431
    let iconSpacing = 1.5;
2✔
1432
    let scaleFactor = 1;
2✔
1433

1434
    if (isIconSymbolizer(graphicFill)) {
2!
1435
      const graphicFillStyle: any = this.getOlIconSymbolizerFromIconSymbolizer(graphicFill);
×
1436

1437
      const graphicFillImage = graphicFillStyle?.getImage();
×
1438
      graphicFillImage?.load(); // Needed for Icon type images with a remote src
×
1439
      // We can only work with the image once it's loaded
1440
      if (graphicFillImage?.getImageState() !== OlImageState.LOADED) {
×
1441
        return null;
×
1442
      }
1443

1444
      iconSize = graphicFillStyle.getImage().getSize();
×
1445
      canvasSize = iconSize.map(item  => item * iconSpacing);
×
1446
      tmpCanvas.width = canvasSize[0];
×
1447
      tmpCanvas.height = canvasSize[1];
×
1448

1449
      // Create the context where we'll be drawing the style on
1450
      const vectorContext = toContext(tmpContext, {
×
1451
        size: canvasSize,
1452
        pixelRatio: 1
1453
      });
1454

1455
      const pointCoords = canvasSize.map(item  => item / 2);
×
1456
      const pointFeature = new OlFeature(new OlGeomPoint(pointCoords));
×
1457

1458
      vectorContext.drawFeature(pointFeature, graphicFillStyle);
×
1459
      return tmpContext.createPattern(tmpCanvas, 'repeat');
×
1460
    }
1461

1462
    const svgPattern = fillPatternSvg(graphicFill);
2✔
1463

1464
    const isLineSymbol = LINE_WELLKNOWNNAMES.includes(String(graphicFill.wellKnownName));
2✔
1465
    const cleanedWellKnownName = cleanWellKnownName(graphicFill.wellKnownName);
2✔
1466
    const isSimplePatternFill = !graphicFill.strokeWidth && NOFILL_WELLKNOWNNAMES.includes(cleanedWellKnownName);
2✔
1467
    const isDenseFill = cleanedWellKnownName.startsWith('dense');
2✔
1468
    const iconRotation = graphicFill.rotate as number || 0;
2✔
1469
    // Hack to try to join lines for hatch patterns, but space out icon patterns.
1470
    // Diagonal lines still do not render nicely in the corners, due to tiling.
1471
    // TODO: Maybe use VendorOption's to control spacing?
1472
    if (isLineSymbol || isSimplePatternFill || isDenseFill) {
2!
1473
      // Extend lines that aren't horizontal or vertical to be full size of the canvas
1474
      const isNotVerticalOrHorizontal = (iconRotation / DEGREES_TO_RADIANS) % 1 !== 0;
×
1475
      if (isNotVerticalOrHorizontal) {
×
1476
        scaleFactor = Math.sin(iconRotation * DEGREES_TO_RADIANS);
×
1477
      }
1478
      iconSpacing = 1;
×
1479
    }
1480
    const diameter = (graphicFill.radius as number ?? 4) * 2;
2✔
1481
    iconSize = [diameter, diameter];
2✔
1482
    canvasSize = iconSize.map(item  => item * iconSpacing * scaleFactor);
4✔
1483
    tmpCanvas.width = canvasSize[0];
2✔
1484
    tmpCanvas.height = canvasSize[1];
2✔
1485

1486
    drawSvgToCanvas(svgPattern, tmpContext, canvasSize[0], iconRotation);
2✔
1487
    return tmpContext.createPattern(tmpCanvas, 'repeat');
2✔
1488
  }
1489

1490
  /**
1491
   * Get the OL StyleFunction object from an GeoStyler-Style TextSymbolizer.
1492
   *
1493
   * @param {TextSymbolizer} textSymbolizer A GeoStyler-Style TextSymbolizer.
1494
   * @return {object} The OL StyleFunction
1495
   */
1496
  getOlTextSymbolizerFromTextSymbolizer(
1497
    symbolizer: TextSymbolizer,
1498
    feat?: OlFeature
1499
  ): OlStyle | OlStyleText | OlStyleFunction {
1500
    for (const key of Object.keys(symbolizer)) {
20✔
1501
      if (isGeoStylerFunction(symbolizer[key as keyof TextSymbolizer])) {
182!
1502
        (symbolizer as any)[key] = OlStyleUtil.evaluateFunction((symbolizer as any)[key], feat);
×
1503
      }
1504
    }
1505
    const color = symbolizer.color as string;
20✔
1506
    let placement = symbolizer.placement;
20✔
1507
    if (!placement) {
20✔
1508
      // When setting placement it must not be undefined.
1509
      // So we set it to the OL default value.
1510
      placement = 'point';
10✔
1511
    }
1512
    if (placement === 'line-center') {
20✔
1513
      // line-center not supported by OL.
1514
      // So we use the closest supported value.
1515
      placement = 'line';
2✔
1516
    }
1517
    const opacity = symbolizer.opacity as number;
20✔
1518
    const fColor = color && Number.isFinite(opacity)
20!
1519
      ? OlStyleUtil.getRgbaColor(color, opacity)
1520
      : color;
1521

1522
    const haloColor = symbolizer.haloColor as string;
20✔
1523
    const haloWidth = symbolizer.haloWidth as number;
20✔
1524
    const sColor = haloColor && Number.isFinite(opacity)
20!
1525
      ? OlStyleUtil.getRgbaColor(haloColor, opacity)
1526
      : haloColor;
1527
    const baseProps: OlStyleTextOptions = {
20✔
1528
      font: OlStyleUtil.getTextFont(symbolizer),
1529
      fill: new this.OlStyleFillConstructor({
1530
        color: fColor
1531
      }),
1532
      stroke: new this.OlStyleStrokeConstructor({
1533
        color: sColor,
1534
        width: haloWidth ? haloWidth : 0 as number
10✔
1535
      }),
1536
      overflow: symbolizer.allowOverlap as boolean,
1537
      offsetX: (symbolizer.offset ? symbolizer.offset[0] : 0) as number,
10✔
1538
      offsetY: (symbolizer.offset ? symbolizer.offset[1] : 0) as number,
10✔
1539
      rotation: typeof(symbolizer.rotate) === 'number' ? symbolizer.rotate * DEGREES_TO_RADIANS : undefined,
10✔
1540
      placement: placement as 'line' | 'point',
1541
      repeat: symbolizer.repeat as number
1542
      // TODO check why props match
1543
      // textAlign: symbolizer.pitchAlignment,
1544
      // textBaseline: symbolizer.anchor
1545
    };
1546

1547
    // check if TextSymbolizer.label contains a placeholder
1548
    const prefix = '\\{\\{';
20✔
1549
    const suffix = '\\}\\}';
20✔
1550
    const regExp = new RegExp(prefix + '.*?' + suffix, 'g');
20✔
1551
    let regExpRes;
1552
    if (!isGeoStylerStringFunction(symbolizer.label)) {
20!
1553
      regExpRes = symbolizer.label ? symbolizer.label.match(regExp) : null;
20!
1554
    }
1555
    if (regExpRes) {
20✔
1556
      // if it contains a placeholder
1557
      // return olStyleFunction
1558
      const olPointStyledLabelFn = (feature: any) => {
4✔
1559

1560
        const text = new this.OlStyleTextConstructor({
4✔
1561
          text: OlStyleUtil.resolveAttributeTemplate(feature, symbolizer.label as string, ''),
1562
          ...baseProps
1563
        });
1564

1565
        const style = new this.OlStyleConstructor({
4✔
1566
          text: text
1567
        });
1568

1569
        return style;
4✔
1570
      };
1571
      return olPointStyledLabelFn;
4✔
1572
    } else {
1573
      // if TextSymbolizer does not contain a placeholder
1574
      // return OlStyle
1575
      return new this.OlStyleConstructor({
16✔
1576
        text: new this.OlStyleTextConstructor({
1577
          text: symbolizer.label as string,
1578
          ...baseProps
1579
        })
1580
      });
1581
    }
1582
  }
1583

1584
}
1585

1586
export default OlStyleParser;
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