@reearth/ezu 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -103,12 +103,17 @@ bytes a browser may download, so for now the choice is explicit.
103
103
 
104
104
  ```ts
105
105
  function simdEnabled(): boolean;
106
+ // How many node ops the module registered, and their names.
107
+ function opCount(): number;
108
+ function opNames(): string[];
106
109
 
107
110
  class Renderer {
108
111
  constructor(styleJson: string);
109
112
 
110
113
  setStyle(styleJson: string): number; // → new node count
111
114
  readonly tileSize: number;
115
+ readonly renderTileSize: number; // what renders use: setTileSize's, or tileSize
116
+ readonly warnings: string[]; // what building the style's graph warned about
112
117
 
113
118
  // Bind tile bytes under a `sources.<name>` entry from the style.
114
119
  // The renderer dispatches on the source's declared `type`:
@@ -136,16 +141,25 @@ class Renderer {
136
141
  indexUrl?: string; // a sprite index that is a URL, not inline
137
142
  }>;
138
143
 
139
- // Cap the resident glyph bytes per fontstack. 0xFFFFFFFF lifts the cap
140
- // (and reads back as `Infinity` from `memoryUsage().glyphBudget`).
144
+ // Cap the resident glyph bytes per fontstack. clearGlyphBudget() lifts
145
+ // the cap, as does 0xFFFFFFFF (it reads back as `Infinity` from
146
+ // `memoryUsage().glyphBudget`).
141
147
  setGlyphBudget(bytes: number): void;
148
+ clearGlyphBudget(): void;
149
+
150
+ // Let the render cache hold `bytes` of pixels instead of its default
151
+ // (8 MiB); clearCacheBudget() goes back. Kept through setStyle.
152
+ setCacheBudget(bytes: number): void;
153
+ clearCacheBudget(): void;
142
154
 
143
155
  // Single unified render. `opts.format` picks the encoder; `tileSize`
144
- // / `pad` override the canvas for hi-DPI / preview.
156
+ // / `pad` override the canvas (in style pixels); `pixelRatio` renders
157
+ // the same tile at a higher density for hi-DPI screens.
145
158
  renderTile(z: number, x: number, y: number, opts?: {
146
159
  format?: "png" | "webp" | "rgba"; // default "png"
147
160
  tileSize?: number;
148
161
  pad?: number;
162
+ pixelRatio?: number; // 1 to 4, default 1
149
163
  // Render-time overrides for the style's declared `params`, validated
150
164
  // against the declarations (type, min/max) — an unknown name or an
151
165
  // out-of-range value throws. Names left out keep their default. One
@@ -154,10 +168,141 @@ class Renderer {
154
168
  png?: { compression?: "fast" | "default" | "best" };
155
169
  }): Uint8Array;
156
170
 
171
+ // Decode a tile-scoped payload (mvt, pmtiles, dem, raster, remote
172
+ // geojson) once, to sit in the set of every render that reads it.
173
+ loadSource(name: string, bytes: Uint8Array): SourceData;
174
+
175
+ // renderTile, from a set of loaded data instead of what is bound. Does
176
+ // not trim glyphs to the budget; call trimGlyphs() after a batch.
177
+ renderTileWith(z: number, x: number, y: number, set: SourceSet,
178
+ opts?: object /* renderTile's opts */): Uint8Array;
179
+
180
+ // renderTileWith, also handing back what the named features nodes held:
181
+ // one { node, extent, geojson } per node, geojson being a GeoJSON
182
+ // FeatureCollection as text in tile units (0..extent). Pass
183
+ // { nodes, coords: "lonlat" | "px" } for WGS84 or the tile's pixels.
184
+ renderTileWithFeatures(z: number, x: number, y: number, set: SourceSet,
185
+ nodes: string[] | { nodes: string[];
186
+ coords?: "tile" | "lonlat" | "px" },
187
+ opts?: object): TileWithFeatures;
188
+
189
+ // neededCodepoints / neededGlyphRanges for the features in a set.
190
+ neededCodepointsIn(set: SourceSet): Record<string, number[]>;
191
+ neededGlyphRangesIn(set: SourceSet): Record<string, number[]>;
192
+
193
+ // Bring the glyph banks back under setGlyphBudget's budget.
194
+ trimGlyphs(): void;
195
+
196
+ // Render every tile at `size` px instead of the style's tile-size,
197
+ // without editing the style, until `clearTileSize()` or `setStyle`.
198
+ setTileSize(size: number): void;
199
+ clearTileSize(): void;
200
+
201
+ // Print frames — see "A page at a print scale" below.
202
+ computeFrame(req: FrameRequest): Frame;
203
+ renderFrame(
204
+ frame: Frame,
205
+ sources: ((tile: { z: number; x: number; y: number },
206
+ binder: FrameBinder) => SourceSet | Promise<SourceSet>)
207
+ | null | undefined,
208
+ opts?: { pad?: number; params?: Record<string, number | boolean | string> },
209
+ ): Promise<Uint8Array>; // straight RGBA, width × height
210
+ // renderFrame, also gathering features nodes over the page: each node's
211
+ // page as the GeoJSON text `ezu frame --features` writes.
212
+ renderFrameWithFeatures(
213
+ frame: Frame, sources: /* as renderFrame */ any,
214
+ request: { nodes: { node: string; key?: string[] }[];
215
+ coords?: "lonlat" | "px" },
216
+ opts?: { pad?: number; params?: Record<string, number | boolean | string> },
217
+ ): Promise<{ image: Uint8Array; features: { node: string; geojson: string }[] }>;
218
+
219
+ free(): void;
220
+ }
221
+
222
+ // One decoded tile. Sets that hold it keep it alive.
223
+ class SourceData {
224
+ readonly source: string; // the sources.<name> it is for
157
225
  free(): void;
158
226
  }
227
+
228
+ // The tile data one render reads, each at its place in the 3×3.
229
+ class SourceSet {
230
+ constructor();
231
+ bind(data: SourceData,
232
+ opts?: { coord?: [number, number]; sourceZoom?: number }): void;
233
+ contains(source: string): boolean;
234
+ isEmpty(): boolean;
235
+ clone(): SourceSet; // shares the data
236
+ free(): void;
237
+ }
238
+
239
+ // Handed to renderFrame's sources: bindSource into the renderer (glyphs,
240
+ // fonts, sprites — not tile data, which goes in the set) for that tile
241
+ // only, until its sources returns or settles (then it throws StaleBinder).
242
+ class FrameBinder {
243
+ bindSource(name: string, bytes: Uint8Array,
244
+ opts?: { coord?: [number, number]; sourceZoom?: number }): void;
245
+ }
246
+
247
+ interface FrameRequest {
248
+ centerLon: number; centerLat: number; // degrees, WGS84
249
+ scale: number; // 1000 for 1:1000
250
+ dpi: number;
251
+ width: number; height: number; // output pixels
252
+ zoom?: number; // the CLI's --zoom
253
+ styleTileSize?: number; // default: the style's
254
+ }
255
+
256
+ interface Frame {
257
+ zoom: number; tileSize: number; // what the tiles render at
258
+ scale: number; // the denominator met
259
+ width: number; height: number;
260
+ left: number; top: number; // corner, in world pixels
261
+ shift: [number, number]; // rounding, within ±0.5 px
262
+ tilesX: [number, number]; // start inclusive, end exclusive
263
+ tilesY: [number, number];
264
+ }
265
+ ```
266
+
267
+ ## A page at a print scale
268
+
269
+ A printed page is asked for as "1:1000 at 200 dpi, 245 × 174 mm, centred
270
+ here", not as a zoom. `computeFrame` picks the zoom and tile size that
271
+ meet the scale and the tiles the page covers; `renderFrame` renders them,
272
+ calling you back for the `SourceSet` each one renders from, and crops them
273
+ into one RGBA page — the same page `ezu frame` writes, pixel for pixel:
274
+
275
+ ```js
276
+ const mm = (length, dpi) => Math.round(length / 25.4 * dpi);
277
+ const f = renderer.computeFrame({
278
+ centerLon: 139.70, centerLat: 35.68,
279
+ scale: 1000, dpi: 200,
280
+ width: mm(245, 200), height: mm(174, 200), // 1929 × 1370
281
+ });
282
+ const loaded = new Map(); // each fetched tile, decoded once
283
+ const rgba = await renderer.renderFrame(f, async ({ z, x, y }, binder) => {
284
+ const key = `${z}/${x}/${y}`;
285
+ if (!loaded.has(key)) {
286
+ loaded.set(key, renderer.loadSource("basemap", await fetchMvt(z, x, y)));
287
+ }
288
+ const set = new SourceSet();
289
+ set.bind(loaded.get(key)); // and the neighbours, at their coord
290
+ return set;
291
+ });
159
292
  ```
