@versatiles/svg-renderer 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -135,7 +135,7 @@ subgraph 0["src"]
135
135
  2["index.ts"]
136
136
  subgraph 3["pipeline"]
137
137
  4["render.ts"]
138
- D["style_layer.ts"]
138
+ E["style_layer.ts"]
139
139
  end
140
140
  subgraph 5["sources"]
141
141
  6["index.ts"]
@@ -143,43 +143,46 @@ subgraph 5["sources"]
143
143
  9["merge.ts"]
144
144
  A["raster.ts"]
145
145
  B["tiles.ts"]
146
- C["vector.ts"]
146
+ C["sprite.ts"]
147
+ D["vector.ts"]
147
148
  end
148
149
  8["geometry.ts"]
149
- subgraph E["renderer"]
150
- F["svg.ts"]
151
- G["color.ts"]
152
- H["svg_path.ts"]
153
- M["types.ts"]
150
+ subgraph F["renderer"]
151
+ G["svg.ts"]
152
+ H["color.ts"]
153
+ I["svg_path.ts"]
154
+ N["types.ts"]
154
155
  end
155
- subgraph I["maplibre"]
156
- J["control.ts"]
157
- K["panel_css.ts"]
158
- L["index.ts"]
156
+ subgraph J["maplibre"]
157
+ K["control.ts"]
158
+ L["panel_css.ts"]
159
+ M["index.ts"]
159
160
  end
160
161
  end
161
162
  1-->2
162
163
  2-->4
163
- 2-->F
164
+ 2-->G
164
165
  4-->6
165
- 4-->D
166
+ 4-->C
167
+ 4-->E
166
168
  6-->7
167
169
  6-->9
168
170
  6-->A
169
171
  6-->C
172
+ 6-->D
170
173
  7-->8
171
174
  9-->8
172
175
  A-->B
173
176
  B-->8
174
- C-->8
175
- C-->B
176
- F-->G
177
- F-->H
178
- J-->2
179
- J-->K
180
- L-->2
181
- L-->J
182
-
183
- class 0,3,5,E,I subgraphs;
177
+ D-->8
178
+ D-->B
179
+ G-->H
180
+ G-->I
181
+ K-->2
182
+ K-->L
183
+ M-->2
184
+ M-->K
185
+
186
+ class 0,3,5,F,J subgraphs;
184
187
  classDef subgraphs fill-opacity:0.1, fill:#888, color:#888, stroke:#888;
