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

geostyler / geostyler-openlayers-parser / 24516225399

16 Apr 2026 02:34PM UTC coverage: 72.334%. First build
24516225399

Pull #934

github

web-flow
Merge 809223fda into 4e9353957
Pull Request #934: fix: evaluating geostyler functions

890 of 1267 branches covered (70.24%)

Branch coverage included in aggregate %.

2 of 6 new or added lines in 1 file covered. (33.33%)

1016 of 1368 relevant lines covered (74.27%)

35.49 hits per line

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

72.9
/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 OlPolygon from 'ol/geom/Polygon';
44
import OlMultiPolygon from 'ol/geom/MultiPolygon';
45
import { METERS_PER_UNIT } from 'ol/proj/Units';
46

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

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

67
type SymbolizerKeyType = keyof UnsupportedProperties['Symbolizer'];
68

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

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

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

163
  title = 'OpenLayers Style Parser';
210✔
164
  olIconStyleCache: any = {};
210✔
165

166
  OlStyleConstructor = OlStyle;
210✔
167
  OlStyleImageConstructor = OlStyleImage;
210✔
168
  OlStyleFillConstructor = OlStyleFill;
210✔
169
  OlStyleStrokeConstructor = OlStyleStroke;
210✔
170
  OlStyleTextConstructor = OlStyleText;
210✔
171
  OlStyleCircleConstructor = OlStyleCircle;
210✔
172
  OlStyleIconConstructor = OlStyleIcon;
210✔
173
  OlStyleRegularshapeConstructor = OlStyleRegularshape;
210✔
174
  OlLineStringContructor = OlLineString;
210✔
175
  OlMultiLineStringConstructor = OlMultiLineString;
210✔
176
  OlPolygonConstructor = OlPolygon;
210✔
177
  OlMultiPolygonConstructor = OlMultiPolygon;
210✔
178
  OlPointConstructor = OlGeomPoint;
210✔
179

180
  constructor(ol?: any) {
181
    if (ol) {
210!
182
      this.OlStyleConstructor = ol.style.Style;
×
183
      this.OlStyleImageConstructor = ol.style.Image;
×
184
      this.OlStyleFillConstructor = ol.style.Fill;
×
185
      this.OlStyleStrokeConstructor = ol.style.Stroke;
×
186
      this.OlStyleTextConstructor = ol.style.Text;
×
187
      this.OlStyleCircleConstructor = ol.style.Circle;
×
188
      this.OlStyleIconConstructor = ol.style.Icon;
×
189
      this.OlStyleRegularshapeConstructor = ol.style.RegularShape;
×
190
      this.OlLineStringContructor = ol.geom.LineString;
×
191
      this.OlMultiLineStringConstructor = ol.geom.MultiLineString;
×
192
      this.OlPolygonConstructor = ol.geom.Polygon;
×
193
      this.OlMultiPolygonConstructor = ol.geom.MultiPolygon;
×
194
      this.OlPointConstructor = ol.geom.Point;
×
195
    }
196
  }
197

198
  isOlParserStyleFct = (x: any): x is OlParserStyleFct => {
210✔
199
    return typeof x === 'function';
60✔
200
  };
201

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

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

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

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

335
      if (Array.isArray(char)) {
2!
336
        char = char[0];
×
337
      }
338

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

362
      const iconSrc = olIconStyle.getSrc() as string;
44✔
363
      let svgString;
364
      if (iconSrc && iconSrc.startsWith('data:image/svg+xml')) {
44✔
365
        svgString = getDecodedSvg(iconSrc);
40✔
366
      } else if (iconSrc && iconSrc.trim().startsWith('<svg')) {
4!
367
        svgString = iconSrc.trim();
×
368
      }
369

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

381
        pointSymbolizer = {
4✔
382
          kind: 'Icon',
383
          image,
384
          size,
385
          ...OlStyleUtil.checkOpacity(opacity) && { opacity },
3✔
386
          ...rotation && { rotate: rotation },
3✔
387
          ...(displacement[0] || displacement[1]) && { offset: displacement },
4✔
388
        } as IconSymbolizer;
