@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
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_OVERLAY_LATITUDE,
|
|
3
|
+
isSupportedStaticSymbolCodePoint,
|
|
4
|
+
jsonByteLength,
|
|
5
|
+
normalizeStaticCoordinate,
|
|
6
|
+
normalizeStaticOverlay,
|
|
7
|
+
roundNumber,
|
|
8
|
+
staticPaddingSchema,
|
|
9
|
+
staticSceneLimits,
|
|
10
|
+
staticSceneSchema,
|
|
11
|
+
stripUndefined
|
|
12
|
+
} from "./chunk-6DWVCK64.js";
|
|
13
|
+
|
|
14
|
+
// src/auto-fit.ts
|
|
15
|
+
import { z as z2 } from "zod";
|
|
16
|
+
|
|
17
|
+
// src/errors.ts
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
var safeErrorTextSchema = z.string().min(1).max(512).refine(hasNoControlCharacters, { message: "Expected no control characters" });
|
|
20
|
+
var safeErrorIdentifierSchema = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u);
|
|
21
|
+
var staticMapErrorResponseSchema = z.object({
|
|
22
|
+
code: safeErrorIdentifierSchema.optional(),
|
|
23
|
+
error: safeErrorTextSchema,
|
|
24
|
+
remainingUnits: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
25
|
+
requestId: safeErrorIdentifierSchema.optional(),
|
|
26
|
+
requiredPlan: safeErrorIdentifierSchema.optional(),
|
|
27
|
+
retryable: z.boolean().optional()
|
|
28
|
+
}).strip();
|
|
29
|
+
var StaticMapError = class extends Error {
|
|
30
|
+
code;
|
|
31
|
+
remainingUnits;
|
|
32
|
+
requestId;
|
|
33
|
+
requiredPlan;
|
|
34
|
+
response;
|
|
35
|
+
retryable;
|
|
36
|
+
status;
|
|
37
|
+
constructor(response, status) {
|
|
38
|
+
const parsed = staticMapErrorResponseSchema.parse(response);
|
|
39
|
+
super(parsed.error);
|
|
40
|
+
this.name = "StaticMapError";
|
|
41
|
+
this.code = parsed.code ?? null;
|
|
42
|
+
this.remainingUnits = parsed.remainingUnits ?? null;
|
|
43
|
+
this.requestId = parsed.requestId ?? null;
|
|
44
|
+
this.requiredPlan = parsed.requiredPlan ?? null;
|
|
45
|
+
this.response = Object.freeze(parsed);
|
|
46
|
+
this.retryable = parsed.retryable ?? null;
|
|
47
|
+
this.status = status;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function hasNoControlCharacters(value) {
|
|
51
|
+
return !Array.from(value).some((character) => {
|
|
52
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
53
|
+
return codePoint <= 31 || codePoint === 127;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/auto-fit.ts
|
|
58
|
+
var autoFitOverlayReferenceSchema = z2.object({
|
|
59
|
+
id: z2.string().min(1).max(64),
|
|
60
|
+
index: z2.number().int().min(0).max(23),
|
|
61
|
+
type: z2.enum(["circle", "line", "marker", "polygon", "symbol"])
|
|
62
|
+
}).strict();
|
|
63
|
+
var autoFitViewportSchema = z2.object({
|
|
64
|
+
height: z2.number().int().min(64).max(1280),
|
|
65
|
+
width: z2.number().int().min(64).max(1280)
|
|
66
|
+
}).strict();
|
|
67
|
+
var autoFitDetailsSchema = z2.object({
|
|
68
|
+
overlay: autoFitOverlayReferenceSchema.optional(),
|
|
69
|
+
padding: staticPaddingSchema.optional(),
|
|
70
|
+
projection: z2.enum(["globe", "mercator"]).optional(),
|
|
71
|
+
requiredInsets: staticPaddingSchema.optional(),
|
|
72
|
+
ringIndex: z2.number().int().min(0).max(15).optional(),
|
|
73
|
+
segmentIndex: z2.number().int().min(0).max(1999).optional(),
|
|
74
|
+
viewport: autoFitViewportSchema.optional()
|
|
75
|
+
}).strict();
|
|
76
|
+
var staticOverlayInvalidDetailsSchema = z2.object({
|
|
77
|
+
latitude: z2.number().finite(),
|
|
78
|
+
limit: z2.literal(MAX_OVERLAY_LATITUDE),
|
|
79
|
+
overlay: autoFitOverlayReferenceSchema
|
|
80
|
+
}).strict();
|
|
81
|
+
var staticLabelUnsupportedDetailsSchema = z2.object({
|
|
82
|
+
codePoint: z2.number().int().min(0).max(1114111),
|
|
83
|
+
overlay: autoFitOverlayReferenceSchema.extend({ type: z2.literal("symbol") }).strict()
|
|
84
|
+
}).strict();
|
|
85
|
+
var staticIconUnavailableDetailsSchema = z2.object({
|
|
86
|
+
icon: z2.string().max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u),
|
|
87
|
+
overlay: autoFitOverlayReferenceSchema.extend({ type: z2.literal("symbol") }).strict()
|
|
88
|
+
}).strict();
|
|
89
|
+
var staticSymbolTooLargeDetailsSchema = z2.object({
|
|
90
|
+
height: z2.number().finite().positive().max(16384),
|
|
91
|
+
limit: z2.literal(2048),
|
|
92
|
+
overlay: autoFitOverlayReferenceSchema.extend({ type: z2.literal("symbol") }).strict(),
|
|
93
|
+
width: z2.number().finite().positive().max(16384)
|
|
94
|
+
}).strict();
|
|
95
|
+
var autoFitErrorBase = {
|
|
96
|
+
error: z2.string().min(1).max(512),
|
|
97
|
+
retryable: z2.literal(false)
|
|
98
|
+
};
|
|
99
|
+
var staticMapRequestErrorResponseSchema = z2.discriminatedUnion("code", [
|
|
100
|
+
z2.object({
|
|
101
|
+
...autoFitErrorBase,
|
|
102
|
+
code: z2.literal("AUTO_FIT_EMPTY"),
|
|
103
|
+
reason: z2.literal("NO_FITTABLE_OVERLAYS")
|
|
104
|
+
}).strict(),
|
|
105
|
+
z2.object({
|
|
106
|
+
...autoFitErrorBase,
|
|
107
|
+
code: z2.literal("AUTO_FIT_AMBIGUOUS"),
|
|
108
|
+
details: autoFitDetailsSchema,
|
|
109
|
+
reason: z2.literal("ANTIMERIDIAN_WRAP_REQUIRED")
|
|
110
|
+
}).strict(),
|
|
111
|
+
z2.object({
|
|
112
|
+
...autoFitErrorBase,
|
|
113
|
+
code: z2.literal("AUTO_FIT_IMPOSSIBLE"),
|
|
114
|
+
details: autoFitDetailsSchema.optional(),
|
|
115
|
+
reason: z2.enum([
|
|
116
|
+
"GLOBE_NOT_SIMULTANEOUSLY_VISIBLE",
|
|
117
|
+
"INSUFFICIENT_VIEWPORT",
|
|
118
|
+
"CAMERA_UNRESOLVABLE",
|
|
119
|
+
"PROJECTED_FOOTPRINT_OUTSIDE_VIEWPORT"
|
|
120
|
+
])
|
|
121
|
+
}).strict(),
|
|
122
|
+
z2.object({
|
|
123
|
+
...autoFitErrorBase,
|
|
124
|
+
code: z2.literal("STATIC_OVERLAY_INVALID"),
|
|
125
|
+
details: staticOverlayInvalidDetailsSchema,
|
|
126
|
+
reason: z2.literal("OVERLAY_LATITUDE_OUT_OF_RANGE")
|
|
127
|
+
}).strict(),
|
|
128
|
+
z2.object({
|
|
129
|
+
...autoFitErrorBase,
|
|
130
|
+
code: z2.literal("STATIC_MAP_LABEL_UNSUPPORTED"),
|
|
131
|
+
details: staticLabelUnsupportedDetailsSchema,
|
|
132
|
+
reason: z2.literal("UNSUPPORTED_GLYPH")
|
|
133
|
+
}).strict(),
|
|
134
|
+
z2.object({
|
|
135
|
+
...autoFitErrorBase,
|
|
136
|
+
code: z2.literal("STATIC_MAP_ICON_UNAVAILABLE"),
|
|
137
|
+
details: staticIconUnavailableDetailsSchema,
|
|
138
|
+
reason: z2.enum(["ICON_NOT_FOUND", "MANAGED_SPRITE_REQUIRED"])
|
|
139
|
+
}).strict(),
|
|
140
|
+
z2.object({
|
|
141
|
+
...autoFitErrorBase,
|
|
142
|
+
code: z2.literal("STATIC_MAP_PLACEMENT_UNAVAILABLE"),
|
|
143
|
+
reason: z2.literal("MAP_REDEPLOY_REQUIRED")
|
|
144
|
+
}).strict(),
|
|
145
|
+
z2.object({
|
|
146
|
+
...autoFitErrorBase,
|
|
147
|
+
code: z2.literal("STATIC_MAP_SYMBOL_TOO_LARGE"),
|
|
148
|
+
details: staticSymbolTooLargeDetailsSchema,
|
|
149
|
+
reason: z2.literal("COMPOSED_IMAGE_TOO_LARGE")
|
|
150
|
+
}).strict(),
|
|
151
|
+
z2.object({
|
|
152
|
+
...autoFitErrorBase,
|
|
153
|
+
code: z2.literal("STATIC_MAP_ATTRIBUTION_UNSUPPORTED"),
|
|
154
|
+
reason: z2.enum(["UNSUPPORTED_DECLARATION", "UNSUPPORTED_GLYPH"])
|
|
155
|
+
}).strict(),
|
|
156
|
+
z2.object({
|
|
157
|
+
...autoFitErrorBase,
|
|
158
|
+
code: z2.literal("STATIC_MAP_ATTRIBUTION_TOO_LARGE"),
|
|
159
|
+
reason: z2.literal("CONTENT_LIMIT_EXCEEDED")
|
|
160
|
+
}).strict(),
|
|
161
|
+
z2.object({
|
|
162
|
+
...autoFitErrorBase,
|
|
163
|
+
code: z2.literal("STATIC_MAP_ATTRIBUTION_DOES_NOT_FIT"),
|
|
164
|
+
reason: z2.literal("BLOCK_DOES_NOT_FIT")
|
|
165
|
+
}).strict()
|
|
166
|
+
]);
|
|
167
|
+
var StaticMapRequestError = class extends StaticMapError {
|
|
168
|
+
code;
|
|
169
|
+
details;
|
|
170
|
+
reason;
|
|
171
|
+
retryable = false;
|
|
172
|
+
status;
|
|
173
|
+
constructor(response, status = 422) {
|
|
174
|
+
super(response, status);
|
|
175
|
+
this.name = "StaticMapRequestError";
|
|
176
|
+
this.code = response.code;
|
|
177
|
+
this.details = "details" in response ? response.details : void 0;
|
|
178
|
+
this.reason = response.reason;
|
|
179
|
+
this.status = status;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
var staticAutoFitPlanSchema = z2.object({
|
|
183
|
+
bounds: z2.tuple([
|
|
184
|
+
z2.number().finite(),
|
|
185
|
+
z2.number().finite().min(-90).max(90),
|
|
186
|
+
z2.number().finite(),
|
|
187
|
+
z2.number().finite().min(-90).max(90)
|
|
188
|
+
]),
|
|
189
|
+
longitudeOffsets: z2.array(z2.number().int().min(-360).max(360)).max(24),
|
|
190
|
+
nominalInsets: staticPaddingSchema,
|
|
191
|
+
requiredInsets: staticPaddingSchema
|
|
192
|
+
}).strict();
|
|
193
|
+
function analyzeStaticAutoFit(scene) {
|
|
194
|
+
if (scene.camera.type !== "auto") {
|
|
195
|
+
throw new Error("Static auto-fit analysis requires an auto camera");
|
|
196
|
+
}
|
|
197
|
+
if (scene.overlays.length === 0) {
|
|
198
|
+
return failure({
|
|
199
|
+
code: "AUTO_FIT_EMPTY",
|
|
200
|
+
error: "Auto-fit requires at least one overlay",
|
|
201
|
+
reason: "NO_FITTABLE_OVERLAYS",
|
|
202
|
+
retryable: false
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const components = [];
|
|
206
|
+
let south = Number.POSITIVE_INFINITY;
|
|
207
|
+
let north = Number.NEGATIVE_INFINITY;
|
|
208
|
+
let nominalExpansion = 0;
|
|
209
|
+
for (const [overlayIndex, overlay] of scene.overlays.entries()) {
|
|
210
|
+
const coordinates = overlayCoordinates(overlay);
|
|
211
|
+
const ambiguity = findAmbiguousSegment(overlay);
|
|
212
|
+
if (ambiguity) {
|
|
213
|
+
return failure({
|
|
214
|
+
code: "AUTO_FIT_AMBIGUOUS",
|
|
215
|
+
details: {
|
|
216
|
+
overlay: {
|
|
217
|
+
id: overlay.id ?? `overlay-${overlayIndex + 1}`,
|
|
218
|
+
index: overlayIndex,
|
|
219
|
+
type: overlay.type
|
|
220
|
+
},
|
|
221
|
+
...ambiguity.ringIndex === void 0 ? {} : { ringIndex: ambiguity.ringIndex },
|
|
222
|
+
segmentIndex: ambiguity.segmentIndex
|
|
223
|
+
},
|
|
224
|
+
error: `Overlay overlays.${overlayIndex} requires an explicit antimeridian wrap decision`,
|
|
225
|
+
reason: "ANTIMERIDIAN_WRAP_REQUIRED",
|
|
226
|
+
retryable: false
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const longitudes = coordinates.map(([longitude]) => longitude);
|
|
230
|
+
components.push({
|
|
231
|
+
east: Math.max(...longitudes),
|
|
232
|
+
overlayIndex,
|
|
233
|
+
west: Math.min(...longitudes)
|
|
234
|
+
});
|
|
235
|
+
for (const [, latitude] of coordinates) {
|
|
236
|
+
south = Math.min(south, latitude);
|
|
237
|
+
north = Math.max(north, latitude);
|
|
238
|
+
}
|
|
239
|
+
nominalExpansion = Math.max(nominalExpansion, nominalOverlayExpansion(overlay));
|
|
240
|
+
}
|
|
241
|
+
const longitudePlan = fitLongitudeComponents(components);
|
|
242
|
+
const nominalInset = Math.ceil(nominalExpansion);
|
|
243
|
+
const nominalInsets = allSides(nominalInset);
|
|
244
|
+
const padding = normalizedPadding(scene.camera.padding, scene.size);
|
|
245
|
+
if ([padding.bottom, padding.left, padding.right, padding.top].some(
|
|
246
|
+
(value) => !Number.isSafeInteger(value + nominalInset)
|
|
247
|
+
)) {
|
|
248
|
+
return failure({
|
|
249
|
+
code: "AUTO_FIT_IMPOSSIBLE",
|
|
250
|
+
details: {
|
|
251
|
+
padding,
|
|
252
|
+
viewport: { height: scene.size.height, width: scene.size.width }
|
|
253
|
+
},
|
|
254
|
+
error: "Auto-fit nominal footprint leaves no usable viewport",
|
|
255
|
+
reason: "INSUFFICIENT_VIEWPORT",
|
|
256
|
+
retryable: false
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const requiredInsets = {
|
|
260
|
+
bottom: padding.bottom + nominalInset,
|
|
261
|
+
left: padding.left + nominalInset,
|
|
262
|
+
right: padding.right + nominalInset,
|
|
263
|
+
top: padding.top + nominalInset
|
|
264
|
+
};
|
|
265
|
+
if (requiredInsets.left + requiredInsets.right >= scene.size.width || requiredInsets.top + requiredInsets.bottom >= scene.size.height) {
|
|
266
|
+
return failure({
|
|
267
|
+
code: "AUTO_FIT_IMPOSSIBLE",
|
|
268
|
+
details: {
|
|
269
|
+
padding,
|
|
270
|
+
requiredInsets,
|
|
271
|
+
viewport: { height: scene.size.height, width: scene.size.width }
|
|
272
|
+
},
|
|
273
|
+
error: "Auto-fit nominal footprint leaves no usable viewport",
|
|
274
|
+
reason: "INSUFFICIENT_VIEWPORT",
|
|
275
|
+
retryable: false
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
plan: {
|
|
281
|
+
bounds: [
|
|
282
|
+
roundNumber(longitudePlan.west),
|
|
283
|
+
roundNumber(south),
|
|
284
|
+
roundNumber(longitudePlan.east),
|
|
285
|
+
roundNumber(north)
|
|
286
|
+
],
|
|
287
|
+
longitudeOffsets: longitudePlan.offsets,
|
|
288
|
+
nominalInsets,
|
|
289
|
+
requiredInsets
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function failure(response) {
|
|
294
|
+
return { ...response, ok: false };
|
|
295
|
+
}
|
|
296
|
+
function overlayCoordinates(overlay) {
|
|
297
|
+
if (overlay.type === "circle" || overlay.type === "marker" || overlay.type === "symbol") {
|
|
298
|
+
return [overlay.coordinate];
|
|
299
|
+
}
|
|
300
|
+
if (overlay.type === "line") {
|
|
301
|
+
return overlay.coordinates;
|
|
302
|
+
}
|
|
303
|
+
return overlay.coordinates.flat();
|
|
304
|
+
}
|
|
305
|
+
function findAmbiguousSegment(overlay) {
|
|
306
|
+
if (overlay.type === "circle" || overlay.type === "marker" || overlay.type === "symbol")
|
|
307
|
+
return null;
|
|
308
|
+
const paths = overlay.type === "line" ? [overlay.coordinates] : overlay.coordinates;
|
|
309
|
+
for (const [ringIndex, path] of paths.entries()) {
|
|
310
|
+
for (let segmentIndex = 0; segmentIndex < path.length - 1; segmentIndex += 1) {
|
|
311
|
+
const start = path[segmentIndex];
|
|
312
|
+
const end = path[segmentIndex + 1];
|
|
313
|
+
if (start && end && Math.abs(end[0] - start[0]) >= 180) {
|
|
314
|
+
return {
|
|
315
|
+
...overlay.type === "polygon" ? { ringIndex } : {},
|
|
316
|
+
segmentIndex
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
function nominalOverlayExpansion(overlay) {
|
|
324
|
+
if (overlay.type === "circle" || overlay.type === "marker") {
|
|
325
|
+
return overlay.radius + overlay.strokeWidth;
|
|
326
|
+
}
|
|
327
|
+
if (overlay.type === "symbol") return 0;
|
|
328
|
+
if (overlay.type === "line") {
|
|
329
|
+
return overlay.width / 2 + 0.5;
|
|
330
|
+
}
|
|
331
|
+
return overlay.stroke && overlay.strokeWidth > 0 ? overlay.strokeWidth / 2 + 0.5 : 0;
|
|
332
|
+
}
|
|
333
|
+
function fitLongitudeComponents(components) {
|
|
334
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
335
|
+
for (const component of components) {
|
|
336
|
+
candidates.add(toCircle(component.west));
|
|
337
|
+
candidates.add(toCircle(component.east));
|
|
338
|
+
}
|
|
339
|
+
let best;
|
|
340
|
+
for (const cut of [...candidates].sort((left, right) => left - right)) {
|
|
341
|
+
const assignedStarts = [];
|
|
342
|
+
let west = Number.POSITIVE_INFINITY;
|
|
343
|
+
let east = Number.NEGATIVE_INFINITY;
|
|
344
|
+
let valid = true;
|
|
345
|
+
for (const component of components) {
|
|
346
|
+
const width = component.east - component.west;
|
|
347
|
+
let start = toCircle(component.west);
|
|
348
|
+
if (start < cut) start += 360;
|
|
349
|
+
const end = start + width;
|
|
350
|
+
if (end > cut + 360 + 1e-9) {
|
|
351
|
+
valid = false;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
assignedStarts.push(start);
|
|
355
|
+
west = Math.min(west, start);
|
|
356
|
+
east = Math.max(east, end);
|
|
357
|
+
}
|
|
358
|
+
if (!valid) continue;
|
|
359
|
+
const span = east - west;
|
|
360
|
+
const canonicalWest2 = canonicalLongitude(west - 180);
|
|
361
|
+
const bestCanonicalWest = best ? canonicalLongitude(best.west - 180) : Number.POSITIVE_INFINITY;
|
|
362
|
+
if (!best || span < best.span - 1e-9 || Math.abs(span - best.span) <= 1e-9 && canonicalWest2 < bestCanonicalWest) {
|
|
363
|
+
best = { assignedStarts, east, span, west };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (!best) {
|
|
367
|
+
const west = Math.min(...components.map((component) => component.west));
|
|
368
|
+
const east = Math.max(...components.map((component) => component.east));
|
|
369
|
+
return { east, offsets: components.map(() => 0), west };
|
|
370
|
+
}
|
|
371
|
+
const unshiftedWest = best.west - 180;
|
|
372
|
+
const canonicalWest = canonicalLongitude(unshiftedWest);
|
|
373
|
+
const globalOffset = canonicalWest - unshiftedWest;
|
|
374
|
+
const offsets = components.map((component, index) => {
|
|
375
|
+
const assignedLongitude = (best?.assignedStarts[index] ?? 0) - 180 + globalOffset;
|
|
376
|
+
return Math.round(assignedLongitude - component.west);
|
|
377
|
+
});
|
|
378
|
+
return {
|
|
379
|
+
east: canonicalWest + best.span,
|
|
380
|
+
offsets,
|
|
381
|
+
west: canonicalWest
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function toCircle(longitude) {
|
|
385
|
+
const value = (longitude + 180) % 360;
|
|
386
|
+
return value < 0 ? value + 360 : value;
|
|
387
|
+
}
|
|
388
|
+
function canonicalLongitude(longitude) {
|
|
389
|
+
const value = ((longitude + 180) % 360 + 360) % 360;
|
|
390
|
+
return value - 180;
|
|
391
|
+
}
|
|
392
|
+
function allSides(value) {
|
|
393
|
+
return { bottom: value, left: value, right: value, top: value };
|
|
394
|
+
}
|
|
395
|
+
function normalizedPadding(padding, size) {
|
|
396
|
+
if (padding === void 0) {
|
|
397
|
+
return allSides(Math.min(32, Math.ceil(Math.min(size.width, size.height) * 0.05)));
|
|
398
|
+
}
|
|
399
|
+
if (typeof padding === "number") return allSides(padding);
|
|
400
|
+
return {
|
|
401
|
+
bottom: padding.bottom ?? 0,
|
|
402
|
+
left: padding.left ?? 0,
|
|
403
|
+
right: padding.right ?? 0,
|
|
404
|
+
top: padding.top ?? 0
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function autoFitErrorResponse(failure2) {
|
|
408
|
+
const { ok: _ok, ...response } = failure2;
|
|
409
|
+
return response;
|
|
410
|
+
}
|
|
411
|
+
function findStaticOverlayLatitudeFailure(input) {
|
|
412
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
413
|
+
const overlays = input.overlays;
|
|
414
|
+
if (!Array.isArray(overlays)) return null;
|
|
415
|
+
for (const [index, candidate] of overlays.entries()) {
|
|
416
|
+
if (index >= staticSceneLimits.maxOverlays) break;
|
|
417
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
|
|
418
|
+
const overlay = candidate;
|
|
419
|
+
const type = overlay.type;
|
|
420
|
+
if (!["circle", "line", "marker", "polygon", "symbol"].includes(String(type))) continue;
|
|
421
|
+
const coordinates = type === "circle" || type === "marker" || type === "symbol" ? [overlay.coordinate] : overlayCoordinateCandidates(type, overlay.coordinates);
|
|
422
|
+
for (const coordinate of coordinates) {
|
|
423
|
+
if (!Array.isArray(coordinate) || coordinate.length !== 2 || typeof coordinate[1] !== "number" || !Number.isFinite(coordinate[1]) || Math.abs(coordinate[1]) <= MAX_OVERLAY_LATITUDE) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
const id = typeof overlay.id === "string" && overlay.id.trim() ? overlay.id.trim() : `overlay-${index + 1}`;
|
|
427
|
+
const response = {
|
|
428
|
+
code: "STATIC_OVERLAY_INVALID",
|
|
429
|
+
details: {
|
|
430
|
+
latitude: coordinate[1],
|
|
431
|
+
limit: MAX_OVERLAY_LATITUDE,
|
|
432
|
+
overlay: {
|
|
433
|
+
id: id.slice(0, 64),
|
|
434
|
+
index,
|
|
435
|
+
type
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
error: `Overlay overlays.${index} latitude ${coordinate[1]} exceeds the supported \xB1${MAX_OVERLAY_LATITUDE}\xB0 range`,
|
|
439
|
+
reason: "OVERLAY_LATITUDE_OUT_OF_RANGE",
|
|
440
|
+
retryable: false
|
|
441
|
+
};
|
|
442
|
+
return { ...response, ok: false };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
function findStaticSymbolLabelGlyphFailure(input) {
|
|
448
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
449
|
+
const overlays = input.overlays;
|
|
450
|
+
if (!Array.isArray(overlays)) return null;
|
|
451
|
+
for (const [index, candidate] of overlays.entries()) {
|
|
452
|
+
if (index >= staticSceneLimits.maxOverlays) break;
|
|
453
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
|
|
454
|
+
const overlay = candidate;
|
|
455
|
+
if (overlay.type !== "symbol") continue;
|
|
456
|
+
const label = overlay.label;
|
|
457
|
+
const text = typeof label === "string" ? label : label && typeof label === "object" && !Array.isArray(label) ? label.text : void 0;
|
|
458
|
+
if (typeof text !== "string") continue;
|
|
459
|
+
const unsupported = Array.from(text).find(
|
|
460
|
+
(character) => !isSupportedStaticSymbolCodePoint(character.codePointAt(0))
|
|
461
|
+
);
|
|
462
|
+
if (!unsupported) continue;
|
|
463
|
+
const codePoint = unsupported.codePointAt(0);
|
|
464
|
+
const id = typeof overlay.id === "string" && overlay.id.trim() ? overlay.id.trim() : `overlay-${index + 1}`;
|
|
465
|
+
return {
|
|
466
|
+
code: "STATIC_MAP_LABEL_UNSUPPORTED",
|
|
467
|
+
details: {
|
|
468
|
+
codePoint,
|
|
469
|
+
overlay: { id: id.slice(0, 64), index, type: "symbol" }
|
|
470
|
+
},
|
|
471
|
+
error: `Overlay overlays.${index} label contains unsupported glyph U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}`,
|
|
472
|
+
ok: false,
|
|
473
|
+
reason: "UNSUPPORTED_GLYPH",
|
|
474
|
+
retryable: false
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
function overlayCoordinateCandidates(type, value) {
|
|
480
|
+
if (!Array.isArray(value)) return [];
|
|
481
|
+
if (type === "line") {
|
|
482
|
+
return value.slice(0, staticSceneLimits.maxPathCoordinates);
|
|
483
|
+
}
|
|
484
|
+
return value.slice(0, 16).flatMap(
|
|
485
|
+
(ring) => Array.isArray(ring) ? ring.slice(0, staticSceneLimits.maxPathCoordinates) : []
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/scene.ts
|
|
490
|
+
function validateStaticScene(input) {
|
|
491
|
+
const overlayLatitudeFailure = findStaticOverlayLatitudeFailure(input);
|
|
492
|
+
if (overlayLatitudeFailure) return overlayLatitudeFailure;
|
|
493
|
+
const labelGlyphFailure = findStaticSymbolLabelGlyphFailure(input);
|
|
494
|
+
if (labelGlyphFailure) return labelGlyphFailure;
|
|
495
|
+
const parsed = staticSceneSchema.safeParse(input);
|
|
496
|
+
if (!parsed.success) {
|
|
497
|
+
return {
|
|
498
|
+
error: parsed.error.issues.map((issue) => `${issue.path.join(".") || "scene"}: ${issue.message}`).join("; "),
|
|
499
|
+
ok: false
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (jsonByteLength(parsed.data.overlays) > staticSceneLimits.maxGeoJsonBytes) {
|
|
503
|
+
return {
|
|
504
|
+
error: `overlays exceed ${staticSceneLimits.maxGeoJsonBytes} bytes`,
|
|
505
|
+
ok: false
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
const scene = normalizeParsedStaticScene(parsed.data);
|
|
509
|
+
if (scene.camera.type === "auto") {
|
|
510
|
+
const analysis = analyzeStaticAutoFit(scene);
|
|
511
|
+
if (!analysis.ok) return analysis;
|
|
512
|
+
}
|
|
513
|
+
return { ok: true, scene };
|
|
514
|
+
}
|
|
515
|
+
function normalizeStaticScene(scene) {
|
|
516
|
+
const overlayLatitudeFailure = findStaticOverlayLatitudeFailure(scene);
|
|
517
|
+
if (overlayLatitudeFailure) {
|
|
518
|
+
const { ok: _ok, ...response } = overlayLatitudeFailure;
|
|
519
|
+
throw new StaticMapRequestError(response);
|
|
520
|
+
}
|
|
521
|
+
const labelGlyphFailure = findStaticSymbolLabelGlyphFailure(scene);
|
|
522
|
+
if (labelGlyphFailure) {
|
|
523
|
+
const { ok: _ok, ...response } = labelGlyphFailure;
|
|
524
|
+
throw new StaticMapRequestError(response);
|
|
525
|
+
}
|
|
526
|
+
const normalized = normalizeParsedStaticScene(staticSceneSchema.parse(scene));
|
|
527
|
+
if (normalized.camera.type === "auto") {
|
|
528
|
+
const analysis = analyzeStaticAutoFit(normalized);
|
|
529
|
+
if (!analysis.ok) {
|
|
530
|
+
throw new StaticMapRequestError(autoFitErrorResponse(analysis));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return normalized;
|
|
534
|
+
}
|
|
535
|
+
function normalizeParsedStaticScene(parsed) {
|
|
536
|
+
return stripUndefined({
|
|
537
|
+
...parsed.attribution && Object.keys(parsed.attribution).length > 0 ? {
|
|
538
|
+
attribution: parsed.attribution.mode === "external" ? { mode: "external" } : stripUndefined({
|
|
539
|
+
mode: parsed.attribution.mode,
|
|
540
|
+
position: parsed.attribution.position
|
|
541
|
+
})
|
|
542
|
+
} : {},
|
|
543
|
+
camera: parsed.camera.type === "center" ? {
|
|
544
|
+
bearing: roundNumber(parsed.camera.bearing ?? 0),
|
|
545
|
+
center: normalizeStaticCoordinate(parsed.camera.center),
|
|
546
|
+
type: "center",
|
|
547
|
+
zoom: roundNumber(parsed.camera.zoom)
|
|
548
|
+
} : parsed.camera.type === "bounds" ? {
|
|
549
|
+
bearing: roundNumber(parsed.camera.bearing ?? 0),
|
|
550
|
+
bounds: parsed.camera.bounds.map(roundNumber),
|
|
551
|
+
padding: parsed.camera.padding ?? 32,
|
|
552
|
+
type: "bounds"
|
|
553
|
+
} : {
|
|
554
|
+
bearing: roundNumber(parsed.camera.bearing ?? 0),
|
|
555
|
+
maxZoom: roundNumber(parsed.camera.maxZoom ?? 16),
|
|
556
|
+
padding: normalizeAutoPadding(parsed.camera.padding, parsed.size),
|
|
557
|
+
type: "auto"
|
|
558
|
+
},
|
|
559
|
+
...parsed.format && parsed.format !== "png" ? { format: parsed.format } : {},
|
|
560
|
+
map: parsed.map,
|
|
561
|
+
overlays: parsed.overlays.map(
|
|
562
|
+
(overlay, index) => normalizeStaticOverlay({
|
|
563
|
+
...overlay,
|
|
564
|
+
id: overlay.id ?? `overlay-${index + 1}`
|
|
565
|
+
})
|
|
566
|
+
),
|
|
567
|
+
size: {
|
|
568
|
+
dpr: parsed.size.dpr ?? 1,
|
|
569
|
+
height: parsed.size.height,
|
|
570
|
+
width: parsed.size.width
|
|
571
|
+
},
|
|
572
|
+
theme: parsed.theme
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
function normalizeAutoPadding(padding, size) {
|
|
576
|
+
if (padding === void 0) {
|
|
577
|
+
const value = Math.min(32, Math.ceil(Math.min(size.width, size.height) * 0.05));
|
|
578
|
+
return { bottom: value, left: value, right: value, top: value };
|
|
579
|
+
}
|
|
580
|
+
if (typeof padding === "number") {
|
|
581
|
+
return { bottom: padding, left: padding, right: padding, top: padding };
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
bottom: padding.bottom ?? 0,
|
|
585
|
+
left: padding.left ?? 0,
|
|
586
|
+
right: padding.right ?? 0,
|
|
587
|
+
top: padding.top ?? 0
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export {
|
|
592
|
+
staticMapErrorResponseSchema,
|
|
593
|
+
StaticMapError,
|
|
594
|
+
staticMapRequestErrorResponseSchema,
|
|
595
|
+
StaticMapRequestError,
|
|
596
|
+
staticAutoFitPlanSchema,
|
|
597
|
+
analyzeStaticAutoFit,
|
|
598
|
+
validateStaticScene,
|
|
599
|
+
normalizeStaticScene
|
|
600
|
+
};
|