185
188
  ```
package/dist/index.cjs CHANGED
@@ -2828,6 +2828,23 @@ var paint_raster = {
2828
2828
  },
2829
2829
  "property-type": "data-constant"
2830
2830
  },
2831
+ resampling: {
2832
+ type: "enum",
2833
+ values: {
2834
+ linear: {
2835
+ },
2836
+ nearest: {
2837
+ }
2838
+ },
2839
+ "default": "linear",
2840
+ expression: {
2841
+ interpolated: false,
2842
+ parameters: [
2843
+ "zoom"
2844
+ ]
2845
+ },
2846
+ "property-type": "data-constant"
2847
+ },
2831
2848
  "raster-resampling": {
2832
2849
  type: "enum",
2833
2850
  values: {
@@ -2978,6 +2995,23 @@ var paint_hillshade = {
2978
2995
  ]
2979
2996
  },
2980
2997
  "property-type": "data-constant"
2998
+ },
2999
+ resampling: {
3000
+ type: "enum",
3001
+ values: {
3002
+ linear: {
3003
+ },
3004
+ nearest: {
3005
+ }
3006
+ },
3007
+ "default": "linear",
3008
+ expression: {
3009
+ interpolated: false,
3010
+ parameters: [
3011
+ "zoom"
3012
+ ]
3013
+ },
3014
+ "property-type": "data-constant"
2981
3015
  }
2982
3016
  };
2983
3017
  var paint_background = {
@@ -3398,6 +3432,23 @@ var v8Spec = {
3398
3432
  ]
3399
3433
  },
3400
3434
  "property-type": "color-ramp"
3435
+ },
3436
+ resampling: {
3437
+ type: "enum",
3438
+ values: {
3439
+ linear: {
3440
+ },
3441
+ nearest: {
3442
+ }
3443
+ },
3444
+ "default": "linear",
3445
+ expression: {
3446
+ interpolated: false,
3447
+ parameters: [
3448
+ "zoom"
3449
+ ]
3450
+ },
3451
+ "property-type": "data-constant"
3401
3452
  }
3402
3453
  },
3403
3454
  paint_background: paint_background,
@@ -6242,11 +6293,12 @@ class CollatorExpression {
6242
6293
  }
6243
6294
 
6244
6295
  class NumberFormat {
6245
- constructor(number, locale, currency, minFractionDigits, maxFractionDigits) {
6296
+ constructor(number, locale, currency, unit, minFractionDigits, maxFractionDigits) {
6246
6297
  this.type = StringType;
6247
6298
  this.number = number;
6248
6299
  this.locale = locale;
6249
6300
  this.currency = currency;
6301
+ this.unit = unit;
6250
6302
  this.minFractionDigits = minFractionDigits;
6251
6303
  this.maxFractionDigits = maxFractionDigits;
6252
6304
  }
@@ -6271,6 +6323,15 @@ class NumberFormat {
6271
6323
  if (!currency)
6272
6324
  return null;
6273
6325
  }
6326
+ let unit = null;
6327
+ if (options['unit']) {
6328
+ unit = context.parse(options['unit'], 1, StringType);
6329
+ if (!unit)
6330
+ return null;
6331
+ }
6332
+ if (currency && unit) {
6333
+ return context.error('NumberFormat options `currency` and `unit` are mutually exclusive');
6334
+ }
6274
6335
  let minFractionDigits = null;
6275
6336
  if (options['min-fraction-digits']) {
6276
6337
  minFractionDigits = context.parse(options['min-fraction-digits'], 1, NumberType);
@@ -6283,12 +6344,13 @@ class NumberFormat {
6283
6344
  if (!maxFractionDigits)
6284
6345
  return null;
6285
6346
  }
6286
- return new NumberFormat(number, locale, currency, minFractionDigits, maxFractionDigits);
6347
+ return new NumberFormat(number, locale, currency, unit, minFractionDigits, maxFractionDigits);
6287
6348
  }
6288
6349
  evaluate(ctx) {
6289
6350
  return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [], {
6290
- style: this.currency ? 'currency' : 'decimal',
6351
+ style: this.currency ? 'currency' : this.unit ? 'unit' : 'decimal',
6291
6352
  currency: this.currency ? this.currency.evaluate(ctx) : undefined,
6353
+ unit: this.unit ? this.unit.evaluate(ctx) : undefined,
6292
6354
  minimumFractionDigits: this.minFractionDigits
6293
6355
  ? this.minFractionDigits.evaluate(ctx)
6294
6356
  : undefined,
@@ -6305,6 +6367,9 @@ class NumberFormat {
6305
6367
  if (this.currency) {
6306
6368
  fn(this.currency);
6307
6369
  }
6370
+ if (this.unit) {
6371
+ fn(this.unit);
6372
+ }
6308
6373
  if (this.minFractionDigits) {
6309
6374
  fn(this.minFractionDigits);
6310
6375
  }
@@ -8043,6 +8108,16 @@ CompoundExpression.register(expressions$1, {
8043
8108
  varargs(ValueType),
8044
8109
  (ctx, args) => args.map((arg) => valueToString(arg.evaluate(ctx))).join('')
8045
8110
  ],
8111
+ split: [
8112
+ array(StringType),
8113
+ [StringType, StringType],
8114
+ (ctx, [s, delim]) => s.evaluate(ctx).split(delim.evaluate(ctx))
8115
+ ],
8116
+ join: [
8117
+ StringType,
8118
+ [array(StringType), StringType],
8119
+ (ctx, [arr, delim]) => arr.value.join(delim.evaluate(ctx))
8120
+ ],
8046
8121
  'resolved-locale': [
8047
8122
  StringType,
8048
8123
  [CollatorType],
@@ -9199,7 +9274,7 @@ class SVGRenderer {
9199
9274
  color.alpha *= style.opacity;
9200
9275
  this.#backgroundColor = color;
9201
9276
  }
9202
- drawPolygons(features) {
9277
+ drawPolygons(id, features) {
9203
9278
  if (features.length === 0)
9204
9279
  return;
9205
9280
  const groups = new Map();
@@ -9223,12 +9298,14 @@ class SVGRenderer {
9223
9298
  group.segments.push(ring.map((p) => roundXY(p.x, p.y)));
9224
9299
  });
9225
9300
  });
9301
+ this.#svg.push(`<g id="${escapeXml(id)}">`);
9226
9302
  for (const { segments, attrs } of groups.values()) {
9227
9303
  const d = segmentsToPath(segments, true);
9228
9304
  this.#svg.push(`<path d="${d}" ${attrs} />`);
9229
9305
  }
9306
+ this.#svg.push('</g>');
9230
9307
  }
9231
- drawLineStrings(features) {
9308
+ drawLineStrings(id, features) {
9232
9309
  if (features.length === 0)
9233
9310
  return;
9234
9311
  const groups = new Map();
@@ -9277,13 +9354,15 @@ class SVGRenderer {
9277
9354
  group.segments.push(line.map((p) => roundXY(p.x, p.y)));
9278
9355
  });
9279
9356
  });
9357
+ this.#svg.push(`<g id="${escapeXml(id)}">`);
9280
9358
  for (const { segments, attrs } of groups.values()) {
9281
9359
  const chains = chainSegments(segments);
9282
9360
  const d = segmentsToPath(chains);
9283
9361
  this.#svg.push(`<path d="${d}" ${attrs} />`);
9284
9362
  }
9363
+ this.#svg.push('</g>');
9285
9364
  }
9286
- drawCircles(features) {
9365
+ drawCircles(id, features) {
9287
9366
  if (features.length === 0)
9288
9367
  return;
9289
9368
  const groups = new Map();
@@ -9315,13 +9394,108 @@ class SVGRenderer {
9315
9394
  group.points.push(roundXY(p.x, p.y));
9316
9395
  });
9317
9396
  });
9397
+ this.#svg.push(`<g id="${escapeXml(id)}">`);
9318
9398
  for (const { points, attrs } of groups.values()) {
9319
9399
  for (const [x, y] of points) {
9320
9400
  this.#svg.push(`<circle cx="${formatNum(x)}" cy="${formatNum(y)}" ${attrs} />`);
9321
9401
  }
9322
9402
  }
9403
+ this.#svg.push('</g>');
9404
+ }
9405
+ drawLabels(id, features) {
9406
+ if (features.length === 0)
9407
+ return;
9408
+ this.#svg.push(`<g id="${escapeXml(id)}">`);
9409
+ for (const [feature, style] of features) {
9410
+ if (style.opacity <= 0 || !style.text)
9411
+ continue;
9412
+ const color = new Color(style.color);
9413
+ if (color.alpha <= 0)
9414
+ continue;
9415
+ const ring = feature.geometry[0];
9416
+ if (!ring || ring.length === 0)
9417
+ continue;
9418
+ const point = ring[Math.floor(ring.length / 2)];
9419
+ const [px, py] = roundXY(point.x, point.y);
9420
+ const fontSize = formatScaled(style.size);
9421
+ const fontFamily = style.font.join(', ') + ', Helvetica, Arial, sans-serif';
9422
+ const [svgAnchor, baseline] = mapTextAnchor(style.anchor);
9423
+ const offsetX = style.offset[0] * style.size;
9424
+ const offsetY = style.offset[1] * style.size;
9425
+ const [dx, dy] = roundXY(offsetX, offsetY);
9426
+ const attrs = [
9427
+ `x="${formatNum(px)}"`,
9428
+ `y="${formatNum(py)}"`,
9429
+ `font-family="${escapeXml(fontFamily)}"`,
9430
+ `font-size="${fontSize}"`,
9431
+ `text-anchor="${svgAnchor}"`,
9432
+ `dominant-baseline="${baseline}"`,
9433
+ ];
9434
+ if (dx !== 0)
9435
+ attrs.push(`dx="${formatNum(dx)}"`);
9436
+ if (dy !== 0)
9437
+ attrs.push(`dy="${formatNum(dy)}"`);
9438
+ if (style.rotate !== 0) {
9439
+ attrs.push(`transform="rotate(${String(style.rotate)},${formatNum(px)},${formatNum(py)})"`);
9440
+ }
9441
+ const haloColor = new Color(style.haloColor);
9442
+ if (style.haloWidth > 0 && haloColor.alpha > 0) {
9443
+ const haloWidth = formatScaled(style.haloWidth);
9444
+ attrs.push('paint-order="stroke fill"', `stroke="${haloColor.rgb}"`, `stroke-width="${haloWidth}"`, 'stroke-linejoin="round"');
9445
+ if (haloColor.alpha < 255)
9446
+ attrs.push(`stroke-opacity="${haloColor.opacity.toFixed(3)}"`);
9447
+ }
9448
+ attrs.push(fillAttr(color));
9449
+ if (style.opacity < 1)
9450
+ attrs.push(`opacity="${style.opacity.toFixed(3)}"`);
9451
+ this.#svg.push(`<text ${attrs.join(' ')}>${escapeXml(style.text)}</text>`);
9452
+ }
9453
+ this.#svg.push('</g>');
9454
+ }
9455
+ drawIcons(id, features, spriteAtlas) {
9456
+ if (features.length === 0)
9457
+ return;
9458
+ this.#svg.push(`<g id="${escapeXml(id)}">`);
9459
+ for (const [feature, style] of features) {
9460
+ if (style.opacity <= 0)
9461
+ continue;
9462
+ const sprite = spriteAtlas.get(style.image);
9463
+ if (!sprite)
9464
+ continue;
9465
+ const ring = feature.geometry[0];
9466
+ if (!ring || ring.length === 0)
9467
+ continue;
9468
+ const point = ring[Math.floor(ring.length / 2)];
9469
+ const scale = style.size / sprite.pixelRatio;
9470
+ const iconW = sprite.width * scale;
9471
+ const iconH = sprite.height * scale;
9472
+ const [anchorDx, anchorDy] = mapIconAnchor(style.anchor, iconW, iconH);
9473
+ const ox = style.offset[0] * style.size + anchorDx;
9474
+ const oy = style.offset[1] * style.size + anchorDy;
9475
+ const x = point.x + ox;
9476
+ const y = point.y + oy;
9477
+ const [sx, sy] = roundXY(x, y);
9478
+ const [sw, sh] = roundXY(iconW, iconH);
9479
+ const viewBox = `${String(sprite.x)} ${String(sprite.y)} ${String(sprite.width)} ${String(sprite.height)}`;
9480
+ const attrs = [
9481
+ `x="${formatNum(sx)}"`,
9482
+ `y="${formatNum(sy)}"`,
9483
+ `width="${formatNum(sw)}"`,
9484
+ `height="${formatNum(sh)}"`,
9485
+ ];
9486
+ if (style.opacity < 1)
9487
+ attrs.push(`opacity="${style.opacity.toFixed(3)}"`);
9488
+ if (style.rotate !== 0) {
9489
+ const [cx, cy] = roundXY(point.x + style.offset[0] * style.size, point.y + style.offset[1] * style.size);
9490
+ attrs.push(`transform="rotate(${String(style.rotate)},${formatNum(cx)},${formatNum(cy)})"`);
9491
+ }
9492
+ this.#svg.push(`<svg ${attrs.join(' ')} viewBox="${viewBox}" xmlns="http://www.w3.org/2000/svg">` +
9493
+ `<image width="${String(sprite.sheetWidth)}" height="${String(sprite.sheetHeight)}" href="${sprite.sheetDataUri}" />` +
9494
+ `</svg>`);
9495
+ }
9496
+ this.#svg.push('</g>');
9323
9497
  }
9324
- drawRasterTiles(tiles, style) {
9498
+ drawRasterTiles(id, tiles, style) {
9325
9499
  if (tiles.length === 0)
9326
9500
  return;
9327
9501
  if (style.opacity <= 0)
@@ -9337,7 +9511,7 @@ class SVGRenderer {
9337
9511
  const brightness = (style.brightnessMin + style.brightnessMax) / 2;
9338
9512
  filters.push(`brightness(${String(brightness)})`);
9339
9513
  }
9340
- let gAttrs = `opacity="${String(style.opacity)}"`;
9514
+ let gAttrs = `id="${escapeXml(id)}" opacity="${String(style.opacity)}"`;
9341
9515
  if (filters.length > 0)
9342
9516
  gAttrs += ` filter="${filters.join(' ')}"`;
9343
9517
  this.#svg.push(`<g ${gAttrs}>`);
@@ -9388,6 +9562,57 @@ function formatPoint(p) {
9388
9562
  const [x, y] = roundXY(p[0], p[1]);
9389
9563
  return formatNum(x) + ',' + formatNum(y);
9390
9564
  }
9565
+ function mapTextAnchor(anchor) {
9566
+ switch (anchor) {
9567
+ case 'left':
9568
+ return ['start', 'central'];
9569
+ case 'right':
9570
+ return ['end', 'central'];
9571
+ case 'top':
9572
+ return ['middle', 'text-before-edge'];
9573
+ case 'bottom':
9574
+ return ['middle', 'text-after-edge'];
9575
+ case 'top-left':
9576
+ return ['start', 'text-before-edge'];
9577
+ case 'top-right':
9578
+ return ['end', 'text-before-edge'];
9579
+ case 'bottom-left':
9580
+ return ['start', 'text-after-edge'];
9581
+ case 'bottom-right':
9582
+ return ['end', 'text-after-edge'];
9583
+ default:
9584
+ return ['middle', 'central'];
9585
+ }
9586
+ }
9587
+ function mapIconAnchor(anchor, w, h) {
9588
+ switch (anchor) {
9589
+ case 'left':
9590
+ return [0, -h / 2];
9591
+ case 'right':
9592
+ return [-w, -h / 2];
9593
+ case 'top':
9594
+ return [-w / 2, 0];
9595
+ case 'bottom':
9596
+ return [-w / 2, -h];
9597
+ case 'top-left':
9598
+ return [0, 0];
9599
+ case 'top-right':
9600
+ return [-w, 0];
9601
+ case 'bottom-left':
9602
+ return [0, -h];
9603
+ case 'bottom-right':
9604
+ return [-w, -h];
9605
+ default:
9606
+ return [-w / 2, -h / 2];
9607
+ }
9608
+ }
9609
+ function escapeXml(s) {
9610
+ return s
9611
+ .replace(/&/g, '&amp;')
9612
+ .replace(/</g, '&lt;')
9613
+ .replace(/>/g, '&gt;')
9614
+ .replace(/"/g, '&quot;');
9615
+ }
9391
9616
 
9392
9617
  /*
9393
9618
  * bignumber.js v9.3.1
@@ -15936,6 +16161,65 @@ async function getRasterTiles(job, sourceName) {
15936
16161
  return rasterTiles.filter((tile) => tile !== null);
15937
16162
  }
15938
16163
 
16164
+ async function loadSpriteAtlas(style) {
16165
+ const atlas = new Map();
16166
+ const sprite = style.sprite;
16167
+ if (!sprite)
16168
+ return atlas;
16169
+ const sources = [];
16170
+ if (typeof sprite === 'string') {
16171
+ sources.push({ id: 'default', url: sprite });
16172
+ }
16173
+ else if (Array.isArray(sprite)) {
16174
+ for (const s of sprite) {
16175
+ sources.push({
16176
+ id: s.id,
16177
+ url: s.url,
16178
+ });
16179
+ }
16180
+ }
16181
+ await Promise.all(sources.map(async ({ id, url }) => {
16182
+ try {
16183
+ const [jsonResponse, imageResponse] = await Promise.all([
16184
+ fetch(`${url}.json`),
16185
+ fetch(`${url}.png`),
16186
+ ]);
16187
+ if (!jsonResponse.ok || !imageResponse.ok)
16188
+ return;
16189
+ const json = (await jsonResponse.json());
16190
+ const imageBuffer = await imageResponse.arrayBuffer();
16191
+ const base64 = typeof Buffer !== 'undefined'
16192
+ ? Buffer.from(imageBuffer).toString('base64')
16193
+ : btoa(String.fromCharCode(...new Uint8Array(imageBuffer)));
16194
+ const sheetDataUri = `data:image/png;base64,${base64}`;
16195
+ // Estimate sheet dimensions from sprite entries
16196
+ let sheetWidth = 0;
16197
+ let sheetHeight = 0;
16198
+ for (const entry of Object.values(json)) {
16199
+ sheetWidth = Math.max(sheetWidth, entry.x + entry.width);
16200
+ sheetHeight = Math.max(sheetHeight, entry.y + entry.height);
16201
+ }
16202
+ const prefix = id === 'default' ? '' : `${id}:`;
16203
+ for (const [name, entry] of Object.entries(json)) {
16204
+ atlas.set(`${prefix}${name}`, {
16205
+ width: entry.width,
16206
+ height: entry.height,
16207
+ x: entry.x,
16208
+ y: entry.y,
16209
+ pixelRatio: entry.pixelRatio ?? 1,
16210
+ sheetDataUri,
16211
+ sheetWidth,
16212
+ sheetHeight,
16213
+ });
16214
+ }
16215
+ }
16216
+ catch {
16217
+ // Silently skip failed sprite loads
16218
+ }
16219
+ }));
16220
+ return atlas;
16221
+ }
16222
+
15939
16223
  async function getLayerFeatures(job) {
15940
16224
  const { width, height } = job.renderer;
15941
16225
  const { zoom, center } = job.view;
@@ -16096,6 +16380,12 @@ function getLayerStyles(layers) {
16096
16380
  return layers.map(createStyleLayer);
16097
16381
  }
16098
16382
 
16383
+ function resolveTokens(text, properties) {
16384
+ return text.replace(/\{([^}]+)\}/g, (_, key) => {
16385
+ const value = properties[key];
16386
+ return value != null ? String(value) : '';
16387
+ });
16388
+ }
16099
16389
  async function renderMap(job) {
16100
16390
  await render(job);
16101
16391
  return job.renderer.getString();
@@ -16106,9 +16396,12 @@ function getFeatures(layerFeatures, layerStyle) {
16106
16396
  async function render(job) {
16107
16397
  const { renderer } = job;
16108
16398
  const { zoom } = job.view;
16109
- const layerFeatures = await getLayerFeatures(job);
16399
+ const [layerFeatures, spriteAtlas] = await Promise.all([
16400
+ getLayerFeatures(job),
16401
+ job.renderLabels ? loadSpriteAtlas(job.style) : Promise.resolve(new Map()),
16402
+ ]);
16110
16403
  const layerStyles = getLayerStyles(job.style.layers);
16111
- const availableImages = [];
16404
+ const availableImages = [...spriteAtlas.keys()];
16112
16405
  const featureState = {};
16113
16406
  for (const layerStyle of layerStyles) {
16114
16407
  if (layerStyle.isHidden(zoom))
@@ -16129,6 +16422,7 @@ async function render(job) {
16129
16422
  function getLayout(key, feature) {
16130
16423
  return getStyleValue(layerStyle.layout, key, feature);
16131
16424
  }
16425
+ const layerId = layerStyle.id;
16132
16426
  switch (layerStyle.type) {
16133
16427
  case 'background':
16134
16428
  {
@@ -16148,7 +16442,7 @@ async function render(job) {
16148
16442
  : polygons;
16149
16443
  if (polygonFeatures.length === 0)
16150
16444
  continue;
16151
- renderer.drawPolygons(polygonFeatures.map((feature) => [
16445
+ renderer.drawPolygons(layerId, polygonFeatures.map((feature) => [
16152
16446
  feature,
16153
16447
  {
16154
16448
  color: getPaint('fill-color', feature),
@@ -16168,7 +16462,7 @@ async function render(job) {
16168
16462
  : lineStrings;
16169
16463
  if (lineStringFeatures.length === 0)
16170
16464
  continue;
16171
- renderer.drawLineStrings(lineStringFeatures.map((feature) => [
16465
+ renderer.drawLineStrings(layerId, lineStringFeatures.map((feature) => [
16172
16466
  feature,
16173
16467
  {
16174
16468
  color: getPaint('line-color', feature),
@@ -16187,7 +16481,7 @@ async function render(job) {
16187
16481
  case 'raster':
16188
16482
  {
16189
16483
  const tiles = await getRasterTiles(job, layerStyle.source);
16190
- renderer.drawRasterTiles(tiles, {
16484
+ renderer.drawRasterTiles(layerId, tiles, {
16191
16485
  opacity: getPaint('raster-opacity'),
16192
16486
  hueRotate: getPaint('raster-hue-rotate'),
16193
16487
  brightnessMin: getPaint('raster-brightness-min'),
@@ -16208,7 +16502,7 @@ async function render(job) {
16208
16502
  : points;
16209
16503
  if (pointFeatures.length === 0)
16210
16504
  continue;
16211
- renderer.drawCircles(pointFeatures.map((feature) => [
16505
+ renderer.drawCircles(layerId, pointFeatures.map((feature) => [
16212
16506
  feature,
16213
16507
  {
16214
16508
  color: getPaint('circle-color', feature),
@@ -16221,11 +16515,76 @@ async function render(job) {
16221
16515
  ]));
16222
16516
  }
16223
16517
  continue;
16518
+ case 'symbol':
16519
+ {
16520
+ if (!job.renderLabels)
16521
+ continue;
16522
+ const features = getFeatures(layerFeatures, layerStyle);
16523
+ const allFeatures = [
16524
+ ...(features?.points ?? []),
16525
+ ...(features?.linestrings ?? []),
16526
+ ...(features?.polygons ?? []),
16527
+ ];
16528
+ if (allFeatures.length === 0)
16529
+ continue;
16530
+ const symbolFeatures = layerStyle.filterFn
16531
+ ? allFeatures.filter((feature) => layerStyle.filterFn.filter({ zoom }, feature))
16532
+ : allFeatures;
16533
+ if (symbolFeatures.length === 0)
16534
+ continue;
16535
+ // Render icons first (underneath text)
16536
+ renderer.drawIcons(`${layerId}-icons`, symbolFeatures.flatMap((feature) => {
16537
+ const iconImage = getLayout('icon-image', feature);
16538
+ const iconName = iconImage != null
16539
+ ? resolveTokens(iconImage.toString(), feature.properties)
16540
+ : '';
16541
+ if (!iconName || !spriteAtlas.has(iconName))
16542
+ return [];
16543
+ return [
16544
+ [
16545
+ feature,
16546
+ {
16547
+ image: iconName,
16548
+ size: getLayout('icon-size', feature),
16549
+ anchor: getLayout('icon-anchor', feature),
16550
+ offset: getLayout('icon-offset', feature),
16551
+ rotate: getLayout('icon-rotate', feature),
16552
+ opacity: getPaint('icon-opacity', feature),
16553
+ },
16554
+ ],
16555
+ ];
16556
+ }), spriteAtlas);
16557
+ // Render text labels on top
16558
+ renderer.drawLabels(`${layerId}-labels`, symbolFeatures.flatMap((feature) => {
16559
+ const textField = getLayout('text-field', feature);
16560
+ const textRaw = textField != null ? textField.toString() : '';
16561
+ const text = resolveTokens(textRaw, feature.properties);
16562
+ if (!text)
16563
+ return [];
16564
+ return [
16565
+ [
16566
+ feature,
16567
+ {
16568
+ text,
16569
+ size: getLayout('text-size', feature),
16570
+ font: getLayout('text-font', feature),
16571
+ anchor: getLayout('text-anchor', feature),
16572
+ offset: getLayout('text-offset', feature),
16573
+ rotate: getLayout('text-rotate', feature),
16574
+ color: getPaint('text-color', feature),
16575
+ opacity: getPaint('text-opacity', feature),
16576
+ haloColor: getPaint('text-halo-color', feature),
16577
+ haloWidth: getPaint('text-halo-width', feature),
16578
+ },
16579
+ ],
16580
+ ];
16581
+ }));
16582
+ }
16583
+ continue;
16224
16584
  case 'color-relief':
16225
16585
  case 'fill-extrusion':
16226
16586
  case 'heatmap':
16227
16587
  case 'hillshade':
16228
- case 'symbol':
16229
16588
  continue;
16230
16589
  default:
16231
16590
  throw Error('layerStyle.type: ' + String(layerStyle.type));
@@ -16247,6 +16606,7 @@ async function renderToSVG(options) {
16247
16606
  center: [options.lon ?? 0, options.lat ?? 0],
16248
16607
  zoom: options.zoom ?? 2,
16249
16608
  },
16609
+ renderLabels: options.renderLabels ?? false,
16250
16610
  });
16251
16611
  }
16252
16612