389
      }
390

391
    }
392
    return pointSymbolizer;
46✔
393
  }
394

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

412
      return {
2✔
413
        source: olIconStyle.getSrc()!,
414
        position,
415
        size: size as [number, number]
416
      };
417
    } else {
418
      return olIconStyle.getSrc() ? olIconStyle.getSrc() : undefined;
2!
419
    }
420

421
  }
422

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

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

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

460
    const symbolizer: FillSymbolizer = {
6✔
461
      kind: 'Fill'
462
    };
463

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

484
  }
485

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

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

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

580
    return symbolizers;
60✔
581
  }
582

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

593
    if (Array.isArray(olStyles)) {
60✔
594
      symbolizers = this.getSymbolizersFromOlStyle(olStyles);
4✔
595
    } else {
596
      symbolizers = this.getSymbolizersFromOlStyle([olStyles]);
56✔
597
    }
598

599
    return {
60✔
600
      name, symbolizers
601
    };
602
  }
603

604
  /**
605
   * Get the GeoStyler-Style Symbolizer from an OpenLayers Style object.
606
   *
607
   * @param olStyle The OpenLayers Style object
608
   * @return The GeoStyler-Style Symbolizer
609
   */
610
  getStyleTypeFromOlStyle(olStyle: OlStyle): StyleType {
611
    let styleType: StyleType;
612

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

625
    return styleType;
62✔
626
  }
627

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

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

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

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

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

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

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

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

825
  /**
826
   * Get the OpenLayers Style object from an GeoStyler-Style Style
827
   *
828
   * @param geoStylerStyle A GeoStyler-Style Style.
829
   * @return An OlParserStyleFct
830
   */
831
  geoStylerStyleToOlParserStyleFct(geoStylerStyle: Style): OlParserStyleFct {
832
    const rules = structuredClone(geoStylerStyle.rules);
52✔
833
    const olStyle = (feature: any, resolution: number): any[] => {
52✔
834
      const styles: any[] = [];
76✔
835

836
      // calculate scale for resolution (from ol-util MapUtil)
837
      const dpi = 25.4 / 0.28;
76✔
838
      const mpu = METERS_PER_UNIT.m;
76✔
839
      const inchesPerMeter = 39.37;
76✔
840
      const scale = resolution * mpu * inchesPerMeter * dpi;
76✔
841

842
      rules.forEach((rule: Rule) => {
76✔
843
        // handling scale denominator
844
        let minScale = rule?.scaleDenominator?.min;
118✔
845
        let maxScale = rule?.scaleDenominator?.max;
118✔
846
        let isWithinScale = true;
118✔
847
        if (minScale || maxScale) {
118✔
848
          minScale = isGeoStylerFunction(minScale)
28!
849
            ? OlStyleUtil.evaluateFunction(minScale, feature) as number
850
            : minScale;
851
          maxScale = isGeoStylerFunction(maxScale)
28!
852
            ? OlStyleUtil.evaluateFunction(maxScale, feature) as number
853
            : maxScale;
854
          if (minScale && scale < minScale) {
28✔
855
            isWithinScale = false;
4✔
856
          }
857
          if (maxScale && scale >= maxScale) {
28✔
858
            isWithinScale = false;
10✔
859
          }
860
        }
861

862
        // handling filter
863
        let matchesFilter = false;
118✔
864
        if (!rule.filter) {
118✔
865
          matchesFilter = true;
84✔
866
        } else {
867
          try {
34✔
868
            matchesFilter = this.geoStylerFilterToOlParserFilter(feature, rule.filter);
34✔
869
          } catch {
870
            matchesFilter = false;
2✔
871
          }
872
        }
873

874
        if (isWithinScale && matchesFilter) {
118✔
875
          rule.symbolizers.forEach((symb: Symbolizer) => {
82✔
876
            if (symb.visibility === false) {
82!
877
              styles.push(null);
×
878
            }
879

880
            if (isGeoStylerBooleanFunction(symb.visibility)) {
82!
NEW
881
              const visibility = OlStyleUtil.evaluateFunction(symb.visibility, feature) as boolean;
×
882
              if (!visibility) {
×
883
                styles.push(null);
×
884
              }
885
            }
886

887
            const olSymbolizer: any = this.getOlSymbolizerFromSymbolizer(symb, feature, resolution);
82✔
888
            // either an OlStyle, OlStyle[] or an ol.StyleFunction. OpenLayers only accepts an array
889
            // of OlStyles, not ol.StyleFunctions.
890
            // So we have to check it and in case of an ol.StyleFunction call that function
891
            // and add the returned style to const styles.
892
            if (typeof olSymbolizer === 'function') {
82✔
893
              const styleFromFct: any = olSymbolizer(feature, resolution);
6✔
894
              styles.push(styleFromFct);
6✔
895
            } else if (Array.isArray(olSymbolizer)) {
76✔
896
              olSymbolizer.forEach((s) => {
10✔
897
                if (typeof s === 'function') {
38!
898
                  const styleFromFct: any = s(feature, resolution);
×
899
                  styles.push(styleFromFct);
×
900
                } else {
901
                  styles.push(s);
38✔
902
                }
903
              });
904
            } else {
905
              styles.push(olSymbolizer);
66✔
906
            }
907
          });
908
        }
909
      });
910
      return styles;
76✔
911
    };
912
    const olStyleFct: OlParserStyleFct = olStyle as OlParserStyleFct;
52✔
913
    olStyleFct.__geoStylerStyle = geoStylerStyle;
52✔
914
    return olStyleFct;
52✔
915
  }
