@carto/api-client 0.5.31 → 0.5.32-alpha.02a4470.133
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/CHANGELOG.md +3 -0
- package/build/api-client.cjs +527 -3
- package/build/api-client.cjs.map +1 -1
- package/build/api-client.d.cts +42 -5
- package/build/api-client.d.ts +42 -5
- package/build/api-client.js +520 -3
- package/build/api-client.js.map +1 -1
- package/package.json +2 -2
- package/src/fetch-map/layer-map.ts +48 -0
- package/src/fetch-map/parse-map.ts +133 -10
- package/src/fetch-map/pattern-atlas.ts +461 -0
- package/src/fetch-map/patterns/checker-large.png +0 -0
- package/src/fetch-map/patterns/checker-large.svg +1 -0
- package/src/fetch-map/patterns/checker-medium.png +0 -0
- package/src/fetch-map/patterns/checker-medium.svg +1 -0
- package/src/fetch-map/patterns/checker-small.png +0 -0
- package/src/fetch-map/patterns/checker-small.svg +1 -0
- package/src/fetch-map/patterns/cross-hatch-large.png +0 -0
- package/src/fetch-map/patterns/cross-hatch-large.svg +1 -0
- package/src/fetch-map/patterns/cross-hatch-medium.png +0 -0
- package/src/fetch-map/patterns/cross-hatch-medium.svg +1 -0
- package/src/fetch-map/patterns/cross-hatch-small.png +0 -0
- package/src/fetch-map/patterns/cross-hatch-small.svg +1 -0
- package/src/fetch-map/patterns/diag-left-large.png +0 -0
- package/src/fetch-map/patterns/diag-left-large.svg +1 -0
- package/src/fetch-map/patterns/diag-left-medium.png +0 -0
- package/src/fetch-map/patterns/diag-left-medium.svg +1 -0
- package/src/fetch-map/patterns/diag-left-small.png +0 -0
- package/src/fetch-map/patterns/diag-left-small.svg +1 -0
- package/src/fetch-map/patterns/diag-right-large.png +0 -0
- package/src/fetch-map/patterns/diag-right-large.svg +1 -0
- package/src/fetch-map/patterns/diag-right-medium.png +0 -0
- package/src/fetch-map/patterns/diag-right-medium.svg +1 -0
- package/src/fetch-map/patterns/diag-right-small.png +0 -0
- package/src/fetch-map/patterns/diag-right-small.svg +1 -0
- package/src/fetch-map/patterns/dots-large.png +0 -0
- package/src/fetch-map/patterns/dots-large.svg +1 -0
- package/src/fetch-map/patterns/dots-medium.png +0 -0
- package/src/fetch-map/patterns/dots-medium.svg +1 -0
- package/src/fetch-map/patterns/dots-small.png +0 -0
- package/src/fetch-map/patterns/dots-small.svg +1 -0
- package/src/fetch-map/patterns/hlines-large.png +0 -0
- package/src/fetch-map/patterns/hlines-large.svg +1 -0
- package/src/fetch-map/patterns/hlines-medium.png +0 -0
- package/src/fetch-map/patterns/hlines-medium.svg +1 -0
- package/src/fetch-map/patterns/hlines-small.png +0 -0
- package/src/fetch-map/patterns/hlines-small.svg +1 -0
- package/src/fetch-map/patterns/solid.png +0 -0
- package/src/fetch-map/patterns/solid.svg +1 -0
- package/src/fetch-map/patterns/vlines-large.png +0 -0
- package/src/fetch-map/patterns/vlines-large.svg +1 -0
- package/src/fetch-map/patterns/vlines-medium.png +0 -0
- package/src/fetch-map/patterns/vlines-medium.svg +1 -0
- package/src/fetch-map/patterns/vlines-small.png +0 -0
- package/src/fetch-map/patterns/vlines-small.svg +1 -0
- package/src/fetch-map/types.ts +44 -0
- package/src/global.d.ts +10 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
// Fill-pattern atlas — internal to carto-api-client.
|
|
2
|
+
//
|
|
3
|
+
// The patterns live as individual, developer-editable tiles (src/fetch-map/patterns/):
|
|
4
|
+
// Design's original 64px raster masks (*.png) plus vector twins (*.svg), both inlined
|
|
5
|
+
// by tsup's `dataurl` loader, with procedural canvas painters as a third, asset-free
|
|
6
|
+
// source — selectable via a debug knob while the final asset strategy is evaluated.
|
|
7
|
+
// Tiles are composited into a sprite sheet on a canvas the first time a pattern is
|
|
8
|
+
// needed. Each atlas cell is filled with side-by-side copies of its tile (raster art
|
|
9
|
+
// never resampled), and is surrounded by a gutter holding the tile's own wrapped
|
|
10
|
+
// content — so linear sampling stays seamless at repeat boundaries and never bleeds a
|
|
11
|
+
// neighboring cell.
|
|
12
|
+
//
|
|
13
|
+
// parse-map sets `result.fillPatternAtlas = getPatternAtlas()`. deck.gl's
|
|
14
|
+
// `fillPatternAtlas` prop is async — but the Promise must resolve to a decoded image,
|
|
15
|
+
// never a data-URL string: deck URL-loads string prop values, while a promise-resolved
|
|
16
|
+
// string goes straight to texture creation, where luma.gl rejects it.
|
|
17
|
+
//
|
|
18
|
+
// Debug knobs (set in the browser console, then reload the map):
|
|
19
|
+
// globalThis.__CARTO_PATTERN_CELL_SIZE__ = 64|128|256 // atlas cell px, default 128
|
|
20
|
+
// globalThis.__CARTO_PATTERN_TEXTURE_PARAMS__ = {magFilter: 'nearest', ...}
|
|
21
|
+
// // merged into the atlas texture's sampler via deck's `textureParameters`;
|
|
22
|
+
// // unset -> deck defaults (linear).
|
|
23
|
+
// globalThis.__CARTO_PATTERN_ASSET_SOURCE__ = 'png' | 'svg' | 'procedural'
|
|
24
|
+
// // 'png' (default): Design's original 64px raster masks, tiled at native
|
|
25
|
+
// // resolution. 'svg': in-repo vector tiles rasterized at atlas-build time
|
|
26
|
+
// // (2x texel density per repeat). 'procedural': canvas painters, no assets.
|
|
27
|
+
|
|
28
|
+
import hlinesLarge from './patterns/hlines-large.png';
|
|
29
|
+
import hlinesMedium from './patterns/hlines-medium.png';
|
|
30
|
+
import hlinesSmall from './patterns/hlines-small.png';
|
|
31
|
+
import vlinesLarge from './patterns/vlines-large.png';
|
|
32
|
+
import vlinesMedium from './patterns/vlines-medium.png';
|
|
33
|
+
import vlinesSmall from './patterns/vlines-small.png';
|
|
34
|
+
import diagLeftLarge from './patterns/diag-left-large.png';
|
|
35
|
+
import diagLeftMedium from './patterns/diag-left-medium.png';
|
|
36
|
+
import diagLeftSmall from './patterns/diag-left-small.png';
|
|
37
|
+
import diagRightLarge from './patterns/diag-right-large.png';
|
|
38
|
+
import diagRightMedium from './patterns/diag-right-medium.png';
|
|
39
|
+
import diagRightSmall from './patterns/diag-right-small.png';
|
|
40
|
+
import crossHatchLarge from './patterns/cross-hatch-large.png';
|
|
41
|
+
import crossHatchMedium from './patterns/cross-hatch-medium.png';
|
|
42
|
+
import crossHatchSmall from './patterns/cross-hatch-small.png';
|
|
43
|
+
import dotsLarge from './patterns/dots-large.png';
|
|
44
|
+
import dotsMedium from './patterns/dots-medium.png';
|
|
45
|
+
import dotsSmall from './patterns/dots-small.png';
|
|
46
|
+
import checkerLarge from './patterns/checker-large.png';
|
|
47
|
+
import checkerMedium from './patterns/checker-medium.png';
|
|
48
|
+
import checkerSmall from './patterns/checker-small.png';
|
|
49
|
+
import solid from './patterns/solid.png';
|
|
50
|
+
|
|
51
|
+
import hlinesLargeSvg from './patterns/hlines-large.svg';
|
|
52
|
+
import hlinesMediumSvg from './patterns/hlines-medium.svg';
|
|
53
|
+
import hlinesSmallSvg from './patterns/hlines-small.svg';
|
|
54
|
+
import vlinesLargeSvg from './patterns/vlines-large.svg';
|
|
55
|
+
import vlinesMediumSvg from './patterns/vlines-medium.svg';
|
|
56
|
+
import vlinesSmallSvg from './patterns/vlines-small.svg';
|
|
57
|
+
import diagLeftLargeSvg from './patterns/diag-left-large.svg';
|
|
58
|
+
import diagLeftMediumSvg from './patterns/diag-left-medium.svg';
|
|
59
|
+
import diagLeftSmallSvg from './patterns/diag-left-small.svg';
|
|
60
|
+
import diagRightLargeSvg from './patterns/diag-right-large.svg';
|
|
61
|
+
import diagRightMediumSvg from './patterns/diag-right-medium.svg';
|
|
62
|
+
import diagRightSmallSvg from './patterns/diag-right-small.svg';
|
|
63
|
+
import crossHatchLargeSvg from './patterns/cross-hatch-large.svg';
|
|
64
|
+
import crossHatchMediumSvg from './patterns/cross-hatch-medium.svg';
|
|
65
|
+
import crossHatchSmallSvg from './patterns/cross-hatch-small.svg';
|
|
66
|
+
import dotsLargeSvg from './patterns/dots-large.svg';
|
|
67
|
+
import dotsMediumSvg from './patterns/dots-medium.svg';
|
|
68
|
+
import dotsSmallSvg from './patterns/dots-small.svg';
|
|
69
|
+
import checkerLargeSvg from './patterns/checker-large.svg';
|
|
70
|
+
import checkerMediumSvg from './patterns/checker-medium.svg';
|
|
71
|
+
import checkerSmallSvg from './patterns/checker-small.svg';
|
|
72
|
+
import solidSvg from './patterns/solid.svg';
|
|
73
|
+
|
|
74
|
+
const DEFAULT_CELL_SIZE = 128;
|
|
75
|
+
// px of the original raster masks; also the design grid the on-screen size is
|
|
76
|
+
// defined against — one tile spans 64 design units whatever the asset source.
|
|
77
|
+
const SOURCE_TILE_SIZE = 64;
|
|
78
|
+
// Vector sources (svg/procedural) rasterize each repeat at up to this many px,
|
|
79
|
+
// doubling texel density per repeat vs the 64px masks.
|
|
80
|
+
const VECTOR_RENDER_SIZE = 128;
|
|
81
|
+
|
|
82
|
+
export type PatternAssetSource = 'png' | 'svg' | 'procedural';
|
|
83
|
+
|
|
84
|
+
type PatternDebugGlobals = {
|
|
85
|
+
__CARTO_PATTERN_CELL_SIZE__?: number;
|
|
86
|
+
__CARTO_PATTERN_TEXTURE_PARAMS__?: Record<string, unknown>;
|
|
87
|
+
__CARTO_PATTERN_ASSET_SOURCE__?: PatternAssetSource;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type PatternAtlasFrame = {
|
|
91
|
+
x: number;
|
|
92
|
+
y: number;
|
|
93
|
+
width: number;
|
|
94
|
+
height: number;
|
|
95
|
+
mask: boolean;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Atlas grid: rows follow PATTERN_ROWS, columns are density sparse->dense
|
|
99
|
+
// [large, medium, small]; last row holds `none` (transparent) and `solid`.
|
|
100
|
+
const PATTERN_ROWS = [
|
|
101
|
+
'hlines',
|
|
102
|
+
'vlines',
|
|
103
|
+
'diag-left',
|
|
104
|
+
'diag-right',
|
|
105
|
+
'cross-hatch',
|
|
106
|
+
'dots',
|
|
107
|
+
'checker',
|
|
108
|
+
] as const;
|
|
109
|
+
const DENSITY_COLUMNS = ['large', 'medium', 'small'] as const;
|
|
110
|
+
|
|
111
|
+
// atlas key -> inlined data URL of its editable source tile
|
|
112
|
+
const CELL_URLS: Record<string, string> = {
|
|
113
|
+
'hlines-large': hlinesLarge,
|
|
114
|
+
'hlines-medium': hlinesMedium,
|
|
115
|
+
'hlines-small': hlinesSmall,
|
|
116
|
+
'vlines-large': vlinesLarge,
|
|
117
|
+
'vlines-medium': vlinesMedium,
|
|
118
|
+
'vlines-small': vlinesSmall,
|
|
119
|
+
'diag-left-large': diagLeftLarge,
|
|
120
|
+
'diag-left-medium': diagLeftMedium,
|
|
121
|
+
'diag-left-small': diagLeftSmall,
|
|
122
|
+
'diag-right-large': diagRightLarge,
|
|
123
|
+
'diag-right-medium': diagRightMedium,
|
|
124
|
+
'diag-right-small': diagRightSmall,
|
|
125
|
+
'cross-hatch-large': crossHatchLarge,
|
|
126
|
+
'cross-hatch-medium': crossHatchMedium,
|
|
127
|
+
'cross-hatch-small': crossHatchSmall,
|
|
128
|
+
'dots-large': dotsLarge,
|
|
129
|
+
'dots-medium': dotsMedium,
|
|
130
|
+
'dots-small': dotsSmall,
|
|
131
|
+
'checker-large': checkerLarge,
|
|
132
|
+
'checker-medium': checkerMedium,
|
|
133
|
+
'checker-small': checkerSmall,
|
|
134
|
+
solid,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// atlas key -> inlined data URL of its vector source tile
|
|
138
|
+
const CELL_SVG_URLS: Record<string, string> = {
|
|
139
|
+
'hlines-large': hlinesLargeSvg,
|
|
140
|
+
'hlines-medium': hlinesMediumSvg,
|
|
141
|
+
'hlines-small': hlinesSmallSvg,
|
|
142
|
+
'vlines-large': vlinesLargeSvg,
|
|
143
|
+
'vlines-medium': vlinesMediumSvg,
|
|
144
|
+
'vlines-small': vlinesSmallSvg,
|
|
145
|
+
'diag-left-large': diagLeftLargeSvg,
|
|
146
|
+
'diag-left-medium': diagLeftMediumSvg,
|
|
147
|
+
'diag-left-small': diagLeftSmallSvg,
|
|
148
|
+
'diag-right-large': diagRightLargeSvg,
|
|
149
|
+
'diag-right-medium': diagRightMediumSvg,
|
|
150
|
+
'diag-right-small': diagRightSmallSvg,
|
|
151
|
+
'cross-hatch-large': crossHatchLargeSvg,
|
|
152
|
+
'cross-hatch-medium': crossHatchMediumSvg,
|
|
153
|
+
'cross-hatch-small': crossHatchSmallSvg,
|
|
154
|
+
'dots-large': dotsLargeSvg,
|
|
155
|
+
'dots-medium': dotsMediumSvg,
|
|
156
|
+
'dots-small': dotsSmallSvg,
|
|
157
|
+
'checker-large': checkerLargeSvg,
|
|
158
|
+
'checker-medium': checkerMediumSvg,
|
|
159
|
+
'checker-small': checkerSmallSvg,
|
|
160
|
+
solid: solidSvg,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
function debugGlobals(): PatternDebugGlobals {
|
|
164
|
+
return globalThis as PatternDebugGlobals;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function getPatternCellSize(): number {
|
|
168
|
+
const cell = debugGlobals().__CARTO_PATTERN_CELL_SIZE__;
|
|
169
|
+
return typeof cell === 'number' && cell > 0 ? cell : DEFAULT_CELL_SIZE;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getPatternAssetSource(): PatternAssetSource {
|
|
173
|
+
const source = debugGlobals().__CARTO_PATTERN_ASSET_SOURCE__;
|
|
174
|
+
return source === 'svg' || source === 'procedural' ? source : 'png';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Sampler overrides for the atlas texture; undefined keeps deck's defaults (linear). */
|
|
178
|
+
export function getPatternTextureParameters():
|
|
179
|
+
| Record<string, unknown>
|
|
180
|
+
| undefined {
|
|
181
|
+
const params = debugGlobals().__CARTO_PATTERN_TEXTURE_PARAMS__;
|
|
182
|
+
return params && typeof params === 'object' ? params : undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Copies of the source tile laid side by side inside one atlas cell, per axis. Raster
|
|
186
|
+
// tiles draw at native resolution (no resampling); vector sources rasterize each
|
|
187
|
+
// repeat at up to VECTOR_RENDER_SIZE px. The UV wrap — where sampling seams live —
|
|
188
|
+
// happens 1/reps as often.
|
|
189
|
+
function getPatternRepeats(
|
|
190
|
+
cell: number,
|
|
191
|
+
source: PatternAssetSource = getPatternAssetSource()
|
|
192
|
+
): number {
|
|
193
|
+
const repeatSize = source === 'png' ? SOURCE_TILE_SIZE : VECTOR_RENDER_SIZE;
|
|
194
|
+
return Math.max(1, Math.floor(cell / repeatSize));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// deck's fill-pattern shader sizes the on-screen repeat proportionally to the mapping
|
|
198
|
+
// frame's texel size (scale = FILL_UV_SCALE * getFillPatternScale * frame.wh). This
|
|
199
|
+
// factor compensates for the cell size and the repeats inside it, normalized to the
|
|
200
|
+
// 64px design grid, so the pattern keeps a constant on-screen size across cell sizes
|
|
201
|
+
// and asset sources.
|
|
202
|
+
export function getPatternScaleAdjustment(
|
|
203
|
+
cell: number = getPatternCellSize()
|
|
204
|
+
): number {
|
|
205
|
+
return (SOURCE_TILE_SIZE * getPatternRepeats(cell)) / cell;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Gutter width around each cell; sized so a few mip levels stay bleed-free.
|
|
209
|
+
function getPatternCellPadding(cell: number): number {
|
|
210
|
+
return Math.max(2, Math.round(cell / 16));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function getPatternAtlasMapping(
|
|
214
|
+
cell: number = getPatternCellSize()
|
|
215
|
+
): Record<string, PatternAtlasFrame> {
|
|
216
|
+
const pad = getPatternCellPadding(cell);
|
|
217
|
+
const pitch = cell + 2 * pad;
|
|
218
|
+
const mapping: Record<string, PatternAtlasFrame> = {};
|
|
219
|
+
const frame = (col: number, row: number): PatternAtlasFrame => ({
|
|
220
|
+
x: pad + col * pitch,
|
|
221
|
+
y: pad + row * pitch,
|
|
222
|
+
width: cell,
|
|
223
|
+
height: cell,
|
|
224
|
+
mask: true,
|
|
225
|
+
});
|
|
226
|
+
PATTERN_ROWS.forEach((pattern, row) => {
|
|
227
|
+
DENSITY_COLUMNS.forEach((density, col) => {
|
|
228
|
+
mapping[`${pattern}-${density}`] = frame(col, row);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
// `none` must be a real transparent cell — a null pattern key resolves to atlas
|
|
232
|
+
// bounds [0,0,0,0] and samples the wrong (0,0) cell.
|
|
233
|
+
mapping.none = frame(0, PATTERN_ROWS.length);
|
|
234
|
+
mapping.solid = frame(1, PATTERN_ROWS.length);
|
|
235
|
+
return mapping;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export type AssembledAtlas = ImageBitmap | HTMLCanvasElement;
|
|
239
|
+
|
|
240
|
+
type AnyCanvas = HTMLCanvasElement | OffscreenCanvas;
|
|
241
|
+
|
|
242
|
+
function createCanvas(w: number, h: number): AnyCanvas {
|
|
243
|
+
if (typeof OffscreenCanvas !== 'undefined') return new OffscreenCanvas(w, h);
|
|
244
|
+
if (typeof document !== 'undefined') {
|
|
245
|
+
const c = document.createElement('canvas');
|
|
246
|
+
c.width = w;
|
|
247
|
+
c.height = h;
|
|
248
|
+
return c;
|
|
249
|
+
}
|
|
250
|
+
throw new Error(
|
|
251
|
+
'carto-api-client: no Canvas available to assemble the pattern atlas'
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function loadImage(dataUrl: string): Promise<CanvasImageSource> {
|
|
256
|
+
if (
|
|
257
|
+
typeof createImageBitmap !== 'undefined' &&
|
|
258
|
+
typeof fetch !== 'undefined'
|
|
259
|
+
) {
|
|
260
|
+
const blob = await (await fetch(dataUrl)).blob();
|
|
261
|
+
return createImageBitmap(blob);
|
|
262
|
+
}
|
|
263
|
+
return new Promise((resolve, reject) => {
|
|
264
|
+
const img = new Image();
|
|
265
|
+
img.onload = () => resolve(img);
|
|
266
|
+
img.onerror = reject;
|
|
267
|
+
img.src = dataUrl;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// SVG images always go through an Image element: createImageBitmap on SVG blobs is
|
|
272
|
+
// inconsistent across engines, and drawImage from an SVG image rasterizes from the
|
|
273
|
+
// vector at the destination size in modern browsers.
|
|
274
|
+
function loadSvgImage(dataUrl: string): Promise<CanvasImageSource> {
|
|
275
|
+
return new Promise((resolve, reject) => {
|
|
276
|
+
const img = new Image();
|
|
277
|
+
img.onload = () => resolve(img);
|
|
278
|
+
img.onerror = reject;
|
|
279
|
+
img.src = dataUrl;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Procedural painters — the same measured design-grid geometry as the assets, drawn
|
|
284
|
+
// in 64-unit design coordinates onto a pre-scaled context (canvas AA does the rest).
|
|
285
|
+
const DENSITY_STEP: Record<string, number> = {small: 1, medium: 2, large: 4};
|
|
286
|
+
|
|
287
|
+
function paintDesignTile(
|
|
288
|
+
ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D,
|
|
289
|
+
key: string
|
|
290
|
+
): void {
|
|
291
|
+
const [, name, density] =
|
|
292
|
+
key.match(/^(.*?)(?:-(small|medium|large))?$/) ?? [];
|
|
293
|
+
const step = DENSITY_STEP[density ?? ''] ?? 1;
|
|
294
|
+
const linePeriod = 4 * step;
|
|
295
|
+
const diagPeriod = 16 * step;
|
|
296
|
+
const square = 2 * step;
|
|
297
|
+
ctx.fillStyle = '#000';
|
|
298
|
+
ctx.strokeStyle = '#000';
|
|
299
|
+
const diag = (dir: 1 | -1) => {
|
|
300
|
+
// dir 1: "\" lines (x - y = k*p); dir -1: "/" lines (x + y = k*p)
|
|
301
|
+
ctx.lineWidth = Math.SQRT2; // 2px horizontal width at 45 degrees
|
|
302
|
+
ctx.beginPath();
|
|
303
|
+
for (
|
|
304
|
+
let k = -Math.ceil(64 / diagPeriod);
|
|
305
|
+
k <= 2 * Math.ceil(64 / diagPeriod);
|
|
306
|
+
k++
|
|
307
|
+
) {
|
|
308
|
+
ctx.moveTo(k * diagPeriod - 8 * dir, -8);
|
|
309
|
+
ctx.lineTo(k * diagPeriod + 72 * dir, 72);
|
|
310
|
+
}
|
|
311
|
+
ctx.stroke();
|
|
312
|
+
};
|
|
313
|
+
switch (name) {
|
|
314
|
+
case 'solid':
|
|
315
|
+
ctx.fillRect(0, 0, 64, 64);
|
|
316
|
+
break;
|
|
317
|
+
case 'hlines':
|
|
318
|
+
for (let y = 0; y < 64; y += linePeriod) ctx.fillRect(0, y, 64, 2);
|
|
319
|
+
break;
|
|
320
|
+
case 'vlines':
|
|
321
|
+
for (let x = 0; x < 64; x += linePeriod) ctx.fillRect(x, 0, 2, 64);
|
|
322
|
+
break;
|
|
323
|
+
case 'dots':
|
|
324
|
+
for (let y = 0; y < 64; y += linePeriod)
|
|
325
|
+
for (let x = 0; x < 64; x += linePeriod) ctx.fillRect(x, y, 2, 2);
|
|
326
|
+
break;
|
|
327
|
+
case 'checker':
|
|
328
|
+
for (let j = 0; j * square < 64; j++)
|
|
329
|
+
for (let i = 0; i * square < 64; i++)
|
|
330
|
+
if ((i + j) % 2 === 0)
|
|
331
|
+
ctx.fillRect(i * square, j * square, square, square);
|
|
332
|
+
break;
|
|
333
|
+
case 'diag-left':
|
|
334
|
+
diag(1);
|
|
335
|
+
break;
|
|
336
|
+
case 'diag-right':
|
|
337
|
+
diag(-1);
|
|
338
|
+
break;
|
|
339
|
+
case 'cross-hatch':
|
|
340
|
+
diag(1);
|
|
341
|
+
diag(-1);
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function paintTileCanvas(key: string, size: number): CanvasImageSource {
|
|
347
|
+
const canvas = createCanvas(size, size);
|
|
348
|
+
const ctx = canvas.getContext('2d');
|
|
349
|
+
if (!ctx)
|
|
350
|
+
throw new Error(
|
|
351
|
+
'carto-api-client: 2D context unavailable for pattern tile'
|
|
352
|
+
);
|
|
353
|
+
ctx.scale(size / 64, size / 64);
|
|
354
|
+
paintDesignTile(ctx, key);
|
|
355
|
+
return canvas;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
type TileImages = Record<string, CanvasImageSource>;
|
|
359
|
+
|
|
360
|
+
// Shared compositor: lay out the per-key tile images on the atlas grid. `none` is
|
|
361
|
+
// left transparent (no tile). Every other cell is filled with reps x reps copies of
|
|
362
|
+
// its tile, with one extra ring clipped into the gutter, so the padding holds the
|
|
363
|
+
// tile's own wrapped content.
|
|
364
|
+
async function composeAtlas(
|
|
365
|
+
cell: number,
|
|
366
|
+
reps: number,
|
|
367
|
+
images: TileImages
|
|
368
|
+
): Promise<AssembledAtlas> {
|
|
369
|
+
const mapping = getPatternAtlasMapping(cell);
|
|
370
|
+
const pad = getPatternCellPadding(cell);
|
|
371
|
+
const pitch = cell + 2 * pad;
|
|
372
|
+
const step = cell / reps;
|
|
373
|
+
const canvas = createCanvas(pitch * 3, pitch * (PATTERN_ROWS.length + 1));
|
|
374
|
+
const ctx = canvas.getContext('2d');
|
|
375
|
+
if (!ctx)
|
|
376
|
+
throw new Error(
|
|
377
|
+
'carto-api-client: 2D context unavailable for pattern atlas'
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
for (const [key, frame] of Object.entries(mapping)) {
|
|
381
|
+
const img = images[key];
|
|
382
|
+
if (!img) continue;
|
|
383
|
+
ctx.save();
|
|
384
|
+
ctx.beginPath();
|
|
385
|
+
ctx.rect(frame.x - pad, frame.y - pad, cell + 2 * pad, cell + 2 * pad);
|
|
386
|
+
ctx.clip();
|
|
387
|
+
for (let i = -1; i <= reps; i++) {
|
|
388
|
+
for (let j = -1; j <= reps; j++) {
|
|
389
|
+
ctx.drawImage(img, frame.x + i * step, frame.y + j * step, step, step);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
ctx.restore();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (typeof createImageBitmap !== 'undefined')
|
|
396
|
+
return createImageBitmap(canvas);
|
|
397
|
+
return canvas as HTMLCanvasElement;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// One builder per asset source, all funneling into composeAtlas.
|
|
401
|
+
|
|
402
|
+
// Design's original 64px raster masks, tiled at native resolution (never resampled).
|
|
403
|
+
async function buildPngAtlas(cell: number): Promise<AssembledAtlas> {
|
|
404
|
+
const images: TileImages = {};
|
|
405
|
+
await Promise.all(
|
|
406
|
+
Object.entries(CELL_URLS).map(async ([key, url]) => {
|
|
407
|
+
images[key] = await loadImage(url);
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
return composeAtlas(cell, getPatternRepeats(cell, 'png'), images);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// In-repo vector tiles, rasterized from the vector at the repeat size.
|
|
414
|
+
async function buildSvgAtlas(cell: number): Promise<AssembledAtlas> {
|
|
415
|
+
const images: TileImages = {};
|
|
416
|
+
await Promise.all(
|
|
417
|
+
Object.entries(CELL_SVG_URLS).map(async ([key, url]) => {
|
|
418
|
+
images[key] = await loadSvgImage(url);
|
|
419
|
+
})
|
|
420
|
+
);
|
|
421
|
+
return composeAtlas(cell, getPatternRepeats(cell, 'svg'), images);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Asset-free canvas painters drawing the measured design-grid geometry.
|
|
425
|
+
async function buildProceduralAtlas(cell: number): Promise<AssembledAtlas> {
|
|
426
|
+
const reps = getPatternRepeats(cell, 'procedural');
|
|
427
|
+
const step = cell / reps;
|
|
428
|
+
const images: TileImages = {};
|
|
429
|
+
for (const key of Object.keys(CELL_URLS)) {
|
|
430
|
+
images[key] = paintTileCanvas(key, step);
|
|
431
|
+
}
|
|
432
|
+
return composeAtlas(cell, reps, images);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const ATLAS_BUILDERS: Record<
|
|
436
|
+
PatternAssetSource,
|
|
437
|
+
(cell: number) => Promise<AssembledAtlas>
|
|
438
|
+
> = {
|
|
439
|
+
png: buildPngAtlas,
|
|
440
|
+
svg: buildSvgAtlas,
|
|
441
|
+
procedural: buildProceduralAtlas,
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const atlasPromises = new Map<string, Promise<AssembledAtlas>>();
|
|
445
|
+
|
|
446
|
+
/** Assemble the sprite sheet for the active asset source and cell size. Memoized. */
|
|
447
|
+
export function getPatternAtlas(
|
|
448
|
+
cell: number = getPatternCellSize()
|
|
449
|
+
): Promise<AssembledAtlas> {
|
|
450
|
+
const source = getPatternAssetSource();
|
|
451
|
+
const memoKey = `${source}:${cell}`;
|
|
452
|
+
let promise = atlasPromises.get(memoKey);
|
|
453
|
+
if (!promise) {
|
|
454
|
+
promise = ATLAS_BUILDERS[source](cell);
|
|
455
|
+
// Keep a rejection observed even if no consumer attaches a handler (e.g. Node,
|
|
456
|
+
// where there is no canvas) — the returned promise still rejects for real callers.
|
|
457
|
+
promise.catch(() => {});
|
|
458
|
+
atlasPromises.set(memoKey, promise);
|
|
459
|
+
}
|
|
460
|
+
return promise;
|
|
461
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="16" height="16" patternUnits="userSpaceOnUse"><rect width="8" height="8"/><rect x="8" y="8" width="8" height="8"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="8" height="8" patternUnits="userSpaceOnUse"><rect width="4" height="4"/><rect x="4" y="4" width="4" height="4"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="4" height="4" patternUnits="userSpaceOnUse"><rect width="2" height="2"/><rect x="2" y="2" width="2" height="2"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="a" width="45.2548" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)"><rect width="1.4142" height="64"/></pattern><pattern id="b" width="45.2548" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="1.4142" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#a)"/><rect width="64" height="64" fill="url(#b)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="a" width="22.6274" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)"><rect width="1.4142" height="64"/></pattern><pattern id="b" width="22.6274" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="1.4142" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#a)"/><rect width="64" height="64" fill="url(#b)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="a" width="11.3137" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)"><rect width="1.4142" height="64"/></pattern><pattern id="b" width="11.3137" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="1.4142" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#a)"/><rect width="64" height="64" fill="url(#b)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="45.2548" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)"><rect width="1.4142" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="22.6274" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)"><rect width="1.4142" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="11.3137" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)"><rect width="1.4142" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="45.2548" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="1.4142" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="22.6274" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="1.4142" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="11.3137" height="64" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="1.4142" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="16" height="16" patternUnits="userSpaceOnUse"><rect width="2" height="2"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="8" height="8" patternUnits="userSpaceOnUse"><rect width="2" height="2"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="4" height="4" patternUnits="userSpaceOnUse"><rect width="2" height="2"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="64" height="16" patternUnits="userSpaceOnUse"><rect width="64" height="2"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="64" height="8" patternUnits="userSpaceOnUse"><rect width="64" height="2"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="64" height="4" patternUnits="userSpaceOnUse"><rect width="64" height="2"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs></defs><rect width="64" height="64"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="16" height="64" patternUnits="userSpaceOnUse"><rect width="2" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="8" height="64" patternUnits="userSpaceOnUse"><rect width="2" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><pattern id="p" width="4" height="64" patternUnits="userSpaceOnUse"><rect width="2" height="64"/></pattern></defs><rect width="64" height="64" fill="url(#p)"/></svg>
|