@react-x11/components 0.4.0 → 0.5.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 +125 -4
- package/dist/charts/components.d.ts +3 -1
- package/dist/charts/components.d.ts.map +1 -1
- package/dist/charts/components.js +8 -4
- package/dist/charts/components.js.map +1 -1
- package/dist/charts/node.d.ts +13 -6
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +75 -24
- package/dist/charts/node.js.map +1 -1
- package/dist/charts/render.d.ts +4 -0
- package/dist/charts/render.d.ts.map +1 -1
- package/dist/charts/render.js +11 -6
- package/dist/charts/render.js.map +1 -1
- package/dist/code-editor/node.d.ts +47 -16
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +121 -51
- package/dist/code-editor/node.js.map +1 -1
- package/dist/flow/draw.d.ts +19 -1
- package/dist/flow/draw.d.ts.map +1 -1
- package/dist/flow/draw.js +64 -35
- package/dist/flow/draw.js.map +1 -1
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +24 -8
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/model.d.ts +4 -2
- package/dist/flow/model.d.ts.map +1 -1
- package/dist/flow/model.js +4 -2
- package/dist/flow/model.js.map +1 -1
- package/dist/flow/node.d.ts +40 -9
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +142 -53
- package/dist/flow/node.js.map +1 -1
- package/dist/flow/types.d.ts +46 -21
- package/dist/flow/types.d.ts.map +1 -1
- package/dist/formula/index.d.ts +4 -2
- package/dist/formula/index.d.ts.map +1 -1
- package/dist/formula/index.js.map +1 -1
- package/dist/formula/node.d.ts +5 -1
- package/dist/formula/node.d.ts.map +1 -1
- package/dist/formula/node.js +19 -3
- package/dist/formula/node.js.map +1 -1
- package/dist/html/controls.d.ts +4 -1
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +11 -5
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +18 -8
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/parse.d.ts +15 -10
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +41 -16
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +3 -0
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +3 -1
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/values.d.ts +14 -1
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +11 -8
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +1 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +12 -5
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +7 -4
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +22 -1
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +22 -8
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +97 -15
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +3 -0
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +2 -2
- package/dist/html/paint.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/maps/clip.d.ts +43 -0
- package/dist/maps/clip.d.ts.map +1 -0
- package/dist/maps/clip.js +171 -0
- package/dist/maps/clip.js.map +1 -0
- package/dist/maps/gzip.d.ts +15 -0
- package/dist/maps/gzip.d.ts.map +1 -0
- package/dist/maps/gzip.js +56 -0
- package/dist/maps/gzip.js.map +1 -0
- package/dist/maps/index.d.ts +55 -0
- package/dist/maps/index.d.ts.map +1 -0
- package/dist/maps/index.js +141 -0
- package/dist/maps/index.js.map +1 -0
- package/dist/maps/labels.d.ts +123 -0
- package/dist/maps/labels.d.ts.map +1 -0
- package/dist/maps/labels.js +323 -0
- package/dist/maps/labels.js.map +1 -0
- package/dist/maps/mvt.d.ts +244 -0
- package/dist/maps/mvt.d.ts.map +1 -0
- package/dist/maps/mvt.js +820 -0
- package/dist/maps/mvt.js.map +1 -0
- package/dist/maps/node.d.ts +278 -0
- package/dist/maps/node.d.ts.map +1 -0
- package/dist/maps/node.js +1459 -0
- package/dist/maps/node.js.map +1 -0
- package/dist/maps/overlay.d.ts +157 -0
- package/dist/maps/overlay.d.ts.map +1 -0
- package/dist/maps/overlay.js +472 -0
- package/dist/maps/overlay.js.map +1 -0
- package/dist/maps/paint.d.ts +226 -0
- package/dist/maps/paint.d.ts.map +1 -0
- package/dist/maps/paint.js +705 -0
- package/dist/maps/paint.js.map +1 -0
- package/dist/maps/proj.d.ts +281 -0
- package/dist/maps/proj.d.ts.map +1 -0
- package/dist/maps/proj.js +451 -0
- package/dist/maps/proj.js.map +1 -0
- package/dist/maps/sources.d.ts +256 -0
- package/dist/maps/sources.d.ts.map +1 -0
- package/dist/maps/sources.js +210 -0
- package/dist/maps/sources.js.map +1 -0
- package/dist/maps/style.d.ts +141 -0
- package/dist/maps/style.d.ts.map +1 -0
- package/dist/maps/style.js +147 -0
- package/dist/maps/style.js.map +1 -0
- package/dist/maps/styles.d.ts +82 -0
- package/dist/maps/styles.d.ts.map +1 -0
- package/dist/maps/styles.js +898 -0
- package/dist/maps/styles.js.map +1 -0
- package/dist/maps/tiles.d.ts +283 -0
- package/dist/maps/tiles.d.ts.map +1 -0
- package/dist/maps/tiles.js +469 -0
- package/dist/maps/tiles.js.map +1 -0
- package/dist/maps/types.d.ts +244 -0
- package/dist/maps/types.d.ts.map +1 -0
- package/dist/maps/types.js +2 -0
- package/dist/maps/types.js.map +1 -0
- package/dist/reorder/hx.d.ts +18 -0
- package/dist/reorder/hx.d.ts.map +1 -0
- package/dist/reorder/hx.js +10 -0
- package/dist/reorder/hx.js.map +1 -0
- package/dist/reorder/index.d.ts +314 -0
- package/dist/reorder/index.d.ts.map +1 -0
- package/dist/reorder/index.js +1539 -0
- package/dist/reorder/index.js.map +1 -0
- package/dist/reorder/model.d.ts +107 -0
- package/dist/reorder/model.d.ts.map +1 -0
- package/dist/reorder/model.js +162 -0
- package/dist/reorder/model.js.map +1 -0
- package/dist/richtext/node.d.ts +15 -5
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +40 -11
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +35 -13
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +83 -27
- package/dist/richtext/runs.js.map +1 -1
- package/dist/tabs/index.d.ts +17 -0
- package/dist/tabs/index.d.ts.map +1 -1
- package/dist/tabs/index.js +849 -186
- package/dist/tabs/index.js.map +1 -1
- package/dist/terminal/vt/fonts.d.ts +45 -15
- package/dist/terminal/vt/fonts.d.ts.map +1 -1
- package/dist/terminal/vt/fonts.js +89 -19
- package/dist/terminal/vt/fonts.js.map +1 -1
- package/dist/terminal/vt/node.d.ts +42 -0
- package/dist/terminal/vt/node.d.ts.map +1 -1
- package/dist/terminal/vt/node.js +95 -11
- package/dist/terminal/vt/node.js.map +1 -1
- package/dist/terminal/vt/renderer.d.ts.map +1 -1
- package/dist/terminal/vt/renderer.js +13 -3
- package/dist/terminal/vt/renderer.js.map +1 -1
- package/package.json +13 -3
- package/src/charts/components.ts +14 -7
- package/src/charts/node.ts +81 -26
- package/src/charts/render.ts +16 -6
- package/src/code-editor/node.ts +147 -53
- package/src/flow/draw.ts +104 -35
- package/src/flow/index.ts +33 -13
- package/src/flow/model.ts +4 -2
- package/src/flow/node.ts +158 -59
- package/src/flow/types.ts +46 -21
- package/src/formula/index.ts +4 -2
- package/src/formula/node.ts +24 -5
- package/src/html/controls.ts +4 -1
- package/src/html/css/cascade.ts +18 -7
- package/src/html/css/parse.ts +46 -21
- package/src/html/css/style.ts +6 -1
- package/src/html/css/values.ts +25 -9
- package/src/html/index.ts +1 -0
- package/src/html/layout/boxes.ts +19 -9
- package/src/html/layout/inline.ts +23 -1
- package/src/html/node.ts +110 -17
- package/src/html/paint.ts +17 -2
- package/src/index.ts +98 -0
- package/src/maps/clip.ts +184 -0
- package/src/maps/gzip.ts +66 -0
- package/src/maps/index.ts +316 -0
- package/src/maps/labels.ts +442 -0
- package/src/maps/mvt.ts +857 -0
- package/src/maps/node.ts +1819 -0
- package/src/maps/overlay.ts +639 -0
- package/src/maps/paint.ts +978 -0
- package/src/maps/proj.ts +641 -0
- package/src/maps/sources.ts +425 -0
- package/src/maps/style.ts +309 -0
- package/src/maps/styles.ts +1040 -0
- package/src/maps/tiles.ts +676 -0
- package/src/maps/types.ts +260 -0
- package/src/reorder/hx.ts +31 -0
- package/src/reorder/index.ts +2223 -0
- package/src/reorder/model.ts +232 -0
- package/src/richtext/node.ts +49 -14
- package/src/richtext/runs.ts +115 -30
- package/src/tabs/index.ts +1040 -204
- package/src/terminal/vt/fonts.ts +128 -36
- package/src/terminal/vt/node.ts +105 -12
- package/src/terminal/vt/renderer.ts +13 -3
package/src/maps/mvt.ts
ADDED
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
// Mapbox Vector Tiles, decoded.
|
|
2
|
+
//
|
|
3
|
+
// MVT 2.1 is the format. Not one format among several: it is what Mapbox,
|
|
4
|
+
// MapTiler, Protomaps, Esri, TomTom, Azure Maps and OpenStreetMap's own
|
|
5
|
+
// tile server all serve, so a decoder for it is a decoder for the whole
|
|
6
|
+
// open half of the industry. (`docs/prd-maps.md` surveys who serves what,
|
|
7
|
+
// and which providers are closed.)
|
|
8
|
+
//
|
|
9
|
+
// Written out rather than taken from `@mapbox/vector-tile` + `pbf`, and the
|
|
10
|
+
// reason is not weight — those two are small — but the hot loop. Both
|
|
11
|
+
// libraries hand back a fresh array-of-arrays of `{x, y}` objects per
|
|
12
|
+
// feature, which for a dense city tile is on the order of 100,000 object
|
|
13
|
+
// allocations that exist for one rasterization and are then garbage. The
|
|
14
|
+
// decoder here reads geometry into a **caller-owned buffer of typed
|
|
15
|
+
// arrays** ({@link readGeometry}), so a tile is rasterized with no
|
|
16
|
+
// allocation at all in the per-vertex path, and the convenience shape
|
|
17
|
+
// ({@link VectorTileFeature.geometry}) is a wrapper over the same code for
|
|
18
|
+
// the callers that want one — hit tests, a GeoJSON export.
|
|
19
|
+
//
|
|
20
|
+
// Everything else is a straight reading of the specification, and the parts
|
|
21
|
+
// that are easy to get subtly wrong carry the spec's own words:
|
|
22
|
+
//
|
|
23
|
+
// - a `CommandInteger` is `(id & 0x7) | (count << 3)`;
|
|
24
|
+
// - `MoveTo` is 1 and `LineTo` is 2, each taking two parameters, and
|
|
25
|
+
// `ClosePath` is 7 and takes none;
|
|
26
|
+
// - a `ParameterInteger` is zigzag: `(n >> 1) ^ -(n & 1)`;
|
|
27
|
+
// - coordinates are **deltas from a cursor**, which starts at (0, 0) and
|
|
28
|
+
// persists across commands *and across the parts of one feature*;
|
|
29
|
+
// - a polygon's exterior ring is the one with **positive** area by the
|
|
30
|
+
// surveyor's formula in tile coordinates, where y increases downward —
|
|
31
|
+
// so an exterior ring is clockwise on screen and an interior one is
|
|
32
|
+
// not;
|
|
33
|
+
// - a ring's last point is not repeated before `ClosePath`;
|
|
34
|
+
// - `extent` defaults to 4096, and geometry **may leave** `[0, extent]`,
|
|
35
|
+
// because producers carry a buffer past the edge so a road's join is
|
|
36
|
+
// right where two tiles meet.
|
|
37
|
+
|
|
38
|
+
/** What a feature's geometry is. The numbers are the specification's. */
|
|
39
|
+
export const enum GeomType {
|
|
40
|
+
Unknown = 0,
|
|
41
|
+
Point = 1,
|
|
42
|
+
LineString = 2,
|
|
43
|
+
Polygon = 3,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A tag value. MVT's seven value types collapse to these three in
|
|
47
|
+
* JavaScript; which of the four numeric ones it was is not information a
|
|
48
|
+
* style can use. */
|
|
49
|
+
export type FeatureValue = string | number | boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Decoded geometry, as flat typed arrays.
|
|
53
|
+
*
|
|
54
|
+
* `coords` is `[x0, y0, x1, y1, …]` in **tile-local integers** — the space
|
|
55
|
+
* `extent` divides, not pixels and not degrees. `starts` indexes it in
|
|
56
|
+
* *points*: part `i` covers `coords[2 * starts[i] .. 2 * starts[i + 1])`,
|
|
57
|
+
* and `starts` has `parts + 1` entries so the last part needs no special
|
|
58
|
+
* case.
|
|
59
|
+
*
|
|
60
|
+
* `areas[i]` is part `i`'s signed area by the surveyor's formula, and is
|
|
61
|
+
* meaningful for {@link GeomType.Polygon} only: positive is an exterior
|
|
62
|
+
* ring, negative an interior one. It is filled during the same pass that
|
|
63
|
+
* reads the vertices, because a second pass over 4,000 points to learn one
|
|
64
|
+
* sign is the sort of thing that turns a 6 ms tile into an 11 ms one.
|
|
65
|
+
*/
|
|
66
|
+
export interface GeometryData {
|
|
67
|
+
coords: Int32Array;
|
|
68
|
+
starts: Uint32Array;
|
|
69
|
+
areas: Float64Array;
|
|
70
|
+
parts: number;
|
|
71
|
+
points: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A growable {@link GeometryData} a caller keeps between features.
|
|
76
|
+
*
|
|
77
|
+
* The rasterizer allocates one of these per tile — or per element, since it
|
|
78
|
+
* is only scratch — and every feature reads into it. Capacity ratchets up
|
|
79
|
+
* to the largest feature seen and then stops moving, which for a tile means
|
|
80
|
+
* two or three grows in total rather than two per feature.
|
|
81
|
+
*/
|
|
82
|
+
export class GeometryBuffer implements GeometryData {
|
|
83
|
+
coords: Int32Array;
|
|
84
|
+
starts: Uint32Array;
|
|
85
|
+
areas: Float64Array;
|
|
86
|
+
parts = 0;
|
|
87
|
+
points = 0;
|
|
88
|
+
|
|
89
|
+
constructor(points = 1024, parts = 32) {
|
|
90
|
+
this.coords = new Int32Array(points * 2);
|
|
91
|
+
this.starts = new Uint32Array(parts + 1);
|
|
92
|
+
this.areas = new Float64Array(parts);
|
|
93
|
+
this.partBounds = new Float64Array(parts * 4);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Room for `points` vertices, keeping what is already written. */
|
|
97
|
+
reserveCoords(points: number): void {
|
|
98
|
+
if (this.coords.length >= points * 2) return;
|
|
99
|
+
let size = this.coords.length || 2;
|
|
100
|
+
while (size < points * 2) size *= 2;
|
|
101
|
+
const next = new Int32Array(size);
|
|
102
|
+
next.set(this.coords);
|
|
103
|
+
this.coords = next;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Per-part bounding boxes, four numbers each: `minX, minY, maxX, maxY`.
|
|
108
|
+
*
|
|
109
|
+
* The feature-wide box is not enough, and the case that proves it is the
|
|
110
|
+
* one that dominates a low-zoom frame: OSM's `land` layer at zoom 8 is a
|
|
111
|
+
* **single feature** whose geometry is thousands of separate rings, most
|
|
112
|
+
* of them a fraction of a pixel across. Culled as one feature it is never
|
|
113
|
+
* culled; culled per part, almost all of it goes.
|
|
114
|
+
*/
|
|
115
|
+
partBounds: Float64Array;
|
|
116
|
+
|
|
117
|
+
/** Room for `parts` parts, keeping what is already written. */
|
|
118
|
+
reserveParts(parts: number): void {
|
|
119
|
+
if (this.areas.length >= parts) return;
|
|
120
|
+
let size = this.areas.length || 1;
|
|
121
|
+
while (size < parts) size *= 2;
|
|
122
|
+
const starts = new Uint32Array(size + 1);
|
|
123
|
+
starts.set(this.starts);
|
|
124
|
+
this.starts = starts;
|
|
125
|
+
const areas = new Float64Array(size);
|
|
126
|
+
areas.set(this.areas);
|
|
127
|
+
this.areas = areas;
|
|
128
|
+
const bounds = new Float64Array(size * 4);
|
|
129
|
+
bounds.set(this.partBounds);
|
|
130
|
+
this.partBounds = bounds;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The bounding box of everything written, in tile-local integers.
|
|
134
|
+
* Free here — the read already touches every vertex — and what lets a
|
|
135
|
+
* painter skip a building that is a third of a pixel across, which at a
|
|
136
|
+
* country-wide zoom is most of what a tile contains. */
|
|
137
|
+
minX = 0;
|
|
138
|
+
minY = 0;
|
|
139
|
+
maxX = 0;
|
|
140
|
+
maxY = 0;
|
|
141
|
+
|
|
142
|
+
reset(): void {
|
|
143
|
+
this.parts = 0;
|
|
144
|
+
this.points = 0;
|
|
145
|
+
this.starts[0] = 0;
|
|
146
|
+
this.minX = Infinity;
|
|
147
|
+
this.minY = Infinity;
|
|
148
|
+
this.maxX = -Infinity;
|
|
149
|
+
this.maxY = -Infinity;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// --- the protobuf wire format ----------------------------------------------
|
|
154
|
+
//
|
|
155
|
+
// A reader rather than a dependency: MVT uses four of protobuf's features
|
|
156
|
+
// (varints, zigzag, length-delimited submessages, packed repeated fields)
|
|
157
|
+
// and none of the rest, so the whole of what a `pbf` dependency would bring
|
|
158
|
+
// is below, at a size where it can be read in one sitting.
|
|
159
|
+
|
|
160
|
+
const WIRE_VARINT = 0;
|
|
161
|
+
const WIRE_FIXED64 = 1;
|
|
162
|
+
const WIRE_BYTES = 2;
|
|
163
|
+
const WIRE_FIXED32 = 5;
|
|
164
|
+
|
|
165
|
+
/** `TextDecoder` through `globalThis`: `src/` compiles with `types: []` and
|
|
166
|
+
* no DOM lib, so naming the global directly would make this package depend
|
|
167
|
+
* on one of them. It exists on every runtime this can run on. */
|
|
168
|
+
const decoderOf = (): { decode(input: Uint8Array): string } | null => {
|
|
169
|
+
const g = globalThis as {
|
|
170
|
+
TextDecoder?: new (label?: string) => { decode(input: Uint8Array): string };
|
|
171
|
+
};
|
|
172
|
+
return g.TextDecoder ? new g.TextDecoder('utf-8') : null;
|
|
173
|
+
};
|
|
174
|
+
let utf8: { decode(input: Uint8Array): string } | null | undefined;
|
|
175
|
+
|
|
176
|
+
/** A malformed tile. Thrown rather than tolerated: a truncated protobuf is
|
|
177
|
+
* a transport bug, and silently rendering the half of a tile that parsed
|
|
178
|
+
* is how a map ends up missing a river nobody can explain. */
|
|
179
|
+
export class MvtError extends Error {
|
|
180
|
+
constructor(message: string) {
|
|
181
|
+
super(message);
|
|
182
|
+
this.name = 'MvtError';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
class Reader {
|
|
187
|
+
buf: Uint8Array;
|
|
188
|
+
pos: number;
|
|
189
|
+
end: number;
|
|
190
|
+
|
|
191
|
+
constructor(buf: Uint8Array, pos = 0, end = buf.length) {
|
|
192
|
+
this.buf = buf;
|
|
193
|
+
this.pos = pos;
|
|
194
|
+
this.end = end;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* A varint, as a JavaScript number.
|
|
199
|
+
*
|
|
200
|
+
* Unrolled to seven bytes — 2^49, which covers every field MVT puts in a
|
|
201
|
+
* varint except a feature `id` that used the full 64 bits. Past that the
|
|
202
|
+
* slow path accumulates in floating point and loses the low bits above
|
|
203
|
+
* 2^53; the alternative is a BigInt per id, which costs every tile
|
|
204
|
+
* something to be exact about a case that does not occur in tiles cut
|
|
205
|
+
* from OSM (whose largest ids are ~2^33).
|
|
206
|
+
*/
|
|
207
|
+
varint(): number {
|
|
208
|
+
const b = this.buf;
|
|
209
|
+
let p = this.pos;
|
|
210
|
+
let byte = b[p++];
|
|
211
|
+
let value = byte & 0x7f;
|
|
212
|
+
if (byte < 0x80) {
|
|
213
|
+
this.pos = p;
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
byte = b[p++];
|
|
217
|
+
value |= (byte & 0x7f) << 7;
|
|
218
|
+
if (byte < 0x80) {
|
|
219
|
+
this.pos = p;
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
byte = b[p++];
|
|
223
|
+
value |= (byte & 0x7f) << 14;
|
|
224
|
+
if (byte < 0x80) {
|
|
225
|
+
this.pos = p;
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
byte = b[p++];
|
|
229
|
+
value |= (byte & 0x7f) << 21;
|
|
230
|
+
if (byte < 0x80) {
|
|
231
|
+
this.pos = p;
|
|
232
|
+
return value >>> 0;
|
|
233
|
+
}
|
|
234
|
+
// Four bytes carry bits 0-27, so from here the shift would leave int32
|
|
235
|
+
// and the rest has to be *added* in floating point: `1 << 35` is 8, not
|
|
236
|
+
// 34359738368. Above 2^53 the low bits are gone — see the note above.
|
|
237
|
+
let out = value >>> 0;
|
|
238
|
+
for (let shift = 28; shift < 64; shift += 7) {
|
|
239
|
+
byte = b[p++];
|
|
240
|
+
if (byte === undefined) throw new MvtError('varint ran past the end');
|
|
241
|
+
out += (byte & 0x7f) * Math.pow(2, shift);
|
|
242
|
+
if (byte < 0x80) break;
|
|
243
|
+
}
|
|
244
|
+
this.pos = p;
|
|
245
|
+
return out;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** A zigzag-encoded signed varint. */
|
|
249
|
+
svarint(): number {
|
|
250
|
+
const n = this.varint();
|
|
251
|
+
return (n >>> 1) ^ -(n & 1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
skip(wire: number): void {
|
|
255
|
+
if (wire === WIRE_VARINT) {
|
|
256
|
+
while (this.buf[this.pos++] >= 0x80) {
|
|
257
|
+
if (this.pos > this.end) throw new MvtError('varint ran past the end');
|
|
258
|
+
}
|
|
259
|
+
} else if (wire === WIRE_BYTES) {
|
|
260
|
+
this.pos += this.varint();
|
|
261
|
+
} else if (wire === WIRE_FIXED32) {
|
|
262
|
+
this.pos += 4;
|
|
263
|
+
} else if (wire === WIRE_FIXED64) {
|
|
264
|
+
this.pos += 8;
|
|
265
|
+
} else {
|
|
266
|
+
throw new MvtError(`unknown wire type ${wire}`);
|
|
267
|
+
}
|
|
268
|
+
if (this.pos > this.end) throw new MvtError('field ran past the end');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** One view over the whole buffer rather than one per read: only a
|
|
272
|
+
* layer's `values` use these, but a dense tile has thousands of them. */
|
|
273
|
+
private _view: DataView | null = null;
|
|
274
|
+
private view(): DataView {
|
|
275
|
+
if (!this._view) {
|
|
276
|
+
this._view = new DataView(
|
|
277
|
+
this.buf.buffer,
|
|
278
|
+
this.buf.byteOffset,
|
|
279
|
+
this.buf.byteLength,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return this._view;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
float(): number {
|
|
286
|
+
const at = this.pos;
|
|
287
|
+
this.pos += 4;
|
|
288
|
+
return this.view().getFloat32(at, true);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
double(): number {
|
|
292
|
+
const at = this.pos;
|
|
293
|
+
this.pos += 8;
|
|
294
|
+
return this.view().getFloat64(at, true);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
string(): string {
|
|
298
|
+
const length = this.varint();
|
|
299
|
+
const start = this.pos;
|
|
300
|
+
this.pos += length;
|
|
301
|
+
if (this.pos > this.end) throw new MvtError('string ran past the end');
|
|
302
|
+
const buf = this.buf;
|
|
303
|
+
// Tag keys and most tag values are ASCII, and building those directly
|
|
304
|
+
// is several times quicker than a `TextDecoder` call whose fixed cost
|
|
305
|
+
// dominates a nine-byte string. Anything with a high bit set — a place
|
|
306
|
+
// name, which is most of them outside the anglosphere — goes the
|
|
307
|
+
// correct way.
|
|
308
|
+
let ascii = true;
|
|
309
|
+
for (let i = start; i < this.pos; i++) {
|
|
310
|
+
if (buf[i] >= 0x80) {
|
|
311
|
+
ascii = false;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (ascii) {
|
|
316
|
+
let out = '';
|
|
317
|
+
for (let i = start; i < this.pos; i += 1024) {
|
|
318
|
+
out += String.fromCharCode.apply(
|
|
319
|
+
null,
|
|
320
|
+
buf.subarray(i, Math.min(i + 1024, this.pos)) as unknown as number[],
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
if (utf8 === undefined) utf8 = decoderOf();
|
|
326
|
+
if (utf8) return utf8.decode(buf.subarray(start, this.pos));
|
|
327
|
+
return decodeUtf8(buf, start, this.pos);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** The fallback for a runtime with no `TextDecoder`. Correct, not quick —
|
|
332
|
+
* and never reached on Node, Bun or a browser. */
|
|
333
|
+
function decodeUtf8(buf: Uint8Array, start: number, end: number): string {
|
|
334
|
+
let out = '';
|
|
335
|
+
let i = start;
|
|
336
|
+
while (i < end) {
|
|
337
|
+
const b0 = buf[i++];
|
|
338
|
+
if (b0 < 0x80) {
|
|
339
|
+
out += String.fromCharCode(b0);
|
|
340
|
+
} else if (b0 < 0xe0) {
|
|
341
|
+
out += String.fromCharCode(((b0 & 0x1f) << 6) | (buf[i++] & 0x3f));
|
|
342
|
+
} else if (b0 < 0xf0) {
|
|
343
|
+
out += String.fromCharCode(
|
|
344
|
+
((b0 & 0x0f) << 12) | ((buf[i++] & 0x3f) << 6) | (buf[i++] & 0x3f),
|
|
345
|
+
);
|
|
346
|
+
} else {
|
|
347
|
+
const cp =
|
|
348
|
+
((b0 & 0x07) << 18) |
|
|
349
|
+
((buf[i++] & 0x3f) << 12) |
|
|
350
|
+
((buf[i++] & 0x3f) << 6) |
|
|
351
|
+
(buf[i++] & 0x3f);
|
|
352
|
+
out += String.fromCodePoint(cp);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// --- the schema ------------------------------------------------------------
|
|
359
|
+
|
|
360
|
+
/** A decoded tile: its layers, by name. Layers are ordered as the tile
|
|
361
|
+
* ordered them, which is the order a producer means them to be drawn in
|
|
362
|
+
* when a style says nothing. */
|
|
363
|
+
export interface VectorTile {
|
|
364
|
+
layers: Map<string, VectorTileLayer>;
|
|
365
|
+
/** Layer names in tile order. */
|
|
366
|
+
order: string[];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* One layer of a tile.
|
|
371
|
+
*
|
|
372
|
+
* `keys` and `values` are decoded eagerly because MVT **deduplicates**
|
|
373
|
+
* them — a tile with 12,000 features has perhaps 40 keys and 2,000 distinct
|
|
374
|
+
* values, so decoding them once is cheaper than decoding a feature's tags
|
|
375
|
+
* lazily would be. Features are the other way round: their byte ranges are
|
|
376
|
+
* collected on construction and nothing inside one is touched until it is
|
|
377
|
+
* asked for.
|
|
378
|
+
*/
|
|
379
|
+
export class VectorTileLayer {
|
|
380
|
+
readonly name: string;
|
|
381
|
+
readonly version: number;
|
|
382
|
+
/** Tile-local coordinate units across the tile's square. 4096 unless the
|
|
383
|
+
* producer said otherwise. */
|
|
384
|
+
readonly extent: number;
|
|
385
|
+
readonly keys: string[];
|
|
386
|
+
readonly values: FeatureValue[];
|
|
387
|
+
/** How many features. */
|
|
388
|
+
readonly length: number;
|
|
389
|
+
|
|
390
|
+
private readonly _buf: Uint8Array;
|
|
391
|
+
/** Byte offset of feature `i`, with a sentinel: `[starts[i], starts[i+1])`
|
|
392
|
+
* is not a valid span (features are not contiguous), so both ends are
|
|
393
|
+
* stored. */
|
|
394
|
+
private readonly _at: Uint32Array;
|
|
395
|
+
private readonly _to: Uint32Array;
|
|
396
|
+
private _keyIndex: Map<string, number> | null = null;
|
|
397
|
+
|
|
398
|
+
constructor(reader: Reader, end: number) {
|
|
399
|
+
this._buf = reader.buf;
|
|
400
|
+
let name = '';
|
|
401
|
+
let version = 1;
|
|
402
|
+
let extent = 4096;
|
|
403
|
+
const keys: string[] = [];
|
|
404
|
+
const values: FeatureValue[] = [];
|
|
405
|
+
const at: number[] = [];
|
|
406
|
+
const to: number[] = [];
|
|
407
|
+
while (reader.pos < end) {
|
|
408
|
+
const tag = reader.varint();
|
|
409
|
+
const field = tag >> 3;
|
|
410
|
+
const wire = tag & 0x7;
|
|
411
|
+
if (field === 1 && wire === WIRE_BYTES) {
|
|
412
|
+
name = reader.string();
|
|
413
|
+
} else if (field === 15 && wire === WIRE_VARINT) {
|
|
414
|
+
version = reader.varint();
|
|
415
|
+
} else if (field === 5 && wire === WIRE_VARINT) {
|
|
416
|
+
extent = reader.varint();
|
|
417
|
+
} else if (field === 3 && wire === WIRE_BYTES) {
|
|
418
|
+
keys.push(reader.string());
|
|
419
|
+
} else if (field === 4 && wire === WIRE_BYTES) {
|
|
420
|
+
const length = reader.varint();
|
|
421
|
+
// `readValue` leaves `pos` at the value's end, so nothing here has
|
|
422
|
+
// to move it.
|
|
423
|
+
values.push(readValue(reader, reader.pos + length));
|
|
424
|
+
} else if (field === 2 && wire === WIRE_BYTES) {
|
|
425
|
+
const length = reader.varint();
|
|
426
|
+
at.push(reader.pos);
|
|
427
|
+
reader.pos += length;
|
|
428
|
+
to.push(reader.pos);
|
|
429
|
+
} else {
|
|
430
|
+
reader.skip(wire);
|
|
431
|
+
}
|
|
432
|
+
if (reader.pos > end) throw new MvtError(`layer "${name}" overran`);
|
|
433
|
+
}
|
|
434
|
+
if (extent <= 0) throw new MvtError(`layer "${name}" has extent ${extent}`);
|
|
435
|
+
this.name = name;
|
|
436
|
+
this.version = version;
|
|
437
|
+
this.extent = extent;
|
|
438
|
+
this.keys = keys;
|
|
439
|
+
this.values = values;
|
|
440
|
+
this._at = new Uint32Array(at);
|
|
441
|
+
this._to = new Uint32Array(to);
|
|
442
|
+
this.length = at.length;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Which tag index a key name has, or -1. Built on first use, because a
|
|
446
|
+
* style usually reads two or three of a layer's forty keys. */
|
|
447
|
+
keyIndex(key: string): number {
|
|
448
|
+
if (!this._keyIndex) {
|
|
449
|
+
this._keyIndex = new Map();
|
|
450
|
+
for (let i = 0; i < this.keys.length; i++) {
|
|
451
|
+
this._keyIndex.set(this.keys[i], i);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return this._keyIndex.get(key) ?? -1;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** Feature `i`, as a fresh cursor. */
|
|
458
|
+
feature(i: number): FeatureCursor {
|
|
459
|
+
const cursor = new FeatureCursor();
|
|
460
|
+
this.seek(i, cursor);
|
|
461
|
+
return cursor;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Point an existing cursor at feature `i`.
|
|
466
|
+
*
|
|
467
|
+
* The whole reason the cursor is a separate object: a rasterizer walks
|
|
468
|
+
* twelve thousand features and wants none of them to be an allocation.
|
|
469
|
+
*/
|
|
470
|
+
seek(i: number, cursor: FeatureCursor): FeatureCursor {
|
|
471
|
+
if (i < 0 || i >= this.length) {
|
|
472
|
+
throw new MvtError(`feature ${i} of ${this.length}`);
|
|
473
|
+
}
|
|
474
|
+
cursor._bind(this, this._buf, this._at[i], this._to[i]);
|
|
475
|
+
return cursor;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Every feature, as fresh cursors. Convenience — the allocation-free
|
|
479
|
+
* path is {@link seek}. */
|
|
480
|
+
*features(): Generator<FeatureCursor> {
|
|
481
|
+
const cursor = new FeatureCursor();
|
|
482
|
+
for (let i = 0; i < this.length; i++) yield this.seek(i, cursor);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function readValue(reader: Reader, end: number): FeatureValue {
|
|
487
|
+
let out: FeatureValue = '';
|
|
488
|
+
while (reader.pos < end) {
|
|
489
|
+
const tag = reader.varint();
|
|
490
|
+
const field = tag >> 3;
|
|
491
|
+
const wire = tag & 0x7;
|
|
492
|
+
if (field === 1 && wire === WIRE_BYTES) out = reader.string();
|
|
493
|
+
else if (field === 2 && wire === WIRE_FIXED32) out = reader.float();
|
|
494
|
+
else if (field === 3 && wire === WIRE_FIXED64) out = reader.double();
|
|
495
|
+
else if (field === 4 && wire === WIRE_VARINT) out = reader.varint();
|
|
496
|
+
else if (field === 5 && wire === WIRE_VARINT) out = reader.varint();
|
|
497
|
+
else if (field === 6 && wire === WIRE_VARINT) out = reader.svarint();
|
|
498
|
+
else if (field === 7 && wire === WIRE_VARINT) out = reader.varint() !== 0;
|
|
499
|
+
else reader.skip(wire);
|
|
500
|
+
}
|
|
501
|
+
reader.pos = end;
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** A position on a cursor's feature. */
|
|
506
|
+
export interface TilePoint {
|
|
507
|
+
x: number;
|
|
508
|
+
y: number;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* One feature, as a movable cursor over the tile's bytes.
|
|
513
|
+
*
|
|
514
|
+
* Nothing inside the feature is decoded until it is asked for, and a
|
|
515
|
+
* cursor's own state is four numbers, so `layer.seek(i, cursor)` in a loop
|
|
516
|
+
* allocates nothing.
|
|
517
|
+
*/
|
|
518
|
+
export class FeatureCursor {
|
|
519
|
+
/** The feature's `id`, or undefined when it carries none. Ids above 2^53
|
|
520
|
+
* lose their low bits — see {@link Reader.varint}. */
|
|
521
|
+
id: number | undefined = undefined;
|
|
522
|
+
type: GeomType = GeomType.Unknown;
|
|
523
|
+
layer: VectorTileLayer | null = null;
|
|
524
|
+
|
|
525
|
+
private _buf: Uint8Array = EMPTY;
|
|
526
|
+
private _tagAt = 0;
|
|
527
|
+
private _tagTo = 0;
|
|
528
|
+
private _geomAt = 0;
|
|
529
|
+
private _geomTo = 0;
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* The feature's tags as key/value **indices**, decoded on the first
|
|
533
|
+
* {@link get} and reused for every one after it.
|
|
534
|
+
*
|
|
535
|
+
* The reason this exists rather than a scan of the bytes per call: a
|
|
536
|
+
* style asks the same feature about the same two or three keys once per
|
|
537
|
+
* style layer over that source layer, and a road network is fourteen
|
|
538
|
+
* such layers (seven classes, a casing pass and a fill pass). Re-decoding
|
|
539
|
+
* a feature's varints fourteen times over is, on the corpus, most of the
|
|
540
|
+
* cost of a zoom-12 tile. Decoded once, a lookup is a scan over a dozen
|
|
541
|
+
* integers.
|
|
542
|
+
*/
|
|
543
|
+
private _tagKeys = EMPTY_INTS;
|
|
544
|
+
private _tagValues = EMPTY_INTS;
|
|
545
|
+
private _tagCount = -1;
|
|
546
|
+
|
|
547
|
+
/** @internal */
|
|
548
|
+
_bind(layer: VectorTileLayer, buf: Uint8Array, at: number, to: number): void {
|
|
549
|
+
this.layer = layer;
|
|
550
|
+
this._buf = buf;
|
|
551
|
+
this.id = undefined;
|
|
552
|
+
this.type = GeomType.Unknown;
|
|
553
|
+
this._tagAt = 0;
|
|
554
|
+
this._tagTo = 0;
|
|
555
|
+
this._geomAt = 0;
|
|
556
|
+
this._geomTo = 0;
|
|
557
|
+
this._tagCount = -1;
|
|
558
|
+
const reader = new Reader(buf, at, to);
|
|
559
|
+
while (reader.pos < to) {
|
|
560
|
+
const tag = reader.varint();
|
|
561
|
+
const field = tag >> 3;
|
|
562
|
+
const wire = tag & 0x7;
|
|
563
|
+
if (field === 1 && wire === WIRE_VARINT) {
|
|
564
|
+
this.id = reader.varint();
|
|
565
|
+
} else if (field === 3 && wire === WIRE_VARINT) {
|
|
566
|
+
this.type = reader.varint() as GeomType;
|
|
567
|
+
} else if (field === 2 && wire === WIRE_BYTES) {
|
|
568
|
+
const length = reader.varint();
|
|
569
|
+
this._tagAt = reader.pos;
|
|
570
|
+
this._tagTo = reader.pos + length;
|
|
571
|
+
reader.pos = this._tagTo;
|
|
572
|
+
} else if (field === 4 && wire === WIRE_BYTES) {
|
|
573
|
+
const length = reader.varint();
|
|
574
|
+
this._geomAt = reader.pos;
|
|
575
|
+
this._geomTo = reader.pos + length;
|
|
576
|
+
reader.pos = this._geomTo;
|
|
577
|
+
} else {
|
|
578
|
+
reader.skip(wire);
|
|
579
|
+
}
|
|
580
|
+
if (reader.pos > to) throw new MvtError('feature overran');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** Tile-local units across this feature's tile. */
|
|
585
|
+
get extent(): number {
|
|
586
|
+
return this.layer?.extent ?? 4096;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* One tag, by key name.
|
|
591
|
+
*
|
|
592
|
+
* A linear scan of the tag pairs rather than a materialized object: a
|
|
593
|
+
* feature has five to fifteen tags and a style filter reads one or two of
|
|
594
|
+
* them, so the scan is quicker than the object it would have built and
|
|
595
|
+
* costs nothing to throw away.
|
|
596
|
+
*/
|
|
597
|
+
get(key: string): FeatureValue | undefined {
|
|
598
|
+
const layer = this.layer;
|
|
599
|
+
if (!layer || this._tagTo === this._tagAt) return undefined;
|
|
600
|
+
const wanted = layer.keyIndex(key);
|
|
601
|
+
if (wanted < 0) return undefined;
|
|
602
|
+
if (this._tagCount < 0) this._loadTags();
|
|
603
|
+
const keys = this._tagKeys;
|
|
604
|
+
for (let i = 0; i < this._tagCount; i++) {
|
|
605
|
+
if (keys[i] === wanted) return layer.values[this._tagValues[i]];
|
|
606
|
+
}
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** Decode the tag pairs into the cursor's own arrays. Grown, never
|
|
611
|
+
* shrunk, so a cursor walking a layer settles on one allocation. */
|
|
612
|
+
private _loadTags(): void {
|
|
613
|
+
const reader = new Reader(this._buf, this._tagAt, this._tagTo);
|
|
614
|
+
let count = 0;
|
|
615
|
+
let keys = this._tagKeys;
|
|
616
|
+
let values = this._tagValues;
|
|
617
|
+
while (reader.pos < this._tagTo) {
|
|
618
|
+
const k = reader.varint();
|
|
619
|
+
if (reader.pos >= this._tagTo) break; // a key with no value: malformed
|
|
620
|
+
const v = reader.varint();
|
|
621
|
+
if (count >= keys.length) {
|
|
622
|
+
const size = Math.max(8, keys.length * 2);
|
|
623
|
+
const nextKeys = new Int32Array(size);
|
|
624
|
+
nextKeys.set(keys);
|
|
625
|
+
keys = nextKeys;
|
|
626
|
+
const nextValues = new Int32Array(size);
|
|
627
|
+
nextValues.set(values);
|
|
628
|
+
values = nextValues;
|
|
629
|
+
}
|
|
630
|
+
keys[count] = k;
|
|
631
|
+
values[count] = v;
|
|
632
|
+
count++;
|
|
633
|
+
}
|
|
634
|
+
this._tagKeys = keys;
|
|
635
|
+
this._tagValues = values;
|
|
636
|
+
this._tagCount = count;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
has(key: string): boolean {
|
|
640
|
+
return this.get(key) !== undefined;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** Every tag, as an object. For a hover readout or a click payload — not
|
|
644
|
+
* for a filter, which should use {@link get}. */
|
|
645
|
+
properties(): Record<string, FeatureValue> {
|
|
646
|
+
const layer = this.layer;
|
|
647
|
+
const out: Record<string, FeatureValue> = {};
|
|
648
|
+
if (!layer || this._tagTo === this._tagAt) return out;
|
|
649
|
+
if (this._tagCount < 0) this._loadTags();
|
|
650
|
+
for (let i = 0; i < this._tagCount; i++) {
|
|
651
|
+
const key = layer.keys[this._tagKeys[i]];
|
|
652
|
+
const index = this._tagValues[i];
|
|
653
|
+
if (key !== undefined && index < layer.values.length) {
|
|
654
|
+
out[key] = layer.values[index];
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return out;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Read this feature's geometry into `into`, in tile-local integers.
|
|
662
|
+
*
|
|
663
|
+
* The one hot loop in the decoder. Everything about its shape is about
|
|
664
|
+
* not allocating: the cursor deltas accumulate in two locals, the
|
|
665
|
+
* vertices go into a typed array the caller owns, and a polygon's ring
|
|
666
|
+
* areas and the whole feature's bounding box are accumulated in the same
|
|
667
|
+
* pass rather than in two more over the same points.
|
|
668
|
+
*/
|
|
669
|
+
readGeometry(into: GeometryBuffer): GeometryData {
|
|
670
|
+
into.reset();
|
|
671
|
+
if (this._geomTo === this._geomAt) {
|
|
672
|
+
into.starts[0] = 0;
|
|
673
|
+
return into;
|
|
674
|
+
}
|
|
675
|
+
const reader = new Reader(this._buf, this._geomAt, this._geomTo);
|
|
676
|
+
const end = this._geomTo;
|
|
677
|
+
const isPolygon = this.type === GeomType.Polygon;
|
|
678
|
+
const isPoint = this.type === GeomType.Point;
|
|
679
|
+
let x = 0;
|
|
680
|
+
let y = 0;
|
|
681
|
+
let cmd = 0;
|
|
682
|
+
let left = 0;
|
|
683
|
+
let points = 0;
|
|
684
|
+
let parts = 0;
|
|
685
|
+
// The open part's first vertex and its running shoelace sum.
|
|
686
|
+
let firstX = 0;
|
|
687
|
+
let firstY = 0;
|
|
688
|
+
let prevX = 0;
|
|
689
|
+
let prevY = 0;
|
|
690
|
+
let sum = 0;
|
|
691
|
+
let open = false;
|
|
692
|
+
let minX = Infinity;
|
|
693
|
+
let minY = Infinity;
|
|
694
|
+
let maxX = -Infinity;
|
|
695
|
+
let maxY = -Infinity;
|
|
696
|
+
|
|
697
|
+
// The open part's own box, closed out into `partBounds` beside its
|
|
698
|
+
// area. Tracked per part rather than per feature because the feature
|
|
699
|
+
// box cannot cull a multipolygon — see `GeometryBuffer.partBounds`.
|
|
700
|
+
let pMinX = Infinity;
|
|
701
|
+
let pMinY = Infinity;
|
|
702
|
+
let pMaxX = -Infinity;
|
|
703
|
+
let pMaxY = -Infinity;
|
|
704
|
+
|
|
705
|
+
const closePart = (): void => {
|
|
706
|
+
if (!open) return;
|
|
707
|
+
into.reserveParts(parts + 1);
|
|
708
|
+
// A ring's last point is not repeated before `ClosePath`, so the
|
|
709
|
+
// closing edge — last back to first — is added here and nowhere else.
|
|
710
|
+
into.areas[parts] = isPolygon
|
|
711
|
+
? (sum + (prevX * firstY - firstX * prevY)) / 2
|
|
712
|
+
: 0;
|
|
713
|
+
const at = parts * 4;
|
|
714
|
+
into.partBounds[at] = pMinX;
|
|
715
|
+
into.partBounds[at + 1] = pMinY;
|
|
716
|
+
into.partBounds[at + 2] = pMaxX;
|
|
717
|
+
into.partBounds[at + 3] = pMaxY;
|
|
718
|
+
if (pMinX < minX) minX = pMinX;
|
|
719
|
+
if (pMinY < minY) minY = pMinY;
|
|
720
|
+
if (pMaxX > maxX) maxX = pMaxX;
|
|
721
|
+
if (pMaxY > maxY) maxY = pMaxY;
|
|
722
|
+
pMinX = Infinity;
|
|
723
|
+
pMinY = Infinity;
|
|
724
|
+
pMaxX = -Infinity;
|
|
725
|
+
pMaxY = -Infinity;
|
|
726
|
+
parts++;
|
|
727
|
+
into.starts[parts] = points;
|
|
728
|
+
open = false;
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
while (reader.pos < end) {
|
|
732
|
+
if (left <= 0) {
|
|
733
|
+
const command = reader.varint();
|
|
734
|
+
cmd = command & 0x7;
|
|
735
|
+
left = command >> 3;
|
|
736
|
+
if (left <= 0) continue; // a zero-count command: nothing to do
|
|
737
|
+
}
|
|
738
|
+
left--;
|
|
739
|
+
if (cmd === 1) {
|
|
740
|
+
// MoveTo. For a line or a polygon it begins a part; for a point
|
|
741
|
+
// feature every step of one MoveTo is another point of the *same*
|
|
742
|
+
// multipoint, and giving each its own part would make a 500-point
|
|
743
|
+
// cluster 500 parts for no reader's benefit.
|
|
744
|
+
if (!isPoint) closePart();
|
|
745
|
+
x += reader.svarint();
|
|
746
|
+
y += reader.svarint();
|
|
747
|
+
if (!open) {
|
|
748
|
+
firstX = x;
|
|
749
|
+
firstY = y;
|
|
750
|
+
sum = 0;
|
|
751
|
+
open = true;
|
|
752
|
+
into.starts[parts] = points;
|
|
753
|
+
}
|
|
754
|
+
into.reserveCoords(points + 1);
|
|
755
|
+
into.coords[points * 2] = x;
|
|
756
|
+
into.coords[points * 2 + 1] = y;
|
|
757
|
+
points++;
|
|
758
|
+
prevX = x;
|
|
759
|
+
prevY = y;
|
|
760
|
+
} else if (cmd === 2) {
|
|
761
|
+
x += reader.svarint();
|
|
762
|
+
y += reader.svarint();
|
|
763
|
+
if (!open) {
|
|
764
|
+
// A LineTo with no MoveTo before it. Malformed, and tolerated as
|
|
765
|
+
// the start of a part rather than dropped: the vertex is real
|
|
766
|
+
// data and the alternative is a road with a gap in it.
|
|
767
|
+
firstX = x;
|
|
768
|
+
firstY = y;
|
|
769
|
+
sum = 0;
|
|
770
|
+
open = true;
|
|
771
|
+
into.starts[parts] = points;
|
|
772
|
+
} else if (isPolygon) {
|
|
773
|
+
sum += prevX * y - x * prevY;
|
|
774
|
+
}
|
|
775
|
+
into.reserveCoords(points + 1);
|
|
776
|
+
into.coords[points * 2] = x;
|
|
777
|
+
into.coords[points * 2 + 1] = y;
|
|
778
|
+
points++;
|
|
779
|
+
prevX = x;
|
|
780
|
+
prevY = y;
|
|
781
|
+
} else if (cmd === 7) {
|
|
782
|
+
closePart();
|
|
783
|
+
} else {
|
|
784
|
+
throw new MvtError(`unknown geometry command ${cmd}`);
|
|
785
|
+
}
|
|
786
|
+
if (x < pMinX) pMinX = x;
|
|
787
|
+
if (x > pMaxX) pMaxX = x;
|
|
788
|
+
if (y < pMinY) pMinY = y;
|
|
789
|
+
if (y > pMaxY) pMaxY = y;
|
|
790
|
+
}
|
|
791
|
+
closePart();
|
|
792
|
+
into.points = points;
|
|
793
|
+
into.parts = parts;
|
|
794
|
+
into.minX = minX;
|
|
795
|
+
into.minY = minY;
|
|
796
|
+
into.maxX = maxX;
|
|
797
|
+
into.maxY = maxY;
|
|
798
|
+
return into;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** Geometry as arrays of its own — the convenience form. */
|
|
802
|
+
geometry(): GeometryData {
|
|
803
|
+
const buffer = new GeometryBuffer(64, 4);
|
|
804
|
+
this.readGeometry(buffer);
|
|
805
|
+
// Trimmed, since this shape is the one that gets kept.
|
|
806
|
+
return {
|
|
807
|
+
coords: buffer.coords.slice(0, buffer.points * 2),
|
|
808
|
+
starts: buffer.starts.slice(0, buffer.parts + 1),
|
|
809
|
+
areas: buffer.areas.slice(0, buffer.parts),
|
|
810
|
+
parts: buffer.parts,
|
|
811
|
+
points: buffer.points,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const EMPTY = new Uint8Array(0);
|
|
817
|
+
const EMPTY_INTS = new Int32Array(0);
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Decode a tile.
|
|
821
|
+
*
|
|
822
|
+
* Cheap: it walks the tile's top-level fields and each layer's, which is
|
|
823
|
+
* the layer names, their key and value tables, and one byte range per
|
|
824
|
+
* feature. No geometry is touched, so a style that draws four of a tile's
|
|
825
|
+
* fourteen layers pays for four.
|
|
826
|
+
*
|
|
827
|
+
* The bytes must be the raw protobuf. Tiles are almost always served
|
|
828
|
+
* `Content-Encoding: gzip` and sometimes gzipped *inside* that, which is a
|
|
829
|
+
* transport concern and deliberately not this function's — a source adapter
|
|
830
|
+
* un-gzips (`src/maps/sources.ts` does).
|
|
831
|
+
*/
|
|
832
|
+
export function parseTile(bytes: Uint8Array): VectorTile {
|
|
833
|
+
const layers = new Map<string, VectorTileLayer>();
|
|
834
|
+
const order: string[] = [];
|
|
835
|
+
const reader = new Reader(bytes);
|
|
836
|
+
while (reader.pos < reader.end) {
|
|
837
|
+
const tag = reader.varint();
|
|
838
|
+
const field = tag >> 3;
|
|
839
|
+
const wire = tag & 0x7;
|
|
840
|
+
if (field === 3 && wire === WIRE_BYTES) {
|
|
841
|
+
const length = reader.varint();
|
|
842
|
+
const end = reader.pos + length;
|
|
843
|
+
if (end > reader.end) throw new MvtError('layer ran past the end');
|
|
844
|
+
const layer = new VectorTileLayer(reader, end);
|
|
845
|
+
reader.pos = end;
|
|
846
|
+
// A tile with two layers of one name is malformed; the first wins,
|
|
847
|
+
// which is what every other decoder does.
|
|
848
|
+
if (!layers.has(layer.name)) {
|
|
849
|
+
layers.set(layer.name, layer);
|
|
850
|
+
order.push(layer.name);
|
|
851
|
+
}
|
|
852
|
+
} else {
|
|
853
|
+
reader.skip(wire);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
return { layers, order };
|
|
857
|
+
}
|