160
293
 
294
+ A page's tiles border each other, so a `SourceData` kept for the tiles
295
+ around it is decoded once however many of them read it. Until the promise
296
+ settles the renderer is the frame's: glyphs a set needs are bound through
297
+ the `binder` you are handed, which works only for its own tile and only
298
+ until that call returns or settles. Calls that would change the renderer —
299
+ `bindSource`, `clearSources`, `renderTile`, `trimGlyphs`, another
300
+ `renderFrame`, `setStyle`, `setTileSize`, `clearTileSize`,
301
+ `setGlyphBudget`, `clearGlyphBudget` — throw `FrameInProgress` meanwhile, so another request
302
+ cannot change the frame's tiles under it. `loadSource`, `renderTileWith`
303
+ and queries that only read, such as `sourceTile` and `neededCodepointsIn`,
304
+ still work.
305
+
161
306
  ## Output formats
162
307
 
163
308
  - **`format: "png"`** (default) returns PNG bytes — feed to `<img>`
@@ -195,6 +340,8 @@ discriminates the kind:
195
340
  | `RenderFailed` | A node `eval` failed (e.g. missing brush, downcast mismatch) |
196
341
  | `PngEncode` | PNG encoding failed |
197
342
  | `WebpEncode` | WebP encoding failed |
343
+ | `FrameInProgress` | A call would change a renderer that a `renderFrame` holds until its promise settles |
344
+ | `StaleBinder` | A frame's `binder` was used after its tile's `sources` returned or settled |
198
345
 
199
346
  ```js
200
347
  try {