@tileflow/static 0.1.0-alpha.3 → 0.1.0-alpha.30
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/LICENSE +201 -0
- package/README.md +286 -7
- package/dist/chunk-6DWVCK64.js +406 -0
- package/dist/chunk-ALFVZ3PC.js +600 -0
- package/dist/client.d.ts +142 -0
- package/dist/client.js +382 -0
- package/dist/errors-CSz1xaDx.d.ts +23 -0
- package/dist/manifest.d.ts +545 -0
- package/dist/manifest.js +339 -0
- package/dist/overlays.d.ts +22 -0
- package/dist/overlays.js +176 -0
- package/dist/scene-contract-NFnIcQtt.d.ts +734 -0
- package/dist/scene.d.ts +290 -0
- package/dist/scene.js +40 -0
- package/package.json +29 -12
- package/dist/index.d.ts +0 -248
- package/dist/index.js +0 -396
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeStaticAutoFit,
|
|
3
|
+
normalizeStaticScene,
|
|
4
|
+
staticAutoFitPlanSchema,
|
|
5
|
+
validateStaticScene
|
|
6
|
+
} from "./chunk-ALFVZ3PC.js";
|
|
7
|
+
import {
|
|
8
|
+
hashStableValue,
|
|
9
|
+
isSafeHttpUrl,
|
|
10
|
+
jsonByteLength,
|
|
11
|
+
stableStringify,
|
|
12
|
+
staticOverlayPlacements,
|
|
13
|
+
staticSceneSchema,
|
|
14
|
+
stripUndefined
|
|
15
|
+
} from "./chunk-6DWVCK64.js";
|
|
16
|
+
|
|
17
|
+
// src/manifest.ts
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
var staticRendererSchemaVersion = 1;
|
|
20
|
+
var staticAttributionPlanSchemaVersion = 1;
|
|
21
|
+
var staticRenderCompositionSchemaVersion = 1;
|
|
22
|
+
var staticAttributionLimits = {
|
|
23
|
+
maxBytes: 32 * 1024,
|
|
24
|
+
maxEntries: 18,
|
|
25
|
+
maxLinksPerEntry: 8,
|
|
26
|
+
maxTextLength: 16384,
|
|
27
|
+
maxUrlLength: 2048
|
|
28
|
+
};
|
|
29
|
+
var sha256HexSchema = z.string().regex(/^[0-9a-f]{64}$/u);
|
|
30
|
+
var sourceIdSchema = z.string().min(1).max(128);
|
|
31
|
+
var boundedIdentitySchema = z.string().min(1).max(512).refine(hasNoControlCharacters, {
|
|
32
|
+
message: "Expected no control characters"
|
|
33
|
+
});
|
|
34
|
+
var staticResolvedAttributionPositionSchema = z.enum([
|
|
35
|
+
"top-left",
|
|
36
|
+
"top-right",
|
|
37
|
+
"bottom-left",
|
|
38
|
+
"bottom-right"
|
|
39
|
+
]);
|
|
40
|
+
var staticAttributionSegmentSchema = z.discriminatedUnion("kind", [
|
|
41
|
+
z.object({
|
|
42
|
+
kind: z.literal("text"),
|
|
43
|
+
text: z.string().min(1).max(staticAttributionLimits.maxTextLength).refine(hasNoControlCharacters, { message: "Attribution text contains control characters" })
|
|
44
|
+
}).strict(),
|
|
45
|
+
z.object({
|
|
46
|
+
kind: z.literal("link"),
|
|
47
|
+
label: z.string().min(1).max(staticAttributionLimits.maxTextLength).refine(hasNoControlCharacters, {
|
|
48
|
+
message: "Attribution link label contains control characters"
|
|
49
|
+
}),
|
|
50
|
+
url: z.string().trim().url().max(staticAttributionLimits.maxUrlLength).refine(isSafeHttpUrl, {
|
|
51
|
+
message: "Expected an http(s) URL without credentials or a fragment"
|
|
52
|
+
})
|
|
53
|
+
}).strict()
|
|
54
|
+
]);
|
|
55
|
+
var attributionSourceSchema = z.object({
|
|
56
|
+
sourceId: sourceIdSchema,
|
|
57
|
+
sourceSelectionIdentity: boundedIdentitySchema
|
|
58
|
+
}).strict();
|
|
59
|
+
var attributionSourcesSchema = z.array(attributionSourceSchema).min(1).max(18).refine(isUniqueSortedSources, {
|
|
60
|
+
message: "Attribution sources must be unique and sorted"
|
|
61
|
+
});
|
|
62
|
+
var staticAttributionProvenanceSchema = z.discriminatedUnion("authority", [
|
|
63
|
+
z.object({
|
|
64
|
+
authority: z.literal("platform-notice"),
|
|
65
|
+
noticeId: boundedIdentitySchema,
|
|
66
|
+
noticeSha256: sha256HexSchema,
|
|
67
|
+
resource: z.enum(["terrain", "world"]),
|
|
68
|
+
sources: attributionSourcesSchema
|
|
69
|
+
}).strict(),
|
|
70
|
+
z.object({
|
|
71
|
+
authority: z.literal("team-declared"),
|
|
72
|
+
sources: z.array(
|
|
73
|
+
attributionSourceSchema.extend({
|
|
74
|
+
mapRevision: boundedIdentitySchema,
|
|
75
|
+
resourceId: boundedIdentitySchema
|
|
76
|
+
}).strict()
|
|
77
|
+
).min(1).max(18).refine(isUniqueSortedSources, {
|
|
78
|
+
message: "Attribution sources must be unique and sorted"
|
|
79
|
+
})
|
|
80
|
+
}).strict()
|
|
81
|
+
]);
|
|
82
|
+
var staticAttributionEntrySchema = z.object({
|
|
83
|
+
authority: z.enum(["platform-notice", "team-declared"]),
|
|
84
|
+
provenance: staticAttributionProvenanceSchema,
|
|
85
|
+
segments: z.array(staticAttributionSegmentSchema).min(1).max(17)
|
|
86
|
+
}).strict().superRefine((entry, context) => {
|
|
87
|
+
if (entry.authority !== entry.provenance.authority) {
|
|
88
|
+
context.addIssue({
|
|
89
|
+
code: "custom",
|
|
90
|
+
message: "Attribution entry authority must match its provenance",
|
|
91
|
+
path: ["authority"]
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const links = entry.segments.filter((segment) => segment.kind === "link").length;
|
|
95
|
+
if (links > staticAttributionLimits.maxLinksPerEntry) {
|
|
96
|
+
context.addIssue({
|
|
97
|
+
code: "custom",
|
|
98
|
+
message: `Attribution entries can contain at most ${staticAttributionLimits.maxLinksPerEntry} links`,
|
|
99
|
+
path: ["segments"]
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
var staticAttributionPlanSchema = z.object({
|
|
104
|
+
entries: z.array(staticAttributionEntrySchema).max(staticAttributionLimits.maxEntries),
|
|
105
|
+
mode: z.enum(["embedded", "external"]),
|
|
106
|
+
position: z.union([z.literal("auto"), staticResolvedAttributionPositionSchema, z.null()]),
|
|
107
|
+
schemaVersion: z.literal(staticAttributionPlanSchemaVersion)
|
|
108
|
+
}).strict().superRefine((plan, context) => {
|
|
109
|
+
if (plan.mode === "external" && plan.position !== null || plan.mode === "embedded" && plan.position === null) {
|
|
110
|
+
context.addIssue({
|
|
111
|
+
code: "custom",
|
|
112
|
+
message: "Attribution position must match its mode",
|
|
113
|
+
path: ["position"]
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (jsonByteLength(plan) > staticAttributionLimits.maxBytes) {
|
|
117
|
+
context.addIssue({
|
|
118
|
+
code: "custom",
|
|
119
|
+
message: `Attribution plan exceeds ${staticAttributionLimits.maxBytes} bytes`
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
var placementAnchorsSchema = z.object({
|
|
124
|
+
"above-water": z.string().min(1).max(256).nullable(),
|
|
125
|
+
"below-roads": z.string().min(1).max(256).nullable(),
|
|
126
|
+
"above-roads": z.string().min(1).max(256).nullable(),
|
|
127
|
+
"above-buildings": z.string().min(1).max(256).nullable(),
|
|
128
|
+
"below-labels": z.string().min(1).max(256).nullable(),
|
|
129
|
+
"above-labels": z.null()
|
|
130
|
+
}).strict().refine(anchorsHaveTerminalNulls, {
|
|
131
|
+
message: "Placement anchors may become null only at the end of semantic order"
|
|
132
|
+
});
|
|
133
|
+
var iconCoordinate = z.number().int().nonnegative().max(2048);
|
|
134
|
+
var iconStretchRegions = z.array(z.tuple([iconCoordinate, iconCoordinate])).min(1).max(2048);
|
|
135
|
+
var iconLayoutSchema = z.object({
|
|
136
|
+
stretchX: iconStretchRegions.optional(),
|
|
137
|
+
stretchY: iconStretchRegions.optional(),
|
|
138
|
+
content: z.tuple([iconCoordinate, iconCoordinate, iconCoordinate, iconCoordinate]).optional()
|
|
139
|
+
}).strict();
|
|
140
|
+
var resolvedIconSchema = z.object({
|
|
141
|
+
layout: iconLayoutSchema.optional(),
|
|
142
|
+
// Verified asset defaults supplied by the manifest producer, never scene input.
|
|
143
|
+
appearance: z.object({
|
|
144
|
+
representation: z.literal("sdf"),
|
|
145
|
+
defaults: z.object({
|
|
146
|
+
color: z.string().min(1).max(128).refine(hasNoControlCharacters),
|
|
147
|
+
haloColor: z.string().min(1).max(128).refine(hasNoControlCharacters),
|
|
148
|
+
haloWidth: z.number().finite().min(0).max(2),
|
|
149
|
+
haloBlur: z.number().finite().min(0).max(1)
|
|
150
|
+
}).strict()
|
|
151
|
+
}).strict().optional(),
|
|
152
|
+
height: z.number().int().positive().max(2048),
|
|
153
|
+
icon: z.string().max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u),
|
|
154
|
+
overlayIndex: z.number().int().min(0).max(23),
|
|
155
|
+
width: z.number().int().positive().max(2048)
|
|
156
|
+
}).strict().superRefine((icon, context) => {
|
|
157
|
+
const layout = icon.layout;
|
|
158
|
+
if (!layout) return;
|
|
159
|
+
let valid = Object.values(layout).some((value) => value !== void 0);
|
|
160
|
+
const content = layout.content;
|
|
161
|
+
if (content)
|
|
162
|
+
valid &&= content[0] < content[2] && content[1] < content[3] && content[2] <= icon.width && content[3] <= icon.height;
|
|
163
|
+
for (const [axis, bound, low, high] of [
|
|
164
|
+
["stretchX", icon.width, content?.[0], content?.[2]],
|
|
165
|
+
["stretchY", icon.height, content?.[1], content?.[3]]
|
|
166
|
+
]) {
|
|
167
|
+
let end = 0;
|
|
168
|
+
for (const [from, to] of layout[axis] ?? []) {
|
|
169
|
+
valid &&= from >= end && from < to && to <= bound;
|
|
170
|
+
end = to;
|
|
171
|
+
}
|
|
172
|
+
if (layout[axis] && low !== void 0 && high !== void 0)
|
|
173
|
+
valid &&= layout[axis].some(([from, to]) => from < high && to > low);
|
|
174
|
+
}
|
|
175
|
+
if (!valid)
|
|
176
|
+
context.addIssue({
|
|
177
|
+
code: "custom",
|
|
178
|
+
path: ["layout"],
|
|
179
|
+
message: "Invalid icon layout for the resolved sprite dimensions"
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
var staticRenderCompositionSchema = z.object({
|
|
183
|
+
anchors: placementAnchorsSchema,
|
|
184
|
+
icons: z.array(resolvedIconSchema).max(24),
|
|
185
|
+
schemaVersion: z.literal(staticRenderCompositionSchemaVersion)
|
|
186
|
+
}).strict().refine(
|
|
187
|
+
(composition) => composition.icons.every(
|
|
188
|
+
(icon, index) => index === 0 || composition.icons[index - 1].overlayIndex < icon.overlayIndex
|
|
189
|
+
),
|
|
190
|
+
{ message: "Resolved symbol icons must be unique and sorted by overlay index" }
|
|
191
|
+
);
|
|
192
|
+
var staticRenderManifestBaseShape = {
|
|
193
|
+
attribution: staticAttributionPlanSchema,
|
|
194
|
+
autoFit: staticAutoFitPlanSchema.optional(),
|
|
195
|
+
composition: staticRenderCompositionSchema,
|
|
196
|
+
mapId: z.string().trim().min(1).max(128),
|
|
197
|
+
rendererVersion: z.string().trim().min(1).max(64),
|
|
198
|
+
schemaVersion: z.literal(staticRendererSchemaVersion),
|
|
199
|
+
scene: staticSceneSchema,
|
|
200
|
+
styleId: z.string().trim().min(1).max(128).optional(),
|
|
201
|
+
styleRevision: z.string().trim().min(1).max(128),
|
|
202
|
+
styleUrl: z.string().trim().url().max(512).refine(isSafeHttpUrl, {
|
|
203
|
+
message: "Expected an http(s) style URL without credentials or a fragment"
|
|
204
|
+
})
|
|
205
|
+
};
|
|
206
|
+
var staticRenderManifestSchema = z.object(staticRenderManifestBaseShape).strict();
|
|
207
|
+
function validateStaticRenderManifest(input) {
|
|
208
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
209
|
+
return { error: "manifest: Expected an object", ok: false };
|
|
210
|
+
}
|
|
211
|
+
if (input.schemaVersion !== staticRendererSchemaVersion) {
|
|
212
|
+
return { error: "manifest.schemaVersion: Unsupported renderer schema version", ok: false };
|
|
213
|
+
}
|
|
214
|
+
const rawScene = input.scene;
|
|
215
|
+
const sceneValidation = validateStaticScene(rawScene);
|
|
216
|
+
if (!sceneValidation.ok) return sceneValidation;
|
|
217
|
+
const parsed = staticRenderManifestSchema.safeParse({
|
|
218
|
+
...input,
|
|
219
|
+
scene: sceneValidation.scene
|
|
220
|
+
});
|
|
221
|
+
if (!parsed.success) return zodFailure(parsed.error);
|
|
222
|
+
const autoFit = expectedAutoFit(sceneValidation.scene);
|
|
223
|
+
if (autoFit === void 0 !== (parsed.data.autoFit === void 0) || autoFit !== void 0 && stableStringify(autoFit) !== stableStringify(parsed.data.autoFit)) {
|
|
224
|
+
return { error: "manifest.autoFit: Expected the exact plan derived from scene", ok: false };
|
|
225
|
+
}
|
|
226
|
+
if (!attributionMatchesScene(parsed.data.attribution, sceneValidation.scene)) {
|
|
227
|
+
return {
|
|
228
|
+
error: "manifest.attribution: Expected the exact request resolved from scene",
|
|
229
|
+
ok: false
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (!compositionMatchesScene(parsed.data.composition, sceneValidation.scene)) {
|
|
233
|
+
return {
|
|
234
|
+
error: "manifest.composition: Expected exact resolved symbol icon references",
|
|
235
|
+
ok: false
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
manifest: {
|
|
240
|
+
...parsed.data,
|
|
241
|
+
...autoFit === void 0 ? {} : { autoFit },
|
|
242
|
+
scene: sceneValidation.scene
|
|
243
|
+
},
|
|
244
|
+
ok: true
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function createRenderManifest(input) {
|
|
248
|
+
const scene = normalizeStaticScene(input.scene);
|
|
249
|
+
const autoFit = expectedAutoFit(scene);
|
|
250
|
+
return staticRenderManifestSchema.parse(
|
|
251
|
+
stripUndefined({
|
|
252
|
+
attribution: input.attribution,
|
|
253
|
+
autoFit,
|
|
254
|
+
composition: input.composition,
|
|
255
|
+
mapId: input.mapId,
|
|
256
|
+
rendererVersion: input.rendererVersion,
|
|
257
|
+
schemaVersion: staticRendererSchemaVersion,
|
|
258
|
+
scene,
|
|
259
|
+
styleId: input.styleId,
|
|
260
|
+
styleRevision: input.styleRevision,
|
|
261
|
+
styleUrl: input.styleUrl
|
|
262
|
+
})
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
async function hashRenderManifest(manifest) {
|
|
266
|
+
const validation = validateStaticRenderManifest(manifest);
|
|
267
|
+
if (!validation.ok) throw new Error(`Invalid Tileflow render manifest: ${validation.error}`);
|
|
268
|
+
return hashStableValue(validation.manifest);
|
|
269
|
+
}
|
|
270
|
+
async function hashStaticSceneRequest(scene) {
|
|
271
|
+
const validation = validateStaticScene(scene);
|
|
272
|
+
if (!validation.ok) throw new Error(`Invalid Tileflow static scene: ${validation.error}`);
|
|
273
|
+
return hashStableValue(validation.scene);
|
|
274
|
+
}
|
|
275
|
+
function attributionMatchesScene(plan, scene) {
|
|
276
|
+
const request = scene.attribution;
|
|
277
|
+
const expectedMode = request?.mode === "external" ? "external" : "embedded";
|
|
278
|
+
const expectedPosition = expectedMode === "external" ? null : request && "position" in request ? request.position ?? "auto" : "auto";
|
|
279
|
+
return plan.mode === expectedMode && plan.position === expectedPosition;
|
|
280
|
+
}
|
|
281
|
+
function compositionMatchesScene(composition, scene) {
|
|
282
|
+
const expected = scene.overlays.flatMap(
|
|
283
|
+
(overlay, overlayIndex) => overlay.type === "symbol" && overlay.icon ? [{ icon: overlay.icon, overlayIndex }] : []
|
|
284
|
+
);
|
|
285
|
+
return expected.length === composition.icons.length && expected.every(
|
|
286
|
+
(icon, index) => composition.icons[index]?.icon === icon.icon && composition.icons[index]?.overlayIndex === icon.overlayIndex
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
function expectedAutoFit(scene) {
|
|
290
|
+
if (scene.camera.type !== "auto") return void 0;
|
|
291
|
+
const analysis = analyzeStaticAutoFit(scene);
|
|
292
|
+
if (!analysis.ok) throw new Error(`Invalid Tileflow auto-fit scene: ${analysis.error}`);
|
|
293
|
+
return analysis.plan;
|
|
294
|
+
}
|
|
295
|
+
function anchorsHaveTerminalNulls(anchors) {
|
|
296
|
+
let sawNull = false;
|
|
297
|
+
for (const placement of staticOverlayPlacements) {
|
|
298
|
+
const anchor = anchors[placement];
|
|
299
|
+
if (anchor === null) sawNull = true;
|
|
300
|
+
else if (sawNull) return false;
|
|
301
|
+
}
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
function hasNoControlCharacters(value) {
|
|
305
|
+
return !Array.from(value).some((character) => {
|
|
306
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
307
|
+
return codePoint <= 31 || codePoint === 127;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function isUniqueSortedSources(values) {
|
|
311
|
+
return values.every(
|
|
312
|
+
(value, index) => index === 0 || compareCodeUnits(values[index - 1].sourceId, value.sourceId) < 0
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
function compareCodeUnits(left, right) {
|
|
316
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
317
|
+
}
|
|
318
|
+
function zodFailure(error) {
|
|
319
|
+
return {
|
|
320
|
+
error: error.issues.map((issue) => `${issue.path.join(".") || "manifest"}: ${issue.message}`).join("; "),
|
|
321
|
+
ok: false
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
export {
|
|
325
|
+
createRenderManifest,
|
|
326
|
+
hashRenderManifest,
|
|
327
|
+
hashStaticSceneRequest,
|
|
328
|
+
staticAttributionEntrySchema,
|
|
329
|
+
staticAttributionLimits,
|
|
330
|
+
staticAttributionPlanSchema,
|
|
331
|
+
staticAttributionPlanSchemaVersion,
|
|
332
|
+
staticAttributionProvenanceSchema,
|
|
333
|
+
staticAttributionSegmentSchema,
|
|
334
|
+
staticRenderCompositionSchema,
|
|
335
|
+
staticRenderCompositionSchemaVersion,
|
|
336
|
+
staticRenderManifestSchema,
|
|
337
|
+
staticRendererSchemaVersion,
|
|
338
|
+
validateStaticRenderManifest
|
|
339
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { c as circleOverlaySchema, b as StaticOverlay, l as lineOverlaySchema, m as markerOverlaySchema, p as polygonOverlaySchema, s as symbolOverlaySchema } from './scene-contract-NFnIcQtt.js';
|
|
3
|
+
export { d as StaticOverlayInput, e as staticOverlaySchema } from './scene-contract-NFnIcQtt.js';
|
|
4
|
+
|
|
5
|
+
declare function line(input: Omit<z.input<typeof lineOverlaySchema>, 'type'>): StaticOverlay;
|
|
6
|
+
declare function circle(input: Omit<z.input<typeof circleOverlaySchema>, 'type'>): StaticOverlay;
|
|
7
|
+
declare function marker(input: Omit<z.input<typeof markerOverlaySchema>, 'type'>): StaticOverlay;
|
|
8
|
+
declare function polygon(input: Omit<z.input<typeof polygonOverlaySchema>, 'type'>): StaticOverlay;
|
|
9
|
+
declare function symbol(input: Omit<z.input<typeof symbolOverlaySchema>, 'type'>): StaticOverlay;
|
|
10
|
+
declare function compileStaticOverlays(overlays: StaticOverlay[], options?: {
|
|
11
|
+
longitudeOffsets?: readonly number[];
|
|
12
|
+
}): {
|
|
13
|
+
layers: Record<string, unknown>[];
|
|
14
|
+
placements: Record<string, "above-buildings" | "above-labels" | "above-roads" | "above-water" | "below-labels" | "below-roads">;
|
|
15
|
+
sources: Record<string, Record<string, unknown>>;
|
|
16
|
+
symbols: {
|
|
17
|
+
imageId: string;
|
|
18
|
+
overlayIndex: number;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { StaticOverlay, circle, compileStaticOverlays, line, marker, polygon, symbol };
|
package/dist/overlays.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeStaticOverlay,
|
|
3
|
+
roundNumber,
|
|
4
|
+
staticOverlaySchema
|
|
5
|
+
} from "./chunk-6DWVCK64.js";
|
|
6
|
+
|
|
7
|
+
// src/overlays.ts
|
|
8
|
+
function line(input) {
|
|
9
|
+
return normalizeStaticOverlay({ ...input, type: "line" });
|
|
10
|
+
}
|
|
11
|
+
function circle(input) {
|
|
12
|
+
return normalizeStaticOverlay({ ...input, type: "circle" });
|
|
13
|
+
}
|
|
14
|
+
function marker(input) {
|
|
15
|
+
return normalizeStaticOverlay({ ...input, type: "marker" });
|
|
16
|
+
}
|
|
17
|
+
function polygon(input) {
|
|
18
|
+
return normalizeStaticOverlay({ ...input, type: "polygon" });
|
|
19
|
+
}
|
|
20
|
+
function symbol(input) {
|
|
21
|
+
return normalizeStaticOverlay({ ...input, type: "symbol" });
|
|
22
|
+
}
|
|
23
|
+
function compileStaticOverlays(overlays, options = {}) {
|
|
24
|
+
if (options.longitudeOffsets !== void 0 && (options.longitudeOffsets.length !== overlays.length || options.longitudeOffsets.some(
|
|
25
|
+
(offset) => !Number.isInteger(offset) || Math.abs(offset) > 360 || offset % 360 !== 0
|
|
26
|
+
))) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"Static overlay longitude offsets must contain one -360, 0, or 360 per overlay"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const sources = {};
|
|
32
|
+
const layers = [];
|
|
33
|
+
const placements = {};
|
|
34
|
+
const symbols = [];
|
|
35
|
+
const pushLayer = (layer, overlay) => {
|
|
36
|
+
const layerId = String(layer.id);
|
|
37
|
+
layers.push(layer);
|
|
38
|
+
placements[layerId] = overlay.placement ?? "above-labels";
|
|
39
|
+
};
|
|
40
|
+
for (const [index, overlay] of overlays.entries()) {
|
|
41
|
+
const longitudeOffset = options.longitudeOffsets?.[index] ?? 0;
|
|
42
|
+
const id = safeLayerId(`tileflow-static-overlay-${index + 1}-${overlay.id ?? overlay.type}`);
|
|
43
|
+
const sourceId = `${id}-source`;
|
|
44
|
+
if (overlay.type === "line") {
|
|
45
|
+
sources[sourceId] = {
|
|
46
|
+
data: feature("LineString", shiftLongitudes(overlay.coordinates, longitudeOffset)),
|
|
47
|
+
type: "geojson"
|
|
48
|
+
};
|
|
49
|
+
pushLayer(
|
|
50
|
+
{
|
|
51
|
+
id,
|
|
52
|
+
layout: { "line-cap": "round", "line-join": "round" },
|
|
53
|
+
paint: {
|
|
54
|
+
"line-color": overlay.color,
|
|
55
|
+
"line-opacity": overlay.opacity,
|
|
56
|
+
"line-width": overlay.width
|
|
57
|
+
},
|
|
58
|
+
source: sourceId,
|
|
59
|
+
type: "line"
|
|
60
|
+
},
|
|
61
|
+
overlay
|
|
62
|
+
);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (overlay.type === "polygon") {
|
|
66
|
+
sources[sourceId] = {
|
|
67
|
+
data: feature("Polygon", shiftLongitudes(overlay.coordinates, longitudeOffset)),
|
|
68
|
+
type: "geojson"
|
|
69
|
+
};
|
|
70
|
+
pushLayer(
|
|
71
|
+
{
|
|
72
|
+
id,
|
|
73
|
+
paint: {
|
|
74
|
+
"fill-color": overlay.fill,
|
|
75
|
+
"fill-opacity": overlay.opacity
|
|
76
|
+
},
|
|
77
|
+
source: sourceId,
|
|
78
|
+
type: "fill"
|
|
79
|
+
},
|
|
80
|
+
overlay
|
|
81
|
+
);
|
|
82
|
+
if (overlay.stroke && overlay.strokeWidth > 0) {
|
|
83
|
+
pushLayer(
|
|
84
|
+
{
|
|
85
|
+
id: `${id}-stroke`,
|
|
86
|
+
layout: { "line-cap": "round", "line-join": "round" },
|
|
87
|
+
paint: {
|
|
88
|
+
"line-color": overlay.stroke,
|
|
89
|
+
"line-width": overlay.strokeWidth
|
|
90
|
+
},
|
|
91
|
+
source: sourceId,
|
|
92
|
+
type: "line"
|
|
93
|
+
},
|
|
94
|
+
overlay
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
sources[sourceId] = {
|
|
100
|
+
data: feature("Point", shiftLongitudes(overlay.coordinate, longitudeOffset)),
|
|
101
|
+
type: "geojson"
|
|
102
|
+
};
|
|
103
|
+
if (overlay.type === "symbol") {
|
|
104
|
+
const composed = overlay.label !== void 0;
|
|
105
|
+
const imageId = composed ? safeLayerId(`__tileflow-static-symbol-${index + 1}-${overlay.id}`) : overlay.icon;
|
|
106
|
+
const alwaysVisible = overlay.collision === "always-visible";
|
|
107
|
+
const iconSize = composed ? 1 : overlay.scale;
|
|
108
|
+
const iconOffset = composed ? overlay.offset : overlay.offset.map((value) => roundNumber(value / overlay.scale));
|
|
109
|
+
pushLayer(
|
|
110
|
+
{
|
|
111
|
+
id,
|
|
112
|
+
layout: {
|
|
113
|
+
"icon-allow-overlap": alwaysVisible,
|
|
114
|
+
"icon-anchor": overlay.anchor,
|
|
115
|
+
"icon-ignore-placement": alwaysVisible,
|
|
116
|
+
"icon-image": imageId,
|
|
117
|
+
"icon-offset": iconOffset,
|
|
118
|
+
"icon-pitch-alignment": "viewport",
|
|
119
|
+
"icon-rotation-alignment": "viewport",
|
|
120
|
+
"icon-size": iconSize,
|
|
121
|
+
"symbol-placement": "point",
|
|
122
|
+
"symbol-z-order": "source"
|
|
123
|
+
},
|
|
124
|
+
paint: { "icon-opacity": overlay.opacity },
|
|
125
|
+
source: sourceId,
|
|
126
|
+
type: "symbol"
|
|
127
|
+
},
|
|
128
|
+
overlay
|
|
129
|
+
);
|
|
130
|
+
symbols.push({ imageId, overlayIndex: index });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
pushLayer(
|
|
134
|
+
{
|
|
135
|
+
id,
|
|
136
|
+
paint: {
|
|
137
|
+
"circle-color": overlay.color,
|
|
138
|
+
"circle-opacity": overlay.type === "circle" ? overlay.opacity : 1,
|
|
139
|
+
"circle-radius": overlay.radius,
|
|
140
|
+
"circle-stroke-color": overlay.strokeColor ?? overlay.color,
|
|
141
|
+
"circle-stroke-width": overlay.strokeWidth
|
|
142
|
+
},
|
|
143
|
+
source: sourceId,
|
|
144
|
+
type: "circle"
|
|
145
|
+
},
|
|
146
|
+
overlay
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return { layers, placements, sources, symbols };
|
|
150
|
+
}
|
|
151
|
+
function shiftLongitudes(value, offset) {
|
|
152
|
+
if (!Array.isArray(value)) return value;
|
|
153
|
+
if (value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number") {
|
|
154
|
+
return [roundNumber(value[0] + offset), value[1]];
|
|
155
|
+
}
|
|
156
|
+
return value.map((entry) => shiftLongitudes(entry, offset));
|
|
157
|
+
}
|
|
158
|
+
function feature(type, coordinates) {
|
|
159
|
+
return {
|
|
160
|
+
geometry: { coordinates, type },
|
|
161
|
+
properties: {},
|
|
162
|
+
type: "Feature"
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function safeLayerId(value) {
|
|
166
|
+
return value.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80);
|
|
167
|
+
}
|
|
168
|
+
export {
|
|
169
|
+
circle,
|
|
170
|
+
compileStaticOverlays,
|
|
171
|
+
line,
|
|
172
|
+
marker,
|
|
173
|
+
polygon,
|
|
174
|
+
staticOverlaySchema,
|
|
175
|
+
symbol
|
|
176
|
+
};
|