916

917
  /**
918
   * Checks if a feature matches given filter expression(s)
919
   * @param feature ol.Feature
920
   * @param filter Filter
921
   * @return boolean true if feature matches filter expression
922
   */
923
  geoStylerFilterToOlParserFilter(feature: any, filter: Filter): boolean {
924
    const operatorMapping: any = {
84✔
925
      '&&': true,
926
      '||': true,
927
      '!': true
928
    };
929

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

1026
  /**
1027
   * Get the OpenLayers Style object or an OL StyleFunction from an
1028
   * GeoStyler-Style Symbolizer.
1029
   *
1030
   * @param symbolizer A GeoStyler-Style Symbolizer.
1031
   * @return The OpenLayers Style object or a StyleFunction
1032
   */
1033
  getOlSymbolizerFromSymbolizer(symbolizer: Symbolizer, feature?: OlFeature, resolution?: number): OlStyle {
1034
    let olSymbolizer: any;
1035
    symbolizer = structuredClone(symbolizer);
176✔
1036

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

1076
    return olSymbolizer;
176✔
1077
  }
1078

1079
  /**
1080
   * Get the OL Style object from an GeoStyler-Style MarkSymbolizer.
1081
   *
1082
   * @param markSymbolizer A GeoStyler-Style MarkSymbolizer.
1083
   * @return The OL Style object
1084
   */
1085
  getOlPointSymbolizerFromMarkSymbolizer(
1086
    markSymbolizer: MarkSymbolizer,
1087
    feature?: OlFeature
1088
  ): OlStyle {
1089
    for (const key of Object.keys(markSymbolizer)) {
110✔
1090
      if (isGeoStylerFunction(markSymbolizer[key as keyof MarkSymbolizer])) {
488✔
1091
        (markSymbolizer as any)[key] = OlStyleUtil.evaluateFunction((markSymbolizer as any)[key], feature);
8✔
1092
      }
1093
    }
1094

1095
    const strokeColor = markSymbolizer.strokeColor as string;
110✔
1096
    const strokeOpacity = markSymbolizer.strokeOpacity as number;
110✔
1097
    const strokeWidth = markSymbolizer.strokeWidth as number;
110✔
1098
    const fillColor = markSymbolizer.color as string;
110✔
1099
    const fillOpacity = markSymbolizer.fillOpacity as number;
110✔
1100
    const rotation = Number(markSymbolizer.rotate) * DEGREES_TO_RADIANS;
110✔
1101
    const displacement = markSymbolizer.offset as [number, number];
110✔
1102

1103
    let olStyle: any;
1104

1105
    if (isPointDefinedAsSvg(markSymbolizer.wellKnownName)) {
110✔
1106
      const opacity = markSymbolizer.opacity as number;
108✔
1107

1108
      const svg = getPointSvg(markSymbolizer);
108✔
1109
      olStyle = new this.OlStyleConstructor({
108✔
1110
        image: new this.OlStyleIconConstructor({
1111
          crossOrigin: 'anonymous',
1112
          ...displacement && { displacement },
56✔
1113
          ...OlStyleUtil.checkOpacity(opacity) && { opacity },
54!
1114
          ...rotation && { rotation },
67✔
1115
          scale: 1,
1116
          src: getEncodedSvg(svg)
1117
        })
1118
      });
1119
    } else if (OlStyleUtil.getIsFontGlyphBased(markSymbolizer)) {
2!
1120
      const strokeRgbaColor = (strokeColor && OlStyleUtil.checkOpacity(strokeOpacity) ?
2!
1121
        OlStyleUtil.getRgbaColor(strokeColor, strokeOpacity) :
1122
        strokeColor) as string;
1123
      const fillRgbaColor = (fillColor && OlStyleUtil.checkOpacity(fillOpacity) ?
2!
1124
        OlStyleUtil.getRgbaColor(fillColor, fillOpacity) :
1125
        fillColor) as string;
1126

1127
      const stroke = new this.OlStyleStrokeConstructor({
2✔
1128
        ...strokeRgbaColor && { color: strokeRgbaColor as string },
2✔
1129
        ...strokeWidth && { width: strokeWidth }
1!
1130
      });
1131

1132
      const fill = new this.OlStyleFillConstructor({
2✔
1133
        ...fillRgbaColor && { color: fillRgbaColor as string }
2✔
1134
      });
1135

1136
      olStyle = new this.OlStyleConstructor({
2✔
1137
        text: new this.OlStyleTextConstructor({
1138
          text: OlStyleUtil.getCharacterForMarkSymbolizer(markSymbolizer),
1139
          font: OlStyleUtil.getTextFontForMarkSymbolizer(markSymbolizer),
1140
          ...fill && { fill },
2✔
1141
          ...displacement && { offsetX: displacement[0] },
2✔
1142
          ...displacement && { offsetY: displacement[1] },
2✔
1143
          ...stroke && { stroke },
2✔
1144
          ...rotation && { rotation },
1!
1145
        })
1146
      });
1147
    } else {
1148
      throw new Error('MarkSymbolizer cannot be parsed. Unsupported WellKnownName.');
×
1149
    }
1150

1151
    return olStyle;
110✔
1152
  }
1153

1154
  /**
1155
   * Get the OL Style object  from an GeoStyler-Style IconSymbolizer.
1156
   *
1157
   * @param symbolizer  A GeoStyler-Style IconSymbolizer.
1158
   * @return The OL Style object
1159
   */
1160
  getOlIconSymbolizerFromIconSymbolizer(
1161
    symbolizer: IconSymbolizer,
1162
    feat?: OlFeature
1163
  ): OlStyle | OlStyleIcon | OlStyleFunction {
1164
    for (const key of Object.keys(symbolizer)) {
8✔
1165
      if (isGeoStylerFunction(symbolizer[key as keyof IconSymbolizer])) {
28!
1166
        (symbolizer as any)[key] = OlStyleUtil.evaluateFunction((symbolizer as any)[key], feat);
×
1167
      }
1168
    }
1169

1170
    const baseProps: OlStyleIconOptions = {
8✔
1171
      src: isSprite(symbolizer.image) ? symbolizer.image.source as string : symbolizer.image as string,
4✔
1172
      crossOrigin: 'anonymous',
1173
      opacity: symbolizer.opacity as number,
1174
      width: symbolizer.size as number,
1175
      // Rotation in openlayers is radians while we use degree
1176
      rotation: (typeof(symbolizer.rotate) === 'number' ? symbolizer.rotate * DEGREES_TO_RADIANS : undefined) as number,
4✔
1177
      displacement: symbolizer.offset as [number, number],
1178
      size: isSprite(symbolizer.image) ? symbolizer.image.size as [number, number] : undefined,
4✔
1179
      offset: isSprite(symbolizer.image) ? symbolizer.image.position as [number, number] : undefined,
4✔
1180
    };
1181

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

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

1252
    return new this.OlStyleConstructor({
14✔
1253
      stroke: new this.OlStyleStrokeConstructor({
1254
        color: sColor,
1255
        width: symbolizer.width as number,
1256
        lineCap: symbolizer.cap as CapType,
1257
        lineJoin: symbolizer.join as JoinType,
1258
        lineDash: symbolizer.dasharray as number[],
1259
        lineDashOffset: symbolizer.dashOffset as number
1260
      })
1261
    });
1262
  }
1263

1264
  getOlGraphicStrokeFromGraphicStroke(
1265
    symbolizer: LineSymbolizer, feat: OlFeature, resolution: number
1266
  ) {
1267
    const lineStrings = OlGraphicStrokeUtil.getLineStringsFromGeometry(feat.getGeometry(), {
10✔
1268
      LineString: this.OlLineStringContructor,
1269
      MultiLineString: this.OlMultiLineStringConstructor,
1270
      Polygon: this.OlPolygonConstructor,
1271
      MultiPolygon: this.OlMultiPolygonConstructor
1272
    });
1273

1274
    const graphicStroke = symbolizer.graphicStroke!;
10✔
1275
    const symbolSize = this.getSymbolSizeFromGraphicStroke(graphicStroke, feat);
10✔
1276
    if (symbolSize <= 0) {
10!
1277
      console.warn('Symbol size must be greater than zero for graphic stroke. No graphic will be drawn.');
×
1278
      return [];
×
1279
    }
1280
    const symbolRotation = graphicStroke.rotate;
10✔
1281
    const evaluatedSymbolRotation = isGeoStylerFunction(symbolRotation)
10!
1282
      ? OlStyleUtil.evaluateFunction(symbolRotation, feat) as number
1283
      : symbolRotation ?? 0;
10✔
1284
    // We currently do not support expressions for dasharrays
1285
    const dashArray = symbolizer.dasharray as number[] | undefined;
10✔
1286
    const dashOffset = symbolizer.dashOffset || 0;
10✔
1287
    const evaluatedDashOffset = isGeoStylerFunction(dashOffset)
10!
1288
      ? OlStyleUtil.evaluateFunction(dashOffset, feat) as number
1289
      : dashOffset;
1290

1291
    const symbolizerGenerator = (modifiedGraphicStroke: any) => {
10✔
1292
      return this.getOlSymbolizerFromSymbolizer(modifiedGraphicStroke, feat, resolution);
32✔
1293
    };
1294

1295
    // For every line in the MultiLineString, we start the pattern at the
1296
    // beginning of the line. This means that the pattern can be
1297
    // discontinuous at vertices where the lines connect.
1298
    return lineStrings.flatMap(line =>
10✔
1299
      OlGraphicStrokeUtil.processLineStringGraphicStroke(
14✔
1300
        line,
1301
        symbolSize,
1302
        resolution,
1303
        dashArray,
1304
        evaluatedDashOffset,
1305
        evaluatedSymbolRotation,
1306
        graphicStroke,
1307
        symbolizerGenerator,
1308
        this.OlPointConstructor
1309
      )
1310
    );
1311
  }
1312

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

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

1363
    const color = symbolizer.color as string;
14✔
1364
    const opacity = symbolizer.fillOpacity as number;
14✔
1365
    const fColor = color && Number.isFinite(opacity)
14✔
1366
      ? OlStyleUtil.getRgbaColor(color, opacity)
1367
      : color;
1368

1369
    let fill = color
14✔
1370
      ? new this.OlStyleFillConstructor({color: fColor})
1371
      : undefined;
1372

1373
    const outlineColor = symbolizer.outlineColor as string;
14✔
1374
    const outlineOpacity = symbolizer.outlineOpacity as number;
14✔
1375
    const oColor = (outlineColor && Number.isFinite(outlineOpacity))
14✔
1376
      ? OlStyleUtil.getRgbaColor(outlineColor, outlineOpacity)
1377
      : outlineColor;
1378

1379
    const stroke = outlineColor || symbolizer.outlineWidth ? new this.OlStyleStrokeConstructor({
14✔
1380
      color: oColor,
1381
      width: symbolizer.outlineWidth as number,
1382
      lineDash: symbolizer.outlineDasharray as number[],
1383
    }) : undefined;
1384

1385
    const olStyle = new OlStyle({
14✔
1386
      fill,
1387
      stroke
1388
    });
1389

1390
    if (symbolizer.graphicFill) {
14✔
1391
      const pattern = this.getOlPatternFromGraphicFill(symbolizer.graphicFill);
2✔
1392
      if (!fill) {
2!
1393
        fill = new OlStyleFill({});
2✔
1394
      }
1395
      if (pattern) {
2!
1396
        fill.setColor(pattern);
2✔
1397
      }
1398
      olStyle.setFill(fill);
2✔
1399
    }
1400

1401
    return olStyle;
14✔
1402
  }
