Skip to content

Commit a722bd3

Browse files
committed
Refactor PreparedLayer to use ResolvedLayer paint/layout properties
1 parent f3f85ef commit a722bd3

16 files changed

Lines changed: 371 additions & 280 deletions

File tree

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type * as z from "zod/mini";
22
import { camera } from "./camera.js";
33
import { DEFAULT_STYLE_URL } from "./constants.js";
4-
import { renderer } from "./renderer.js";
4+
import { renderer } from "./renderer/index.js";
55
import { composite } from "./source/composite.js";
66
import { dummy as dummySource } from "./source/dummy.js";
77
import { raster } from "./source/raster.js";

src/renderer.ts renamed to src/renderer/index.ts

Lines changed: 6 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
import { TILE_EXTENT } from "./constants.js";
2-
import type { PreparedTile, Renderer, Source, Style } from "./types.js";
3-
import { requestIdleCallback } from "./utils.js";
4-
5-
type TileCoordinates = {
6-
x: number;
7-
y: number;
8-
z: number;
9-
};
10-
11-
type RenderedTile = {
12-
coordinates: TileCoordinates;
13-
layerElements: Record<string, SVGElement>;
14-
};
1+
import { TILE_EXTENT } from "../constants.js";
2+
import type { Renderer } from "../types.js";
3+
import { requestIdleCallback } from "../utils.js";
4+
import { render as renderTile } from "./tile.js";
5+
import type { RenderedTile, TileCoordinates } from "./types.js";
156

