@versatiles/svg-renderer 0.1.0 → 0.2.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 +137 -13
- package/dist/index.cjs +147 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +147 -59
- package/dist/index.js.map +1 -1
- package/dist/maplibre.cjs +16369 -0
- package/dist/maplibre.cjs.map +1 -0
- package/dist/maplibre.d.ts +41 -0
- package/dist/maplibre.js +16366 -0
- package/dist/maplibre.js.map +1 -0
- package/package.json +25 -4
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@versatiles/svg-renderer)
|
|
2
|
+
[](https://www.npmjs.com/package/@versatiles/svg-renderer)
|
|
3
|
+
[](https://codecov.io/gh/versatiles-org/versatiles-svg-renderer)
|
|
4
|
+
[](https://github.com/versatiles-org/versatiles-svg-renderer/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
1
7
|
# VersaTiles SVG Renderer
|
|
2
8
|
|
|
3
9
|
Renders vector maps as SVG.
|
|
@@ -6,7 +12,111 @@ Renders vector maps as SVG.
|
|
|
6
12
|
|
|
7
13
|
[Download SVG](docs/demo.svg)
|
|
8
14
|
|
|
9
|
-
Currently
|
|
15
|
+
Currently supported layer types: background, fill, line, and raster.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @versatiles/svg-renderer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Node.js
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { renderToSVG } from '@versatiles/svg-renderer';
|
|
29
|
+
import { styles } from '@versatiles/style';
|
|
30
|
+
import { writeFileSync } from 'node:fs';
|
|
31
|
+
|
|
32
|
+
const svg = await renderToSVG({
|
|
33
|
+
style: styles.colorful(),
|
|
34
|
+
width: 800,
|
|
35
|
+
height: 600,
|
|
36
|
+
lon: 13.4,
|
|
37
|
+
lat: 52.5,
|
|
38
|
+
zoom: 10,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
writeFileSync('map.svg', svg);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Browser
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { renderToSVG } from '@versatiles/svg-renderer';
|
|
48
|
+
|
|
49
|
+
const svg = await renderToSVG({
|
|
50
|
+
style: await fetch('https://tiles.versatiles.org/assets/styles/colorful/style.json').then((r) =>
|
|
51
|
+
r.json(),
|
|
52
|
+
),
|
|
53
|
+
width: 800,
|
|
54
|
+
height: 600,
|
|
55
|
+
lon: 13.4,
|
|
56
|
+
lat: 52.5,
|
|
57
|
+
zoom: 10,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
document.body.innerHTML = svg;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### MapLibre Plugin
|
|
64
|
+
|
|
65
|
+
The package includes an `SVGExportControl` that adds an export button to any MapLibre GL JS map.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install @versatiles/svg-renderer maplibre-gl
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<!DOCTYPE html>
|
|
73
|
+
<html>
|
|
74
|
+
<head>
|
|
75
|
+
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.css" />
|
|
76
|
+
<script src="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.js"></script>
|
|
77
|
+
<script src="…/svg-renderer/dist/maplibre.cjs"></script>
|
|
78
|
+
<style></style>
|
|
79
|
+
</head>
|
|
80
|
+
<body>
|
|
81
|
+
<div id="map"></div>
|
|
82
|
+
<script>
|
|
83
|
+
const map = new maplibregl.Map({
|
|
84
|
+
container: 'map',
|
|
85
|
+
style: 'https://tiles.versatiles.org/assets/styles/colorful/style.json',
|
|
86
|
+
center: [13.4, 52.5],
|
|
87
|
+
zoom: 10,
|
|
88
|
+
});
|
|
89
|
+
map.addControl(new SVGExportControl(), 'top-right');
|
|
90
|
+
</script>
|
|
91
|
+
</body>
|
|
92
|
+
</html>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The control opens a panel where the user can set width, height, and scale, preview the SVG, download it, or open it in a new tab. Map interactions are disabled while the panel is open.
|
|
96
|
+
|
|
97
|
+
Options:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
new SVGExportControl({
|
|
101
|
+
defaultWidth: 1024, // default: 1024
|
|
102
|
+
defaultHeight: 1024, // default: 1024
|
|
103
|
+
defaultScale: 1, // default: 1
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## API
|
|
108
|
+
|
|
109
|
+
### `renderToSVG(options): Promise<string>`
|
|
110
|
+
|
|
111
|
+
| Option | Type | Default | Description |
|
|
112
|
+
| -------- | -------------------- | ------------ | ---------------------------- |
|
|
113
|
+
| `style` | `StyleSpecification` | *(required)* | MapLibre style specification |
|
|
114
|
+
| `width` | `number` | `1024` | Output width in pixels |
|
|
115
|
+
| `height` | `number` | `1024` | Output height in pixels |
|
|
116
|
+
| `scale` | `number` | `1` | Scale factor |
|
|
117
|
+
| `lon` | `number` | `0` | Center longitude |
|
|
118
|
+
| `lat` | `number` | `0` | Center latitude |
|
|
119
|
+
| `zoom` | `number` | `2` | Zoom level |
|
|
10
120
|
|
|
11
121
|
## E2E Visual Comparison
|
|
12
122
|
|
|
@@ -31,33 +141,47 @@ subgraph 0["src"]
|
|
|
31
141
|
subgraph 3["lib"]
|
|
32
142
|
4["geometry.ts"]
|
|
33
143
|
7["color.ts"]
|
|
34
|
-
|
|
144
|
+
B["style_layer.ts"]
|
|
35
145
|
end
|
|
36
146
|
subgraph 5["processor"]
|
|
37
147
|
6["render.ts"]
|
|
38
|
-
8["
|
|
39
|
-
|
|
40
|
-
|
|
148
|
+
8["raster.ts"]
|
|
149
|
+
9["tiles.ts"]
|
|
150
|
+
A["styles.ts"]
|
|
151
|
+
C["vector.ts"]
|
|
152
|
+
D["helper.ts"]
|
|
153
|
+
end
|
|
154
|
+
subgraph E["renderer"]
|
|
155
|
+
F["renderer_svg.ts"]
|
|
41
156
|
end
|
|
42
|
-
subgraph
|
|
43
|
-
|
|
157
|
+
subgraph G["maplibre"]
|
|
158
|
+
H["control.ts"]
|
|
159
|
+
I["styles.ts"]
|
|
160
|
+
J["index.ts"]
|
|
44
161
|
end
|
|
45
|
-
|
|
162
|
+
K["types.ts"]
|
|
46
163
|
end
|
|
47
164
|
1-->2
|
|
48
165
|
2-->4
|
|
49
166
|
2-->6
|
|
50
|
-
2-->
|
|
167
|
+
2-->F
|
|
51
168
|
6-->7
|
|
52
169
|
6-->4
|
|
53
170
|
6-->8
|
|
54
171
|
6-->A
|
|
172
|
+
6-->C
|
|
55
173
|
8-->9
|
|
56
|
-
A-->4
|
|
57
174
|
A-->B
|
|
58
|
-
|
|
59
|
-
D
|
|
175
|
+
C-->4
|
|
176
|
+
C-->D
|
|
177
|
+
C-->9
|
|
178
|
+
D-->4
|
|
179
|
+
F-->7
|
|
180
|
+
H-->2
|
|
181
|
+
H-->I
|
|
182
|
+
J-->2
|
|
183
|
+
J-->H
|
|
60
184
|
|
|
61
|
-
class 0,3,5,
|
|
185
|
+
class 0,3,5,E,G subgraphs;
|
|
62
186
|
classDef subgraphs fill-opacity:0.1, fill:#888, color:#888, stroke:#888;
|
|
63
187
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -9158,6 +9158,37 @@ class SVGRenderer {
|
|
|
9158
9158
|
}
|
|
9159
9159
|
this.#svg.push('</g>');
|
|
9160
9160
|
}
|
|
9161
|
+
drawRasterTiles(tiles, style) {
|
|
9162
|
+
if (tiles.length === 0)
|
|
9163
|
+
return;
|
|
9164
|
+
if (style.opacity <= 0)
|
|
9165
|
+
return;
|
|
9166
|
+
const filters = [];
|
|
9167
|
+
if (style.hueRotate !== 0)
|
|
9168
|
+
filters.push(`hue-rotate(${String(style.hueRotate)}deg)`);
|
|
9169
|
+
if (style.saturation !== 0)
|
|
9170
|
+
filters.push(`saturate(${String(style.saturation + 1)})`);
|
|
9171
|
+
if (style.contrast !== 0)
|
|
9172
|
+
filters.push(`contrast(${String(style.contrast + 1)})`);
|
|
9173
|
+
if (style.brightnessMin !== 0 || style.brightnessMax !== 1) {
|
|
9174
|
+
const brightness = (style.brightnessMin + style.brightnessMax) / 2;
|
|
9175
|
+
filters.push(`brightness(${String(brightness)})`);
|
|
9176
|
+
}
|
|
9177
|
+
let gAttrs = `opacity="${String(style.opacity)}"`;
|
|
9178
|
+
if (filters.length > 0)
|
|
9179
|
+
gAttrs += ` filter="${filters.join(' ')}"`;
|
|
9180
|
+
this.#svg.push(`<g ${gAttrs}>`);
|
|
9181
|
+
const pixelated = style.resampling === 'nearest';
|
|
9182
|
+
for (const tile of tiles) {
|
|
9183
|
+
const overlap = Math.min(tile.width, tile.height) / 10000; // slight overlap to prevent sub-pixel gaps between tiles
|
|
9184
|
+
const s = this.#scale;
|
|
9185
|
+
let attrs = `x="${roundValue(tile.x - overlap, s)}" y="${roundValue(tile.y - overlap, s)}" width="${roundValue(tile.width + overlap * 2, s)}" height="${roundValue(tile.height + overlap * 2, s)}" href="${tile.dataUri}"`;
|
|
9186
|
+
if (pixelated)
|
|
9187
|
+
attrs += ' style="image-rendering:pixelated"';
|
|
9188
|
+
this.#svg.push(`<image ${attrs} />`);
|
|
9189
|
+
}
|
|
9190
|
+
this.#svg.push('</g>');
|
|
9191
|
+
}
|
|
9161
9192
|
getString() {
|
|
9162
9193
|
return [
|
|
9163
9194
|
`<svg viewBox="0 0 ${String(this.width)} ${String(this.height)}" width="${String(this.width)}" height="${String(this.height)}" xmlns="http://www.w3.org/2000/svg" style="background-color:${this.#backgroundColor.hex}">`,
|
|
@@ -14049,6 +14080,45 @@ function mergePolygons(featureList) {
|
|
|
14049
14080
|
return mergedFeatures;
|
|
14050
14081
|
}
|
|
14051
14082
|
|
|
14083
|
+
function calculateTileGrid(width, height, center, zoom, maxzoom) {
|
|
14084
|
+
const zoomLevel = Math.min(Math.floor(zoom), maxzoom ?? Infinity);
|
|
14085
|
+
const tileCenterCoordinate = center.getProject2Pixel().scale(2 ** zoomLevel);
|
|
14086
|
+
const tileSize = 2 ** (zoom - zoomLevel + 9); // 512 (2^9) is the standard tile size
|
|
14087
|
+
const tileCols = width / tileSize;
|
|
14088
|
+
const tileRows = height / tileSize;
|
|
14089
|
+
const tileMinX = Math.floor(tileCenterCoordinate.x - tileCols / 2);
|
|
14090
|
+
const tileMinY = Math.floor(tileCenterCoordinate.y - tileRows / 2);
|
|
14091
|
+
const tileMaxX = Math.floor(tileCenterCoordinate.x + tileCols / 2);
|
|
14092
|
+
const tileMaxY = Math.floor(tileCenterCoordinate.y + tileRows / 2);
|
|
14093
|
+
const tiles = [];
|
|
14094
|
+
for (let x = tileMinX; x <= tileMaxX; x++) {
|
|
14095
|
+
for (let y = tileMinY; y <= tileMaxY; y++) {
|
|
14096
|
+
tiles.push({
|
|
14097
|
+
x,
|
|
14098
|
+
y,
|
|
14099
|
+
offsetX: width / 2 + (x - tileCenterCoordinate.x) * tileSize,
|
|
14100
|
+
offsetY: height / 2 + (y - tileCenterCoordinate.y) * tileSize,
|
|
14101
|
+
});
|
|
14102
|
+
}
|
|
14103
|
+
}
|
|
14104
|
+
return { zoomLevel, tileSize, tiles };
|
|
14105
|
+
}
|
|
14106
|
+
async function getTile(url, z, x, y) {
|
|
14107
|
+
const tileUrl = url.replace('{z}', String(z)).replace('{x}', String(x)).replace('{y}', String(y));
|
|
14108
|
+
try {
|
|
14109
|
+
const response = await fetch(tileUrl);
|
|
14110
|
+
if (!response.ok)
|
|
14111
|
+
return null;
|
|
14112
|
+
const buffer = await response.arrayBuffer();
|
|
14113
|
+
const contentType = response.headers.get('content-type') ?? 'application/octet-stream';
|
|
14114
|
+
return { buffer, contentType };
|
|
14115
|
+
}
|
|
14116
|
+
catch {
|
|
14117
|
+
console.warn(`Failed to load tile: ${tileUrl}`);
|
|
14118
|
+
return null;
|
|
14119
|
+
}
|
|
14120
|
+
}
|
|
14121
|
+
|
|
14052
14122
|
/**
|
|
14053
14123
|
* A standalone point geometry with useful accessor, comparison, and
|
|
14054
14124
|
* modification methods.
|
|
@@ -15594,32 +15664,22 @@ async function getLayerFeatures(job) {
|
|
|
15594
15664
|
const { zoom, center } = job.view;
|
|
15595
15665
|
const { sources } = job.style;
|
|
15596
15666
|
const source = sources['versatiles-shortbread'];
|
|
15597
|
-
if (
|
|
15667
|
+
if (!source)
|
|
15668
|
+
return new Map();
|
|
15669
|
+
if (source.type !== 'vector' || !source.tiles) {
|
|
15670
|
+
console.error('Invalid source configuration. Expected a vector source with tile URLs.');
|
|
15671
|
+
console.error('Source config:', source);
|
|
15598
15672
|
throw Error('Invalid source');
|
|
15599
15673
|
}
|
|
15600
15674
|
const sourceUrl = source.tiles[0];
|
|
15601
|
-
const zoomLevel =
|
|
15602
|
-
const tileCenterCoordinate = center.getProject2Pixel().scale(2 ** zoomLevel);
|
|
15603
|
-
const tileSize = 2 ** (zoom - zoomLevel + 9); // 512 (2^9) is the standard tile size
|
|
15604
|
-
const tileCols = width / tileSize;
|
|
15605
|
-
const tileRows = height / tileSize;
|
|
15606
|
-
const tileMinX = Math.floor(tileCenterCoordinate.x - tileCols / 2);
|
|
15607
|
-
const tileMinY = Math.floor(tileCenterCoordinate.y - tileRows / 2);
|
|
15608
|
-
const tileMaxX = Math.floor(tileCenterCoordinate.x + tileCols / 2);
|
|
15609
|
-
const tileMaxY = Math.floor(tileCenterCoordinate.y + tileRows / 2);
|
|
15610
|
-
const tileCoordinates = [];
|
|
15611
|
-
for (let x = tileMinX; x <= tileMaxX; x++) {
|
|
15612
|
-
for (let y = tileMinY; y <= tileMaxY; y++) {
|
|
15613
|
-
tileCoordinates.push({ x, y });
|
|
15614
|
-
}
|
|
15615
|
-
}
|
|
15675
|
+
const { zoomLevel, tileSize, tiles: tileCoordinates, } = calculateTileGrid(width, height, center, zoom, source.maxzoom);
|
|
15616
15676
|
const layerFeatures = new Map();
|
|
15617
|
-
await Promise.all(tileCoordinates.map(async ({ x, y }) => {
|
|
15618
|
-
const offset = new Point2D(
|
|
15619
|
-
const
|
|
15620
|
-
if (!
|
|
15677
|
+
await Promise.all(tileCoordinates.map(async ({ x, y, offsetX, offsetY }) => {
|
|
15678
|
+
const offset = new Point2D(offsetX, offsetY);
|
|
15679
|
+
const tile = await getTile(sourceUrl, zoomLevel, x, y);
|
|
15680
|
+
if (!tile)
|
|
15621
15681
|
return;
|
|
15622
|
-
const vectorTile = new VectorTile(new Pbf(buffer));
|
|
15682
|
+
const vectorTile = new VectorTile(new Pbf(tile.buffer));
|
|
15623
15683
|
for (const [name, layer] of Object.entries(vectorTile.layers)) {
|
|
15624
15684
|
let features = layerFeatures.get(name);
|
|
15625
15685
|
if (!features) {
|
|
@@ -15669,18 +15729,33 @@ async function getLayerFeatures(job) {
|
|
|
15669
15729
|
}
|
|
15670
15730
|
return layerFeatures;
|
|
15671
15731
|
}
|
|
15672
|
-
|
|
15673
|
-
|
|
15674
|
-
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
}
|
|
15680
|
-
catch {
|
|
15681
|
-
console.warn(`Failed to load tile: ${tileUrl}`);
|
|
15682
|
-
return null;
|
|
15732
|
+
|
|
15733
|
+
async function getRasterTiles(job, sourceName) {
|
|
15734
|
+
const { width, height } = job.renderer;
|
|
15735
|
+
const { zoom, center } = job.view;
|
|
15736
|
+
const source = job.style.sources[sourceName];
|
|
15737
|
+
if (source?.type !== 'raster' || !source.tiles) {
|
|
15738
|
+
throw Error('Invalid raster source: ' + sourceName);
|
|
15683
15739
|
}
|
|
15740
|
+
const sourceUrl = source.tiles[0];
|
|
15741
|
+
const { zoomLevel, tileSize, tiles } = calculateTileGrid(width, height, center, zoom, source.maxzoom);
|
|
15742
|
+
const rasterTiles = await Promise.all(tiles.map(async ({ x, y, offsetX, offsetY }) => {
|
|
15743
|
+
const tile = await getTile(sourceUrl, zoomLevel, x, y);
|
|
15744
|
+
if (!tile)
|
|
15745
|
+
return null;
|
|
15746
|
+
const base64 = typeof Buffer !== 'undefined'
|
|
15747
|
+
? Buffer.from(tile.buffer).toString('base64')
|
|
15748
|
+
: btoa(String.fromCharCode(...new Uint8Array(tile.buffer)));
|
|
15749
|
+
const dataUri = `data:${tile.contentType};base64,${base64}`;
|
|
15750
|
+
return {
|
|
15751
|
+
x: offsetX,
|
|
15752
|
+
y: offsetY,
|
|
15753
|
+
width: tileSize,
|
|
15754
|
+
height: tileSize,
|
|
15755
|
+
dataUri,
|
|
15756
|
+
};
|
|
15757
|
+
}));
|
|
15758
|
+
return rasterTiles.filter((tile) => tile !== null);
|
|
15684
15759
|
}
|
|
15685
15760
|
|
|
15686
15761
|
/**
|
|
@@ -15818,10 +15893,25 @@ async function render(job) {
|
|
|
15818
15893
|
const layerStyles = getLayerStyles(job.style.layers);
|
|
15819
15894
|
const availableImages = [];
|
|
15820
15895
|
const featureState = {};
|
|
15821
|
-
|
|
15896
|
+
for (const layerStyle of layerStyles) {
|
|
15822
15897
|
if (layerStyle.isHidden(zoom))
|
|
15823
|
-
|
|
15898
|
+
continue;
|
|
15824
15899
|
layerStyle.recalculate({ zoom }, availableImages);
|
|
15900
|
+
function getStyleValue(obj, key, feature) {
|
|
15901
|
+
const getter = obj;
|
|
15902
|
+
const value = getter.get(key);
|
|
15903
|
+
if (typeof value === 'object' && value !== null && 'evaluate' in value) {
|
|
15904
|
+
const evaluatable = value;
|
|
15905
|
+
return evaluatable.evaluate(feature ?? {}, featureState, undefined, availableImages);
|
|
15906
|
+
}
|
|
15907
|
+
return value;
|
|
15908
|
+
}
|
|
15909
|
+
function getPaint(key, feature) {
|
|
15910
|
+
return getStyleValue(layerStyle.paint, key, feature);
|
|
15911
|
+
}
|
|
15912
|
+
function getLayout(key, feature) {
|
|
15913
|
+
return getStyleValue(layerStyle.layout, key, feature);
|
|
15914
|
+
}
|
|
15825
15915
|
switch (layerStyle.type) {
|
|
15826
15916
|
case 'background':
|
|
15827
15917
|
{
|
|
@@ -15830,16 +15920,16 @@ async function render(job) {
|
|
|
15830
15920
|
opacity: getPaint('background-opacity'),
|
|
15831
15921
|
});
|
|
15832
15922
|
}
|
|
15833
|
-
|
|
15923
|
+
continue;
|
|
15834
15924
|
case 'fill':
|
|
15835
15925
|
{
|
|
15836
15926
|
const polygons = layerFeatures.get(layerStyle.sourceLayer)?.polygons;
|
|
15837
15927
|
if (!polygons || polygons.length === 0)
|
|
15838
|
-
|
|
15928
|
+
continue;
|
|
15839
15929
|
const filter = featureFilter(layerStyle.filter);
|
|
15840
15930
|
const polygonFeatures = polygons.filter((feature) => filter.filter({ zoom }, feature));
|
|
15841
15931
|
if (polygonFeatures.length === 0)
|
|
15842
|
-
|
|
15932
|
+
continue;
|
|
15843
15933
|
renderer.drawPolygons(polygonFeatures.map((feature) => [
|
|
15844
15934
|
feature,
|
|
15845
15935
|
{
|
|
@@ -15848,16 +15938,16 @@ async function render(job) {
|
|
|
15848
15938
|
},
|
|
15849
15939
|
]), getPaint('fill-opacity', polygonFeatures[0]));
|
|
15850
15940
|
}
|
|
15851
|
-
|
|
15941
|
+
continue;
|
|
15852
15942
|
case 'line':
|
|
15853
15943
|
{
|
|
15854
15944
|
const lineStrings = layerFeatures.get(layerStyle.sourceLayer)?.linestrings;
|
|
15855
15945
|
if (!lineStrings || lineStrings.length === 0)
|
|
15856
|
-
|
|
15946
|
+
continue;
|
|
15857
15947
|
const filter = featureFilter(layerStyle.filter);
|
|
15858
15948
|
const lineStringFeatures = lineStrings.filter((feature) => filter.filter({ zoom }, feature));
|
|
15859
15949
|
if (lineStringFeatures.length === 0)
|
|
15860
|
-
|
|
15950
|
+
continue;
|
|
15861
15951
|
renderer.drawLineStrings(lineStringFeatures.map((feature) => [
|
|
15862
15952
|
feature,
|
|
15863
15953
|
{
|
|
@@ -15875,34 +15965,32 @@ async function render(job) {
|
|
|
15875
15965
|
},
|
|
15876
15966
|
]), getPaint('line-opacity', lineStringFeatures[0]));
|
|
15877
15967
|
}
|
|
15878
|
-
|
|
15968
|
+
continue;
|
|
15969
|
+
case 'raster':
|
|
15970
|
+
{
|
|
15971
|
+
const tiles = await getRasterTiles(job, layerStyle.source);
|
|
15972
|
+
renderer.drawRasterTiles(tiles, {
|
|
15973
|
+
opacity: getPaint('raster-opacity'),
|
|
15974
|
+
hueRotate: getPaint('raster-hue-rotate'),
|
|
15975
|
+
brightnessMin: getPaint('raster-brightness-min'),
|
|
15976
|
+
brightnessMax: getPaint('raster-brightness-max'),
|
|
15977
|
+
saturation: getPaint('raster-saturation'),
|
|
15978
|
+
contrast: getPaint('raster-contrast'),
|
|
15979
|
+
resampling: getPaint('raster-resampling'),
|
|
15980
|
+
});
|
|
15981
|
+
}
|
|
15982
|
+
continue;
|
|
15879
15983
|
case 'circle':
|
|
15880
15984
|
case 'color-relief':
|
|
15881
15985
|
case 'fill-extrusion':
|
|
15882
15986
|
case 'heatmap':
|
|
15883
15987
|
case 'hillshade':
|
|
15884
|
-
case 'raster':
|
|
15885
15988
|
case 'symbol':
|
|
15886
|
-
|
|
15989
|
+
continue;
|
|
15887
15990
|
default:
|
|
15888
15991
|
throw Error('layerStyle.type: ' + String(layerStyle.type));
|
|
15889
15992
|
}
|
|
15890
|
-
|
|
15891
|
-
const getter = obj;
|
|
15892
|
-
const value = getter.get(key);
|
|
15893
|
-
if (typeof value === 'object' && value !== null && 'evaluate' in value) {
|
|
15894
|
-
const evaluatable = value;
|
|
15895
|
-
return evaluatable.evaluate(feature ?? {}, featureState, undefined, availableImages);
|
|
15896
|
-
}
|
|
15897
|
-
return value;
|
|
15898
|
-
}
|
|
15899
|
-
function getPaint(key, feature) {
|
|
15900
|
-
return getStyleValue(layerStyle.paint, key, feature);
|
|
15901
|
-
}
|
|
15902
|
-
function getLayout(key, feature) {
|
|
15903
|
-
return getStyleValue(layerStyle.layout, key, feature);
|
|
15904
|
-
}
|
|
15905
|
-
});
|
|
15993
|
+
}
|
|
15906
15994
|
}
|
|
15907
15995
|
|
|
15908
15996
|
async function renderToSVG(options) {
|