1403

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

1419
    // Temporary canvas.
1420
    // TODO: Can/should we reuse an pre-existing one for efficiency?
1421
    const tmpCanvas: HTMLCanvasElement = document.createElement('canvas');
2✔
1422
    const tmpContext = tmpCanvas.getContext('2d') as CanvasRenderingContext2D;
2✔
1423

1424
    let canvasSize;
1425
    let iconSize: [number, number] = [8, 8];
2✔
1426
    let iconSpacing = 1.5;
2✔
1427
    let scaleFactor = 1;
2✔
1428

1429
    if (isIconSymbolizer(graphicFill)) {
2!
1430
      const graphicFillStyle: any = this.getOlIconSymbolizerFromIconSymbolizer(graphicFill);
×
1431

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

1439
      iconSize = graphicFillStyle.getImage().getSize();
×
1440
      canvasSize = iconSize.map(item  => item * iconSpacing);
×
1441
      tmpCanvas.width = canvasSize[0];
×
1442
      tmpCanvas.height = canvasSize[1];
×
1443

1444
      // Create the context where we'll be drawing the style on
1445
      const vectorContext = toContext(tmpContext, {
×
1446
        size: canvasSize,
1447
        pixelRatio: 1
1448
      });
1449

1450
      const pointCoords = canvasSize.map(item  => item / 2);
×
1451
      const pointFeature = new OlFeature(new OlGeomPoint(pointCoords));
×
1452

1453
      vectorContext.drawFeature(pointFeature, graphicFillStyle);
×
1454
      return tmpContext.createPattern(tmpCanvas, 'repeat');
×
1455
    }
1456

1457
    const svgPattern = fillPatternSvg(graphicFill);
2✔
1458

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

1481
    drawSvgToCanvas(svgPattern, tmpContext, canvasSize[0], iconRotation);
2✔
1482
    return tmpContext.createPattern(tmpCanvas, 'repeat');
2✔
1483
  }
1484

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

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

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

1555
        const text = new this.OlStyleTextConstructor({
4✔
1556
          text: OlStyleUtil.resolveAttributeTemplate(feature, symbolizer.label as string, ''),
1557
          ...baseProps
1558
        });
1559

1560
        const style = new this.OlStyleConstructor({
4✔
1561
          text: text
1562
        });
1563

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

1579
}
1580

1581
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