tileserver-gl-light 4.6.6 → 4.8.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/.eslintrc.cjs +4 -0
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/codeql.yml +3 -3
- package/.github/workflows/ct.yml +2 -2
- package/.github/workflows/release.yml +2 -2
- package/Dockerfile +2 -2
- package/Dockerfile_test +2 -2
- package/README.md +2 -2
- package/docs/config.rst +20 -1
- package/docs/endpoints.rst +8 -8
- package/docs/installation.rst +31 -44
- package/package.json +6 -6
- package/public/resources/images/marker-icon-2x.png +0 -0
- package/public/resources/images/marker-icon.png +0 -0
- package/public/resources/images/marker-shadow.png +0 -0
- package/public/resources/leaflet.css +6 -1
- package/public/resources/leaflet.js +3 -3
- package/public/resources/leaflet.js.map +1 -1
- package/public/resources/maplibre-gl.js +4 -4
- package/public/resources/maplibre-gl.js.map +1 -1
- package/public/templates/data.tmpl +0 -1
- package/public/templates/index.tmpl +1 -4
- package/public/templates/viewer.tmpl +0 -1
- package/src/healthcheck.js +3 -3
- package/src/main.js +30 -29
- package/src/pmtiles_adapter.js +9 -9
- package/src/render.js +303 -0
- package/src/serve_data.js +12 -12
- package/src/serve_font.js +25 -44
- package/src/serve_rendered.js +234 -613
- package/src/serve_style.js +5 -17
- package/src/server.js +24 -29
- package/src/utils.js +22 -3
- package/test/setup.js +0 -1
- package/public/resources/L.TileLayer.NoGap.js +0 -243
package/src/serve_rendered.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// SECTION START
|
|
4
|
+
//
|
|
5
|
+
// The order of the two imports below is important.
|
|
6
|
+
// For an unknown reason, if the order is reversed, rendering can crash.
|
|
7
|
+
// This happens on ARM:
|
|
8
|
+
// > terminate called after throwing an instance of 'std::runtime_error'
|
|
9
|
+
// > what(): Cannot read GLX extensions.
|
|
10
|
+
import 'canvas';
|
|
11
|
+
import '@maplibre/maplibre-gl-native';
|
|
12
|
+
//
|
|
13
|
+
// SECTION END
|
|
14
|
+
|
|
3
15
|
import advancedPool from 'advanced-pool';
|
|
4
16
|
import fs from 'node:fs';
|
|
5
17
|
import path from 'path';
|
|
6
18
|
import url from 'url';
|
|
7
19
|
import util from 'util';
|
|
8
20
|
import zlib from 'zlib';
|
|
9
|
-
import sharp from 'sharp';
|
|
10
|
-
import { createCanvas, Image } from 'canvas';
|
|
21
|
+
import sharp from 'sharp';
|
|
11
22
|
import clone from 'clone';
|
|
12
23
|
import Color from 'color';
|
|
13
24
|
import express from 'express';
|
|
@@ -20,15 +31,17 @@ import proj4 from 'proj4';
|
|
|
20
31
|
import axios from 'axios';
|
|
21
32
|
import {
|
|
22
33
|
getFontsPbf,
|
|
34
|
+
listFonts,
|
|
23
35
|
getTileUrls,
|
|
24
36
|
isValidHttpUrl,
|
|
25
37
|
fixTileJSONCenter,
|
|
26
38
|
} from './utils.js';
|
|
27
39
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
openPMtiles,
|
|
41
|
+
getPMtilesInfo,
|
|
42
|
+
getPMtilesTile,
|
|
31
43
|
} from './pmtiles_adapter.js';
|
|
44
|
+
import { renderOverlay, renderWatermark, renderAttribution } from './render.js';
|
|
32
45
|
|
|
33
46
|
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
|
34
47
|
const PATH_PATTERN =
|
|
@@ -96,7 +109,7 @@ function createEmptyResponse(format, color, callback) {
|
|
|
96
109
|
raw: {
|
|
97
110
|
width: 1,
|
|
98
111
|
height: 1,
|
|
99
|
-
channels
|
|
112
|
+
channels,
|
|
100
113
|
},
|
|
101
114
|
})
|
|
102
115
|
.toFormat(format)
|
|
@@ -330,263 +343,6 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
|
|
330
343
|
return markers;
|
|
331
344
|
};
|
|
332
345
|
|
|
333
|
-
/**
|
|
334
|
-
* Transforms coordinates to pixels.
|
|
335
|
-
* @param {List[Number]} ll Longitude/Latitude coordinate pair.
|
|
336
|
-
* @param {number} zoom Map zoom level.
|
|
337
|
-
*/
|
|
338
|
-
const precisePx = (ll, zoom) => {
|
|
339
|
-
const px = mercator.px(ll, 20);
|
|
340
|
-
const scale = Math.pow(2, zoom - 20);
|
|
341
|
-
return [px[0] * scale, px[1] * scale];
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Draws a marker in cavans context.
|
|
346
|
-
* @param {object} ctx Canvas context object.
|
|
347
|
-
* @param {object} marker Marker object parsed by extractMarkersFromQuery.
|
|
348
|
-
* @param {number} z Map zoom level.
|
|
349
|
-
*/
|
|
350
|
-
const drawMarker = (ctx, marker, z) => {
|
|
351
|
-
return new Promise((resolve) => {
|
|
352
|
-
const img = new Image();
|
|
353
|
-
const pixelCoords = precisePx(marker.location, z);
|
|
354
|
-
|
|
355
|
-
const getMarkerCoordinates = (imageWidth, imageHeight, scale) => {
|
|
356
|
-
// Images are placed with their top-left corner at the provided location
|
|
357
|
-
// within the canvas but we expect icons to be centered and above it.
|
|
358
|
-
|
|
359
|
-
// Substract half of the images width from the x-coordinate to center
|
|
360
|
-
// the image in relation to the provided location
|
|
361
|
-
let xCoordinate = pixelCoords[0] - imageWidth / 2;
|
|
362
|
-
// Substract the images height from the y-coordinate to place it above
|
|
363
|
-
// the provided location
|
|
364
|
-
let yCoordinate = pixelCoords[1] - imageHeight;
|
|
365
|
-
|
|
366
|
-
// Since image placement is dependent on the size offsets have to be
|
|
367
|
-
// scaled as well. Additionally offsets are provided as either positive or
|
|
368
|
-
// negative values so we always add them
|
|
369
|
-
if (marker.offsetX) {
|
|
370
|
-
xCoordinate = xCoordinate + marker.offsetX * scale;
|
|
371
|
-
}
|
|
372
|
-
if (marker.offsetY) {
|
|
373
|
-
yCoordinate = yCoordinate + marker.offsetY * scale;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return {
|
|
377
|
-
x: xCoordinate,
|
|
378
|
-
y: yCoordinate,
|
|
379
|
-
};
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
const drawOnCanvas = () => {
|
|
383
|
-
// Check if the images should be resized before beeing drawn
|
|
384
|
-
const defaultScale = 1;
|
|
385
|
-
const scale = marker.scale ? marker.scale : defaultScale;
|
|
386
|
-
|
|
387
|
-
// Calculate scaled image sizes
|
|
388
|
-
const imageWidth = img.width * scale;
|
|
389
|
-
const imageHeight = img.height * scale;
|
|
390
|
-
|
|
391
|
-
// Pass the desired sizes to get correlating coordinates
|
|
392
|
-
const coords = getMarkerCoordinates(imageWidth, imageHeight, scale);
|
|
393
|
-
|
|
394
|
-
// Draw the image on canvas
|
|
395
|
-
if (scale != defaultScale) {
|
|
396
|
-
ctx.drawImage(img, coords.x, coords.y, imageWidth, imageHeight);
|
|
397
|
-
} else {
|
|
398
|
-
ctx.drawImage(img, coords.x, coords.y);
|
|
399
|
-
}
|
|
400
|
-
// Resolve the promise when image has been drawn
|
|
401
|
-
resolve();
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
img.onload = drawOnCanvas;
|
|
405
|
-
img.onerror = (err) => {
|
|
406
|
-
throw err;
|
|
407
|
-
};
|
|
408
|
-
img.src = marker.icon;
|
|
409
|
-
});
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Draws a list of markers onto a canvas.
|
|
414
|
-
* Wraps drawing of markers into list of promises and awaits them.
|
|
415
|
-
* It's required because images are expected to load asynchronous in canvas js
|
|
416
|
-
* even when provided from a local disk.
|
|
417
|
-
* @param {object} ctx Canvas context object.
|
|
418
|
-
* @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery.
|
|
419
|
-
* @param {number} z Map zoom level.
|
|
420
|
-
*/
|
|
421
|
-
const drawMarkers = async (ctx, markers, z) => {
|
|
422
|
-
const markerPromises = [];
|
|
423
|
-
|
|
424
|
-
for (const marker of markers) {
|
|
425
|
-
// Begin drawing marker
|
|
426
|
-
markerPromises.push(drawMarker(ctx, marker, z));
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Await marker drawings before continuing
|
|
430
|
-
await Promise.all(markerPromises);
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Draws a list of coordinates onto a canvas and styles the resulting path.
|
|
435
|
-
* @param {object} ctx Canvas context object.
|
|
436
|
-
* @param {List[Number]} path List of coordinates.
|
|
437
|
-
* @param {object} query Request query parameters.
|
|
438
|
-
* @param {string} pathQuery Path query parameter.
|
|
439
|
-
* @param {number} z Map zoom level.
|
|
440
|
-
*/
|
|
441
|
-
const drawPath = (ctx, path, query, pathQuery, z) => {
|
|
442
|
-
const splitPaths = pathQuery.split('|');
|
|
443
|
-
|
|
444
|
-
if (!path || path.length < 2) {
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
ctx.beginPath();
|
|
449
|
-
|
|
450
|
-
// Transform coordinates to pixel on canvas and draw lines between points
|
|
451
|
-
for (const pair of path) {
|
|
452
|
-
const px = precisePx(pair, z);
|
|
453
|
-
ctx.lineTo(px[0], px[1]);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Check if first coordinate matches last coordinate
|
|
457
|
-
if (
|
|
458
|
-
path[0][0] === path[path.length - 1][0] &&
|
|
459
|
-
path[0][1] === path[path.length - 1][1]
|
|
460
|
-
) {
|
|
461
|
-
ctx.closePath();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Optionally fill drawn shape with a rgba color from query
|
|
465
|
-
const pathHasFill = splitPaths.filter((x) => x.startsWith('fill')).length > 0;
|
|
466
|
-
if (query.fill !== undefined || pathHasFill) {
|
|
467
|
-
if ('fill' in query) {
|
|
468
|
-
ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
|
|
469
|
-
}
|
|
470
|
-
if (pathHasFill) {
|
|
471
|
-
ctx.fillStyle = splitPaths
|
|
472
|
-
.find((x) => x.startsWith('fill:'))
|
|
473
|
-
.replace('fill:', '');
|
|
474
|
-
}
|
|
475
|
-
ctx.fill();
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Get line width from query and fall back to 1 if not provided
|
|
479
|
-
const pathHasWidth =
|
|
480
|
-
splitPaths.filter((x) => x.startsWith('width')).length > 0;
|
|
481
|
-
if (query.width !== undefined || pathHasWidth) {
|
|
482
|
-
let lineWidth = 1;
|
|
483
|
-
// Get line width from query
|
|
484
|
-
if ('width' in query) {
|
|
485
|
-
lineWidth = Number(query.width);
|
|
486
|
-
}
|
|
487
|
-
// Get line width from path in query
|
|
488
|
-
if (pathHasWidth) {
|
|
489
|
-
lineWidth = Number(
|
|
490
|
-
splitPaths.find((x) => x.startsWith('width:')).replace('width:', ''),
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
// Get border width from query and fall back to 10% of line width
|
|
494
|
-
const borderWidth =
|
|
495
|
-
query.borderwidth !== undefined
|
|
496
|
-
? parseFloat(query.borderwidth)
|
|
497
|
-
: lineWidth * 0.1;
|
|
498
|
-
|
|
499
|
-
// Set rendering style for the start and end points of the path
|
|
500
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
|
|
501
|
-
ctx.lineCap = query.linecap || 'butt';
|
|
502
|
-
|
|
503
|
-
// Set rendering style for overlapping segments of the path with differing directions
|
|
504
|
-
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
|
|
505
|
-
ctx.lineJoin = query.linejoin || 'miter';
|
|
506
|
-
|
|
507
|
-
// In order to simulate a border we draw the path two times with the first
|
|
508
|
-
// beeing the wider border part.
|
|
509
|
-
if (query.border !== undefined && borderWidth > 0) {
|
|
510
|
-
// We need to double the desired border width and add it to the line width
|
|
511
|
-
// in order to get the desired border on each side of the line.
|
|
512
|
-
ctx.lineWidth = lineWidth + borderWidth * 2;
|
|
513
|
-
// Set border style as rgba
|
|
514
|
-
ctx.strokeStyle = query.border;
|
|
515
|
-
ctx.stroke();
|
|
516
|
-
}
|
|
517
|
-
ctx.lineWidth = lineWidth;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const pathHasStroke =
|
|
521
|
-
splitPaths.filter((x) => x.startsWith('stroke')).length > 0;
|
|
522
|
-
if (query.stroke !== undefined || pathHasStroke) {
|
|
523
|
-
if ('stroke' in query) {
|
|
524
|
-
ctx.strokeStyle = query.stroke;
|
|
525
|
-
}
|
|
526
|
-
// Path Stroke gets higher priority
|
|
527
|
-
if (pathHasStroke) {
|
|
528
|
-
ctx.strokeStyle = splitPaths
|
|
529
|
-
.find((x) => x.startsWith('stroke:'))
|
|
530
|
-
.replace('stroke:', '');
|
|
531
|
-
}
|
|
532
|
-
} else {
|
|
533
|
-
ctx.strokeStyle = 'rgba(0,64,255,0.7)';
|
|
534
|
-
}
|
|
535
|
-
ctx.stroke();
|
|
536
|
-
};
|
|
537
|
-
|
|
538
|
-
const renderOverlay = async (
|
|
539
|
-
z,
|
|
540
|
-
x,
|
|
541
|
-
y,
|
|
542
|
-
bearing,
|
|
543
|
-
pitch,
|
|
544
|
-
w,
|
|
545
|
-
h,
|
|
546
|
-
scale,
|
|
547
|
-
paths,
|
|
548
|
-
markers,
|
|
549
|
-
query,
|
|
550
|
-
) => {
|
|
551
|
-
if ((!paths || paths.length === 0) && (!markers || markers.length === 0)) {
|
|
552
|
-
return null;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
const center = precisePx([x, y], z);
|
|
556
|
-
|
|
557
|
-
const mapHeight = 512 * (1 << z);
|
|
558
|
-
const maxEdge = center[1] + h / 2;
|
|
559
|
-
const minEdge = center[1] - h / 2;
|
|
560
|
-
if (maxEdge > mapHeight) {
|
|
561
|
-
center[1] -= maxEdge - mapHeight;
|
|
562
|
-
} else if (minEdge < 0) {
|
|
563
|
-
center[1] -= minEdge;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const canvas = createCanvas(scale * w, scale * h);
|
|
567
|
-
const ctx = canvas.getContext('2d');
|
|
568
|
-
ctx.scale(scale, scale);
|
|
569
|
-
if (bearing) {
|
|
570
|
-
ctx.translate(w / 2, h / 2);
|
|
571
|
-
ctx.rotate((-bearing / 180) * Math.PI);
|
|
572
|
-
ctx.translate(-center[0], -center[1]);
|
|
573
|
-
} else {
|
|
574
|
-
// optimized path
|
|
575
|
-
ctx.translate(-center[0] + w / 2, -center[1] + h / 2);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// Draw provided paths if any
|
|
579
|
-
paths.forEach((path, i) => {
|
|
580
|
-
const pathQuery = Array.isArray(query.path) ? query.path.at(i) : query.path;
|
|
581
|
-
drawPath(ctx, path, query, pathQuery, z);
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
// Await drawing of markers before rendering the canvas
|
|
585
|
-
await drawMarkers(ctx, markers, z);
|
|
586
|
-
|
|
587
|
-
return canvas.toBuffer();
|
|
588
|
-
};
|
|
589
|
-
|
|
590
346
|
const calcZForBBox = (bbox, w, h, query) => {
|
|
591
347
|
let z = 25;
|
|
592
348
|
|
|
@@ -608,237 +364,172 @@ const calcZForBBox = (bbox, w, h, query) => {
|
|
|
608
364
|
return z;
|
|
609
365
|
};
|
|
610
366
|
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
});
|
|
367
|
+
const respondImage = (
|
|
368
|
+
options,
|
|
369
|
+
item,
|
|
370
|
+
z,
|
|
371
|
+
lon,
|
|
372
|
+
lat,
|
|
373
|
+
bearing,
|
|
374
|
+
pitch,
|
|
375
|
+
width,
|
|
376
|
+
height,
|
|
377
|
+
scale,
|
|
378
|
+
format,
|
|
379
|
+
res,
|
|
380
|
+
overlay = null,
|
|
381
|
+
mode = 'tile',
|
|
382
|
+
) => {
|
|
383
|
+
if (
|
|
384
|
+
Math.abs(lon) > 180 ||
|
|
385
|
+
Math.abs(lat) > 85.06 ||
|
|
386
|
+
lon !== lon ||
|
|
387
|
+
lat !== lat
|
|
388
|
+
) {
|
|
389
|
+
return res.status(400).send('Invalid center');
|
|
390
|
+
}
|
|
636
391
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
392
|
+
if (
|
|
393
|
+
Math.min(width, height) <= 0 ||
|
|
394
|
+
Math.max(width, height) * scale > (options.maxSize || 2048) ||
|
|
395
|
+
width !== width ||
|
|
396
|
+
height !== height
|
|
397
|
+
) {
|
|
398
|
+
return res.status(400).send('Invalid size');
|
|
399
|
+
}
|
|
643
400
|
|
|
644
|
-
|
|
401
|
+
if (format === 'png' || format === 'webp') {
|
|
402
|
+
} else if (format === 'jpg' || format === 'jpeg') {
|
|
403
|
+
format = 'jpeg';
|
|
404
|
+
} else {
|
|
405
|
+
return res.status(400).send('Invalid format');
|
|
406
|
+
}
|
|
645
407
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
408
|
+
const tileMargin = Math.max(options.tileMargin || 0, 0);
|
|
409
|
+
let pool;
|
|
410
|
+
if (mode === 'tile' && tileMargin === 0) {
|
|
411
|
+
pool = item.map.renderers[scale];
|
|
412
|
+
} else {
|
|
413
|
+
pool = item.map.renderersStatic[scale];
|
|
414
|
+
}
|
|
415
|
+
pool.acquire((err, renderer) => {
|
|
416
|
+
const mlglZ = Math.max(0, z - 1);
|
|
417
|
+
const params = {
|
|
418
|
+
zoom: mlglZ,
|
|
419
|
+
center: [lon, lat],
|
|
651
420
|
bearing,
|
|
652
421
|
pitch,
|
|
653
422
|
width,
|
|
654
423
|
height,
|
|
655
|
-
|
|
656
|
-
format,
|
|
657
|
-
res,
|
|
658
|
-
next,
|
|
659
|
-
opt_overlay,
|
|
660
|
-
opt_mode = 'tile',
|
|
661
|
-
) => {
|
|
662
|
-
if (
|
|
663
|
-
Math.abs(lon) > 180 ||
|
|
664
|
-
Math.abs(lat) > 85.06 ||
|
|
665
|
-
lon !== lon ||
|
|
666
|
-
lat !== lat
|
|
667
|
-
) {
|
|
668
|
-
return res.status(400).send('Invalid center');
|
|
669
|
-
}
|
|
424
|
+
};
|
|
670
425
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
height !== height
|
|
676
|
-
) {
|
|
677
|
-
return res.status(400).send('Invalid size');
|
|
678
|
-
}
|
|
426
|
+
if (z === 0) {
|
|
427
|
+
params.width *= 2;
|
|
428
|
+
params.height *= 2;
|
|
429
|
+
}
|
|
679
430
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
return res.status(400).send('Invalid format');
|
|
685
|
-
}
|
|
431
|
+
if (z > 2 && tileMargin > 0) {
|
|
432
|
+
params.width += tileMargin * 2;
|
|
433
|
+
params.height += tileMargin * 2;
|
|
434
|
+
}
|
|
686
435
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
if (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
pool = item.map.renderers_static[scale];
|
|
436
|
+
renderer.render(params, (err, data) => {
|
|
437
|
+
pool.release(renderer);
|
|
438
|
+
if (err) {
|
|
439
|
+
console.error(err);
|
|
440
|
+
return res.status(500).header('Content-Type', 'text/plain').send(err);
|
|
693
441
|
}
|
|
694
|
-
pool.acquire((err, renderer) => {
|
|
695
|
-
const mlglZ = Math.max(0, z - 1);
|
|
696
|
-
const params = {
|
|
697
|
-
zoom: mlglZ,
|
|
698
|
-
center: [lon, lat],
|
|
699
|
-
bearing: bearing,
|
|
700
|
-
pitch: pitch,
|
|
701
|
-
width: width,
|
|
702
|
-
height: height,
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
if (z === 0) {
|
|
706
|
-
params.width *= 2;
|
|
707
|
-
params.height *= 2;
|
|
708
|
-
}
|
|
709
442
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
console.error(err);
|
|
719
|
-
return res
|
|
720
|
-
.status(500)
|
|
721
|
-
.header('Content-Type', 'text/plain')
|
|
722
|
-
.send(err);
|
|
723
|
-
}
|
|
443
|
+
const image = sharp(data, {
|
|
444
|
+
raw: {
|
|
445
|
+
premultiplied: true,
|
|
446
|
+
width: params.width * scale,
|
|
447
|
+
height: params.height * scale,
|
|
448
|
+
channels: 4,
|
|
449
|
+
},
|
|
450
|
+
});
|
|
724
451
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
}
|
|
452
|
+
if (z > 2 && tileMargin > 0) {
|
|
453
|
+
const [_, y] = mercator.px(params.center, z);
|
|
454
|
+
let yoffset = Math.max(
|
|
455
|
+
Math.min(0, y - 128 - tileMargin),
|
|
456
|
+
y + 128 + tileMargin - Math.pow(2, z + 8),
|
|
457
|
+
);
|
|
458
|
+
image.extract({
|
|
459
|
+
left: tileMargin * scale,
|
|
460
|
+
top: (tileMargin + yoffset) * scale,
|
|
461
|
+
width: width * scale,
|
|
462
|
+
height: height * scale,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
740
465
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
channels: 4,
|
|
746
|
-
},
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
if (z > 2 && tileMargin > 0) {
|
|
750
|
-
const [_, y] = mercator.px(params.center, z);
|
|
751
|
-
let yoffset = Math.max(
|
|
752
|
-
Math.min(0, y - 128 - tileMargin),
|
|
753
|
-
y + 128 + tileMargin - Math.pow(2, z + 8),
|
|
754
|
-
);
|
|
755
|
-
image.extract({
|
|
756
|
-
left: tileMargin * scale,
|
|
757
|
-
top: (tileMargin + yoffset) * scale,
|
|
758
|
-
width: width * scale,
|
|
759
|
-
height: height * scale,
|
|
760
|
-
});
|
|
761
|
-
}
|
|
466
|
+
if (z === 0) {
|
|
467
|
+
// HACK: when serving zoom 0, resize the 0 tile from 512 to 256
|
|
468
|
+
image.resize(width * scale, height * scale);
|
|
469
|
+
}
|
|
762
470
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
471
|
+
const composites = [];
|
|
472
|
+
if (overlay) {
|
|
473
|
+
composites.push({ input: overlay });
|
|
474
|
+
}
|
|
475
|
+
if (item.watermark) {
|
|
476
|
+
const canvas = renderWatermark(width, height, scale, item.watermark);
|
|
767
477
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
composite_array.push({ input: opt_overlay });
|
|
771
|
-
}
|
|
772
|
-
if (item.watermark) {
|
|
773
|
-
const canvas = createCanvas(scale * width, scale * height);
|
|
774
|
-
const ctx = canvas.getContext('2d');
|
|
775
|
-
ctx.scale(scale, scale);
|
|
776
|
-
ctx.font = '10px sans-serif';
|
|
777
|
-
ctx.strokeWidth = '1px';
|
|
778
|
-
ctx.strokeStyle = 'rgba(255,255,255,.4)';
|
|
779
|
-
ctx.strokeText(item.watermark, 5, height - 5);
|
|
780
|
-
ctx.fillStyle = 'rgba(0,0,0,.4)';
|
|
781
|
-
ctx.fillText(item.watermark, 5, height - 5);
|
|
782
|
-
|
|
783
|
-
composite_array.push({ input: canvas.toBuffer() });
|
|
784
|
-
}
|
|
478
|
+
composites.push({ input: canvas.toBuffer() });
|
|
479
|
+
}
|
|
785
480
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const textMetrics = ctx.measureText(text);
|
|
794
|
-
const textWidth = textMetrics.width;
|
|
795
|
-
const textHeight = 14;
|
|
796
|
-
|
|
797
|
-
const padding = 6;
|
|
798
|
-
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
|
799
|
-
ctx.fillRect(
|
|
800
|
-
width - textWidth - padding,
|
|
801
|
-
height - textHeight - padding,
|
|
802
|
-
textWidth + padding,
|
|
803
|
-
textHeight + padding,
|
|
804
|
-
);
|
|
805
|
-
ctx.fillStyle = 'rgba(0,0,0,.8)';
|
|
806
|
-
ctx.fillText(
|
|
807
|
-
item.staticAttributionText,
|
|
808
|
-
width - textWidth - padding / 2,
|
|
809
|
-
height - textHeight + 8,
|
|
810
|
-
);
|
|
481
|
+
if (mode === 'static' && item.staticAttributionText) {
|
|
482
|
+
const canvas = renderAttribution(
|
|
483
|
+
width,
|
|
484
|
+
height,
|
|
485
|
+
scale,
|
|
486
|
+
item.staticAttributionText,
|
|
487
|
+
);
|
|
811
488
|
|
|
812
|
-
|
|
813
|
-
|
|
489
|
+
composites.push({ input: canvas.toBuffer() });
|
|
490
|
+
}
|
|
814
491
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
492
|
+
if (composites.length > 0) {
|
|
493
|
+
image.composite(composites);
|
|
494
|
+
}
|
|
818
495
|
|
|
819
|
-
|
|
496
|
+
const formatQuality = (options.formatQuality || {})[format];
|
|
820
497
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
498
|
+
if (format === 'png') {
|
|
499
|
+
image.png({ adaptiveFiltering: false });
|
|
500
|
+
} else if (format === 'jpeg') {
|
|
501
|
+
image.jpeg({ quality: formatQuality || 80 });
|
|
502
|
+
} else if (format === 'webp') {
|
|
503
|
+
image.webp({ quality: formatQuality || 90 });
|
|
504
|
+
}
|
|
505
|
+
image.toBuffer((err, buffer, info) => {
|
|
506
|
+
if (!buffer) {
|
|
507
|
+
return res.status(404).send('Not found');
|
|
508
|
+
}
|
|
832
509
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
});
|
|
837
|
-
return res.status(200).send(buffer);
|
|
838
|
-
});
|
|
510
|
+
res.set({
|
|
511
|
+
'Last-Modified': item.lastModified,
|
|
512
|
+
'Content-Type': `image/${format}`,
|
|
839
513
|
});
|
|
514
|
+
return res.status(200).send(buffer);
|
|
840
515
|
});
|
|
841
|
-
};
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const existingFonts = {};
|
|
521
|
+
let maxScaleFactor = 2;
|
|
522
|
+
|
|
523
|
+
export const serve_rendered = {
|
|
524
|
+
init: async (options, repo) => {
|
|
525
|
+
maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9);
|
|
526
|
+
let scalePattern = '';
|
|
527
|
+
for (let i = 2; i <= maxScaleFactor; i++) {
|
|
528
|
+
scalePattern += i.toFixed();
|
|
529
|
+
}
|
|
530
|
+
scalePattern = `@[${scalePattern}]x`;
|
|
531
|
+
|
|
532
|
+
const app = express().disable('x-powered-by');
|
|
842
533
|
|
|
843
534
|
app.get(
|
|
844
535
|
`/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`,
|
|
@@ -879,19 +570,10 @@ export const serve_rendered = {
|
|
|
879
570
|
],
|
|
880
571
|
z,
|
|
881
572
|
);
|
|
573
|
+
|
|
574
|
+
// prettier-ignore
|
|
882
575
|
return respondImage(
|
|
883
|
-
item,
|
|
884
|
-
z,
|
|
885
|
-
tileCenter[0],
|
|
886
|
-
tileCenter[1],
|
|
887
|
-
0,
|
|
888
|
-
0,
|
|
889
|
-
tileSize,
|
|
890
|
-
tileSize,
|
|
891
|
-
scale,
|
|
892
|
-
format,
|
|
893
|
-
res,
|
|
894
|
-
next,
|
|
576
|
+
options, item, z, tileCenter[0], tileCenter[1], 0, 0, tileSize, tileSize, scale, format, res,
|
|
895
577
|
);
|
|
896
578
|
},
|
|
897
579
|
);
|
|
@@ -947,35 +629,15 @@ export const serve_rendered = {
|
|
|
947
629
|
options,
|
|
948
630
|
transformer,
|
|
949
631
|
);
|
|
632
|
+
|
|
633
|
+
// prettier-ignore
|
|
950
634
|
const overlay = await renderOverlay(
|
|
951
|
-
z,
|
|
952
|
-
x,
|
|
953
|
-
y,
|
|
954
|
-
bearing,
|
|
955
|
-
pitch,
|
|
956
|
-
w,
|
|
957
|
-
h,
|
|
958
|
-
scale,
|
|
959
|
-
paths,
|
|
960
|
-
markers,
|
|
961
|
-
req.query,
|
|
635
|
+
z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query,
|
|
962
636
|
);
|
|
963
637
|
|
|
638
|
+
// prettier-ignore
|
|
964
639
|
return respondImage(
|
|
965
|
-
item,
|
|
966
|
-
z,
|
|
967
|
-
x,
|
|
968
|
-
y,
|
|
969
|
-
bearing,
|
|
970
|
-
pitch,
|
|
971
|
-
w,
|
|
972
|
-
h,
|
|
973
|
-
scale,
|
|
974
|
-
format,
|
|
975
|
-
res,
|
|
976
|
-
next,
|
|
977
|
-
overlay,
|
|
978
|
-
'static',
|
|
640
|
+
options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static',
|
|
979
641
|
);
|
|
980
642
|
} catch (e) {
|
|
981
643
|
next(e);
|
|
@@ -1029,34 +691,15 @@ export const serve_rendered = {
|
|
|
1029
691
|
options,
|
|
1030
692
|
transformer,
|
|
1031
693
|
);
|
|
694
|
+
|
|
695
|
+
// prettier-ignore
|
|
1032
696
|
const overlay = await renderOverlay(
|
|
1033
|
-
z,
|
|
1034
|
-
x,
|
|
1035
|
-
y,
|
|
1036
|
-
bearing,
|
|
1037
|
-
pitch,
|
|
1038
|
-
w,
|
|
1039
|
-
h,
|
|
1040
|
-
scale,
|
|
1041
|
-
paths,
|
|
1042
|
-
markers,
|
|
1043
|
-
req.query,
|
|
697
|
+
z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query,
|
|
1044
698
|
);
|
|
699
|
+
|
|
700
|
+
// prettier-ignore
|
|
1045
701
|
return respondImage(
|
|
1046
|
-
item,
|
|
1047
|
-
z,
|
|
1048
|
-
x,
|
|
1049
|
-
y,
|
|
1050
|
-
bearing,
|
|
1051
|
-
pitch,
|
|
1052
|
-
w,
|
|
1053
|
-
h,
|
|
1054
|
-
scale,
|
|
1055
|
-
format,
|
|
1056
|
-
res,
|
|
1057
|
-
next,
|
|
1058
|
-
overlay,
|
|
1059
|
-
'static',
|
|
702
|
+
options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static',
|
|
1060
703
|
);
|
|
1061
704
|
} catch (e) {
|
|
1062
705
|
next(e);
|
|
@@ -1162,35 +805,14 @@ export const serve_rendered = {
|
|
|
1162
805
|
const x = center[0];
|
|
1163
806
|
const y = center[1];
|
|
1164
807
|
|
|
808
|
+
// prettier-ignore
|
|
1165
809
|
const overlay = await renderOverlay(
|
|
1166
|
-
z,
|
|
1167
|
-
x,
|
|
1168
|
-
y,
|
|
1169
|
-
bearing,
|
|
1170
|
-
pitch,
|
|
1171
|
-
w,
|
|
1172
|
-
h,
|
|
1173
|
-
scale,
|
|
1174
|
-
paths,
|
|
1175
|
-
markers,
|
|
1176
|
-
req.query,
|
|
810
|
+
z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query,
|
|
1177
811
|
);
|
|
1178
812
|
|
|
813
|
+
// prettier-ignore
|
|
1179
814
|
return respondImage(
|
|
1180
|
-
item,
|
|
1181
|
-
z,
|
|
1182
|
-
x,
|
|
1183
|
-
y,
|
|
1184
|
-
bearing,
|
|
1185
|
-
pitch,
|
|
1186
|
-
w,
|
|
1187
|
-
h,
|
|
1188
|
-
scale,
|
|
1189
|
-
format,
|
|
1190
|
-
res,
|
|
1191
|
-
next,
|
|
1192
|
-
overlay,
|
|
1193
|
-
'static',
|
|
815
|
+
options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static',
|
|
1194
816
|
);
|
|
1195
817
|
} catch (e) {
|
|
1196
818
|
next(e);
|
|
@@ -1215,22 +837,24 @@ export const serve_rendered = {
|
|
|
1215
837
|
return res.send(info);
|
|
1216
838
|
});
|
|
1217
839
|
|
|
1218
|
-
|
|
840
|
+
const fonts = await listFonts(options.paths.fonts);
|
|
841
|
+
Object.assign(existingFonts, fonts);
|
|
842
|
+
return app;
|
|
1219
843
|
},
|
|
1220
844
|
add: async (options, repo, params, id, publicUrl, dataResolver) => {
|
|
1221
845
|
const map = {
|
|
1222
846
|
renderers: [],
|
|
1223
|
-
|
|
847
|
+
renderersStatic: [],
|
|
1224
848
|
sources: {},
|
|
1225
|
-
|
|
849
|
+
sourceTypes: {},
|
|
1226
850
|
};
|
|
1227
851
|
|
|
1228
852
|
let styleJSON;
|
|
1229
853
|
const createPool = (ratio, mode, min, max) => {
|
|
1230
854
|
const createRenderer = (ratio, createCallback) => {
|
|
1231
855
|
const renderer = new mlgl.Map({
|
|
1232
|
-
mode
|
|
1233
|
-
ratio
|
|
856
|
+
mode,
|
|
857
|
+
ratio,
|
|
1234
858
|
request: async (req, callback) => {
|
|
1235
859
|
const protocol = req.url.split(':')[0];
|
|
1236
860
|
// console.log('Handling request:', req);
|
|
@@ -1244,25 +868,24 @@ export const serve_rendered = {
|
|
|
1244
868
|
const parts = req.url.split('/');
|
|
1245
869
|
const fontstack = unescape(parts[2]);
|
|
1246
870
|
const range = parts[3].split('.')[0];
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
);
|
|
871
|
+
|
|
872
|
+
try {
|
|
873
|
+
const concatenated = await getFontsPbf(
|
|
874
|
+
null,
|
|
875
|
+
options.paths[protocol],
|
|
876
|
+
fontstack,
|
|
877
|
+
range,
|
|
878
|
+
existingFonts,
|
|
879
|
+
);
|
|
880
|
+
callback(null, { data: concatenated });
|
|
881
|
+
} catch (err) {
|
|
882
|
+
callback(err, { data: null });
|
|
883
|
+
}
|
|
1261
884
|
} else if (protocol === 'mbtiles' || protocol === 'pmtiles') {
|
|
1262
885
|
const parts = req.url.split('/');
|
|
1263
886
|
const sourceId = parts[2];
|
|
1264
887
|
const source = map.sources[sourceId];
|
|
1265
|
-
const
|
|
888
|
+
const sourceType = map.sourceTypes[sourceId];
|
|
1266
889
|
const sourceInfo = styleJSON.sources[sourceId];
|
|
1267
890
|
|
|
1268
891
|
const z = parts[3] | 0;
|
|
@@ -1270,8 +893,8 @@ export const serve_rendered = {
|
|
|
1270
893
|
const y = parts[5].split('.')[0] | 0;
|
|
1271
894
|
const format = parts[5].split('.')[1];
|
|
1272
895
|
|
|
1273
|
-
if (
|
|
1274
|
-
let tileinfo = await
|
|
896
|
+
if (sourceType === 'pmtiles') {
|
|
897
|
+
let tileinfo = await getPMtilesTile(source, z, x, y);
|
|
1275
898
|
let data = tileinfo.data;
|
|
1276
899
|
let headers = tileinfo.header;
|
|
1277
900
|
if (data == undefined) {
|
|
@@ -1305,7 +928,7 @@ export const serve_rendered = {
|
|
|
1305
928
|
|
|
1306
929
|
callback(null, response);
|
|
1307
930
|
}
|
|
1308
|
-
} else if (
|
|
931
|
+
} else if (sourceType === 'mbtiles') {
|
|
1309
932
|
source.getTile(z, x, y, (err, data, headers) => {
|
|
1310
933
|
if (err) {
|
|
1311
934
|
if (options.verbose)
|
|
@@ -1390,8 +1013,8 @@ export const serve_rendered = {
|
|
|
1390
1013
|
createCallback(null, renderer);
|
|
1391
1014
|
};
|
|
1392
1015
|
return new advancedPool.Pool({
|
|
1393
|
-
min
|
|
1394
|
-
max
|
|
1016
|
+
min,
|
|
1017
|
+
max,
|
|
1395
1018
|
create: createRenderer.bind(null, ratio),
|
|
1396
1019
|
destroy: (renderer) => {
|
|
1397
1020
|
renderer.release();
|
|
@@ -1466,7 +1089,7 @@ export const serve_rendered = {
|
|
|
1466
1089
|
|
|
1467
1090
|
const queue = [];
|
|
1468
1091
|
for (const name of Object.keys(styleJSON.sources)) {
|
|
1469
|
-
let
|
|
1092
|
+
let sourceType;
|
|
1470
1093
|
let source = styleJSON.sources[name];
|
|
1471
1094
|
let url = source.url;
|
|
1472
1095
|
if (
|
|
@@ -1487,10 +1110,10 @@ export const serve_rendered = {
|
|
|
1487
1110
|
}
|
|
1488
1111
|
|
|
1489
1112
|
let inputFile;
|
|
1490
|
-
const
|
|
1491
|
-
if (
|
|
1492
|
-
inputFile =
|
|
1493
|
-
|
|
1113
|
+
const dataInfo = dataResolver(dataId);
|
|
1114
|
+
if (dataInfo.inputFile) {
|
|
1115
|
+
inputFile = dataInfo.inputFile;
|
|
1116
|
+
sourceType = dataInfo.fileType;
|
|
1494
1117
|
} else {
|
|
1495
1118
|
console.error(`ERROR: data "${inputFile}" not found!`);
|
|
1496
1119
|
process.exit(1);
|
|
@@ -1503,10 +1126,10 @@ export const serve_rendered = {
|
|
|
1503
1126
|
}
|
|
1504
1127
|
}
|
|
1505
1128
|
|
|
1506
|
-
if (
|
|
1507
|
-
map.sources[name] =
|
|
1508
|
-
map.
|
|
1509
|
-
const metadata = await
|
|
1129
|
+
if (sourceType === 'pmtiles') {
|
|
1130
|
+
map.sources[name] = openPMtiles(inputFile);
|
|
1131
|
+
map.sourceTypes[name] = 'pmtiles';
|
|
1132
|
+
const metadata = await getPMtilesInfo(map.sources[name]);
|
|
1510
1133
|
|
|
1511
1134
|
if (!repoobj.dataProjWGStoInternalWGS && metadata.proj4) {
|
|
1512
1135
|
// how to do this for multiple sources with different proj4 defs?
|
|
@@ -1551,7 +1174,7 @@ export const serve_rendered = {
|
|
|
1551
1174
|
console.error(err);
|
|
1552
1175
|
return;
|
|
1553
1176
|
}
|
|
1554
|
-
map.
|
|
1177
|
+
map.sourceTypes[name] = 'mbtiles';
|
|
1555
1178
|
|
|
1556
1179
|
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
|
1557
1180
|
// how to do this for multiple sources with different proj4 defs?
|
|
@@ -1599,26 +1222,24 @@ export const serve_rendered = {
|
|
|
1599
1222
|
}
|
|
1600
1223
|
}
|
|
1601
1224
|
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
return Promise.all([renderersReadyPromise]);
|
|
1225
|
+
await Promise.all(queue);
|
|
1226
|
+
|
|
1227
|
+
// standard and @2x tiles are much more usual -> default to larger pools
|
|
1228
|
+
const minPoolSizes = options.minRendererPoolSizes || [8, 4, 2];
|
|
1229
|
+
const maxPoolSizes = options.maxRendererPoolSizes || [16, 8, 4];
|
|
1230
|
+
for (let s = 1; s <= maxScaleFactor; s++) {
|
|
1231
|
+
const i = Math.min(minPoolSizes.length - 1, s - 1);
|
|
1232
|
+
const j = Math.min(maxPoolSizes.length - 1, s - 1);
|
|
1233
|
+
const minPoolSize = minPoolSizes[i];
|
|
1234
|
+
const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
|
|
1235
|
+
map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize);
|
|
1236
|
+
map.renderersStatic[s] = createPool(
|
|
1237
|
+
s,
|
|
1238
|
+
'static',
|
|
1239
|
+
minPoolSize,
|
|
1240
|
+
maxPoolSize,
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1622
1243
|
},
|
|
1623
1244
|
remove: (repo, id) => {
|
|
1624
1245
|
const item = repo[id];
|
|
@@ -1626,7 +1247,7 @@ export const serve_rendered = {
|
|
|
1626
1247
|
item.map.renderers.forEach((pool) => {
|
|
1627
1248
|
pool.close();
|
|
1628
1249
|
});
|
|
1629
|
-
item.map.
|
|
1250
|
+
item.map.renderersStatic.forEach((pool) => {
|
|
1630
1251
|
pool.close();
|
|
1631
1252
|
});
|
|
1632
1253
|
}
|