167
export const renderer = (): Renderer => {
178
const tileCache = new Map<
@@ -137,7 +128,7 @@ export const renderer = (): Renderer => {
137128
const { added, removed } = updateWantedTiles(wantedTiles);
138129

139130
for (const { x, y, z } of added) {
140-
const tile = await renderTileCached({
131+
const tile = await renderTile({
141132
tile: { x, y, z },
142133
cache: tileCache,
143134
source,
@@ -276,98 +267,3 @@ const calculateTransformForTile = ({
276267
scale,
277268
};
278269
};
279-
280-
const renderTileCached = async ({
281-
tile: { x, y, z },
282-
cache,
283-
source,
284-
style,
285-
}: {
286-
tile: { x: number; y: number; z: number };
287-
source: Source;
288-
style: Style;
289-
cache: Map<string, RenderedTile | null>;
290-
}): Promise<RenderedTile | null> => {
291-
const cached = cache.get(`${x}-${y}-${z}`);
292-
if (cached !== undefined) {
293-
return cached;
294-
}
295-
const preparedTile = await style.prepare({ source, tile: { x, y, z } });
296-
const renderedTile = {
297-
coordinates: { x, y, z },
298-
layerElements: renderTile(preparedTile),
299-
};
300-
cache.set(`${x}-${y}-${z}`, renderedTile);
301-
return renderedTile;
302-
};
303-
304-
const renderTile = (tile: PreparedTile): Record<string, SVGElement> => {
305-
const layerElements: Record<string, SVGElement> = {};
306-
307-
for (const layer of Object.values(tile.layers)) {
308-
if (layer.type === "raster") {
309-
const image = document.createElementNS(
310-
"http://www.w3.org/2000/svg",
311-
"image",
312-
);
313-
image.setAttribute("x", "-1");
314-
image.setAttribute("y", "-1");
315-
image.setAttribute("width", (TILE_EXTENT + 2).toString());
316-
image.setAttribute("height", (TILE_EXTENT + 2).toString());
317-
image.setAttribute("href", layer.url);
318-
layerElements[layer.name] = image;
319-
continue;
320-
}
321-
322-
const element = document.createElementNS("http://www.w3.org/2000/svg", "g");
323-
324-
for (const feature of layer.features) {
325-
const path = document.createElementNS(
326-
"http://www.w3.org/2000/svg",
327-
"path",
328-
);
329-
330-
path.setAttribute("data-layername", layer.name);
331-
332-
const d = feature.geometry.commands
333-
.map((command) => {
334-
switch (command.type) {
335-
case "move_to":
336-
return `m${command.x} ${command.y}`;
337-
case "line_to":
338-
return command.points
339-
.map((point) => `l${point.x} ${point.y}`)
340-
.join("");
341-
case "close_path":
342-
return "z";
343-
case "reset":
344-
return "M0 0";
345-
default:
346-
throw new Error("Unknown command type");
347-
}
348-
})
349-
.join("");
350-
path.setAttribute("d", d);
351-
path.setAttribute("fill", feature.static.fill ?? "none");
352-
path.setAttribute("stroke", feature.static.stroke ?? "none");
353-
path.setAttribute(
354-
"stroke-width",
355-
feature.static.strokeWidth?.toString() ?? "1",
356-
);
357-
if (feature.static.opacity) {
358-
path.setAttribute("opacity", feature.static.opacity?.toString() ?? "1");
359-
}
360-
if (feature.static.fillTranslate) {
361-
path.setAttribute(
362-
"transform",
363-
`translate(${feature.static.fillTranslate.x} ${feature.static.fillTranslate.y})`,
364-
);
365-
}
366-
element.appendChild(path);
367-
}
368-
369-
layerElements[layer.name] = element;
370-
}
371-
372-
return layerElements;
373-
};

src/renderer/layers/common.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { PreparedFeatureGeometry } from "../../types.js";
2+
3+
export const getResolvedValue = <T>(
4+
value:
5+
| { type: "constant"; value: T }
6+
| { type: "dynamic"; value: () => T }
7+
| undefined,
8+
): T | undefined => {
9+
if (value?.type === "constant") return value.value;
10+
if (value?.type === "dynamic") return value.value();
11+
return undefined;
12+
};
13+
14+
export const getSvgPathD = (geometry: PreparedFeatureGeometry) =>
15+
geometry.commands
16+
.map((command) => {
17+
switch (command.type) {
18+
case "move_to":
19+
return `m${command.x} ${command.y}`;
20+
case "line_to":
21+
return command.points
22+
.map((point) => `l${point.x} ${point.y}`)
23+
.join("");
24+
case "close_path":
25+
return "z";
26+
case "reset":
27+
return "M0 0";
28+
default:
29+
throw new Error("Unknown command type");
30+
}
31+
})
32+
.join("");

src/renderer/layers/fill.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { PreparedLayer } from "../../types.js";
2+
import { getResolvedValue, getSvgPathD } from "./common.js";
3+
4+
export const render = (layer: Extract<PreparedLayer, { type: "fill" }>) => {
5+
const element = document.createElementNS("http://www.w3.org/2000/svg", "g");
6+
7+
for (const feature of layer.features) {
8+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
9+
10+
path.setAttribute("data-layername", layer.name);
11+
12+
const d = getSvgPathD(feature.geometry);
13+
path.setAttribute("d", d);
14+
15+
path.setAttribute(
16+
"fill",
17+
getResolvedValue(feature.paint?.["fill-color"]) ?? "black",
18+
);
19+
20+
const opacity = getResolvedValue(feature.paint?.["fill-opacity"]);
21+
if (opacity !== undefined) {
22+
path.setAttribute("opacity", opacity.toString() ?? "1");
23+
}
24+
25+
// const fillTranslate = getResolvedValue(feature.paint?.["fill-translate"]);
26+
// if (fillTranslate) {
27+
// path.setAttribute(
28+
// "transform",
29+
// `translate(${fillTranslate.x} ${fillTranslate.y})`,
30+
// );
31+
// }
32+
33+
// TODO: move to parent styles
34+
path.setAttribute("stroke", "none");
35+
36+
element.appendChild(path);
37+
}
38+
39+
return element;
40+
};

src/renderer/layers/line.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { PreparedLayer } from "../../types.js";
2+
import { getResolvedValue, getSvgPathD } from "./common.js";
3+
4+
export const render = (layer: Extract<PreparedLayer, { type: "line" }>) => {
5+
const element = document.createElementNS("http://www.w3.org/2000/svg", "g");
6+
7+
for (const feature of layer.features) {
8+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
9+
10+
path.setAttribute("data-layername", layer.name);
11+
12+
const d = getSvgPathD(feature.geometry);
13+
path.setAttribute("d", d);
14+
15+
path.setAttribute(
16+
"stroke",
17+
getResolvedValue(feature.paint?.["line-color"]) ?? "black",
18+
);
19+
20+
path.setAttribute(
21+
"stroke-width",
22+
getResolvedValue(feature.paint?.["line-width"])?.toString() ?? "1",
23+
);
24+
25+
const opacity = getResolvedValue(feature.paint?.["line-opacity"]);
26+
if (opacity !== undefined) {
27+
path.setAttribute("opacity", opacity.toString() ?? "1");
28+
}
29+
30+
// TODO: move to parent styles
31+
path.setAttribute("fill", "none");
32+
33+
element.appendChild(path);
34+
}
35+
36+
return element;
37+
};

src/renderer/tile.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { TILE_EXTENT } from "../constants.js";
2+
import type { PreparedTile, Source, Style } from "../types.js";
3+
import * as fill from "./layers/fill.js";
4+
import * as line from "./layers/line.js";
5+
import type { RenderedTile } from "./types.js";
6+
7+
export const render = async ({
8+
tile: { x, y, z },
9+
cache,
10+
source,
11+
style,
12+
}: {
13+
tile: { x: number; y: number; z: number };
14+
source: Source;
15+
style: Style;
16+
cache: Map<string, RenderedTile | null>;
17+
}): Promise<RenderedTile | null> => {
18+
const cached = cache.get(`${x}-${y}-${z}`);
19+
if (cached !== undefined) {
20+
return cached;
21+
}
22+
const preparedTile = await style.prepare({ source, tile: { x, y, z } });
23+
const renderedTile = {
24+
coordinates: { x, y, z },
25+
layerElements: renderTile(preparedTile),
26+
};
27+
cache.set(`${x}-${y}-${z}`, renderedTile);
28+
return renderedTile;
29+
};
30+
31+
const renderTile = (tile: PreparedTile): Record<string, SVGElement> => {
32+
const layerElements: Record<string, SVGElement> = {};
33+
34+
for (const layer of Object.values(tile.layers)) {
35+
if (layer.type === "raster") {
36+
const image = document.createElementNS(
37+
"http://www.w3.org/2000/svg",
38+
"image",
39+
);
40+
image.setAttribute("x", "-1");
41+
image.setAttribute("y", "-1");
42+
image.setAttribute("width", (TILE_EXTENT + 2).toString());
43+
image.setAttribute("height", (TILE_EXTENT + 2).toString());
44+
image.setAttribute("href", layer.url);
45+
layerElements[layer.name] = image;
46+
} else if (layer.type === "fill") {
47+
layerElements[layer.name] = fill.render(layer);
48+
} else if (layer.type === "line") {
49+
layerElements[layer.name] = line.render(layer);
50+
}
51+
}
52+
53+
return layerElements;
54+
};

src/renderer/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type TileCoordinates = {
2+
x: number;
3+
y: number;
4+
z: number;
5+
};
6+
7+
export type RenderedTile = {
8+
coordinates: TileCoordinates;
9+
layerElements: Record<string, SVGElement>;
10+
};

0 commit comments

Comments
 (0)