@tileflow/static 0.1.0-alpha.22 → 0.1.0-alpha.24

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.
@@ -0,0 +1,175 @@
1
+ import {
2
+ normalizeStaticOverlay,
3
+ roundNumber
4
+ } from "./chunk-6DWVCK64.js";
5
+
6
+ // src/overlays.ts
7
+ function line(input) {
8
+ return normalizeStaticOverlay({ ...input, type: "line" });
9
+ }
10
+ function circle(input) {
11
+ return normalizeStaticOverlay({ ...input, type: "circle" });
12
+ }
13
+ function marker(input) {
14
+ return normalizeStaticOverlay({ ...input, type: "marker" });
15
+ }
16
+ function polygon(input) {
17
+ return normalizeStaticOverlay({ ...input, type: "polygon" });
18
+ }
19
+ function symbol(input) {
20
+ return normalizeStaticOverlay({ ...input, type: "symbol" });
21
+ }
22
+ function compileStaticOverlays(overlays, options = {}) {
23
+ if (options.longitudeOffsets !== void 0 && (options.longitudeOffsets.length !== overlays.length || options.longitudeOffsets.some(
24
+ (offset) => !Number.isInteger(offset) || Math.abs(offset) > 360 || offset % 360 !== 0
25
+ ))) {
26
+ throw new Error(
27
+ "Static overlay longitude offsets must contain one -360, 0, or 360 per overlay"
28
+ );
29
+ }
30
+ const sources = {};
31
+ const layers = [];
32
+ const placements = {};
33
+ const symbols = [];
34
+ const pushLayer = (layer, overlay) => {
35
+ const layerId = String(layer.id);
36
+ layers.push(layer);
37
+ placements[layerId] = overlay.placement ?? "above-labels";
38
+ };
39
+ for (const [index, overlay] of overlays.entries()) {
40
+ const longitudeOffset = options.longitudeOffsets?.[index] ?? 0;
41
+ const id = safeLayerId(`tileflow-static-overlay-${index + 1}-${overlay.id ?? overlay.type}`);
42
+ const sourceId = `${id}-source`;
43
+ if (overlay.type === "line") {
44
+ sources[sourceId] = {
45
+ data: feature("LineString", shiftLongitudes(overlay.coordinates, longitudeOffset)),
46
+ type: "geojson"
47
+ };
48
+ pushLayer(
49
+ {
50
+ id,
51
+ layout: { "line-cap": "round", "line-join": "round" },
52
+ paint: {
53
+ "line-color": overlay.color,
54
+ "line-opacity": overlay.opacity,
55
+ "line-width": overlay.width
56
+ },
57
+ source: sourceId,
58
+ type: "line"
59
+ },
60
+ overlay
61
+ );
62
+ continue;
63
+ }
64
+ if (overlay.type === "polygon") {
65
+ sources[sourceId] = {
66
+ data: feature("Polygon", shiftLongitudes(overlay.coordinates, longitudeOffset)),
67
+ type: "geojson"
68
+ };
69
+ pushLayer(
70
+ {
71
+ id,
72
+ paint: {
73
+ "fill-color": overlay.fill,
74
+ "fill-opacity": overlay.opacity
75
+ },
76
+ source: sourceId,
77
+ type: "fill"
78
+ },
79
+ overlay
80
+ );
81
+ if (overlay.stroke && overlay.strokeWidth > 0) {
82
+ pushLayer(
83
+ {
84
+ id: `${id}-stroke`,
85
+ layout: { "line-cap": "round", "line-join": "round" },
86
+ paint: {
87
+ "line-color": overlay.stroke,
88
+ "line-width": overlay.strokeWidth
89
+ },
90
+ source: sourceId,
91
+ type: "line"
92
+ },
93
+ overlay
94
+ );
95
+ }
96
+ continue;
97
+ }
98
+ sources[sourceId] = {
99
+ data: feature("Point", shiftLongitudes(overlay.coordinate, longitudeOffset)),
100
+ type: "geojson"
101
+ };
102
+ if (overlay.type === "symbol") {
103
+ const composed = overlay.label !== void 0;
104
+ const imageId = composed ? safeLayerId(`__tileflow-static-symbol-${index + 1}-${overlay.id}`) : overlay.icon;
105
+ const alwaysVisible = overlay.collision === "always-visible";
106
+ const iconSize = composed ? 1 : overlay.scale;
107
+ const iconOffset = composed ? overlay.offset : overlay.offset.map((value) => roundNumber(value / overlay.scale));
108
+ pushLayer(
109
+ {
110
+ id,
111
+ layout: {
112
+ "icon-allow-overlap": alwaysVisible,
113
+ "icon-anchor": overlay.anchor,
114
+ "icon-ignore-placement": alwaysVisible,
115
+ "icon-image": imageId,
116
+ "icon-offset": iconOffset,
117
+ "icon-pitch-alignment": "viewport",
118
+ "icon-rotation-alignment": "viewport",
119
+ "icon-size": iconSize,
120
+ "symbol-placement": "point",
121
+ "symbol-z-order": "source"
122
+ },
123
+ paint: { "icon-opacity": overlay.opacity },
124
+ source: sourceId,
125
+ type: "symbol"
126
+ },
127
+ overlay
128
+ );
129
+ symbols.push({ imageId, overlayIndex: index });
130
+ continue;
131
+ }
132
+ pushLayer(
133
+ {
134
+ id,
135
+ paint: {
136
+ "circle-color": overlay.color,
137
+ "circle-opacity": overlay.type === "circle" ? overlay.opacity : 1,
138
+ "circle-radius": overlay.radius,
139
+ "circle-stroke-color": overlay.strokeColor ?? overlay.color,
140
+ "circle-stroke-width": overlay.strokeWidth
141
+ },
142
+ source: sourceId,
143
+ type: "circle"
144
+ },
145
+ overlay
146
+ );
147
+ }
148
+ return { layers, placements, sources, symbols };
149
+ }
150
+ function shiftLongitudes(value, offset) {
151
+ if (!Array.isArray(value)) return value;
152
+ if (value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number") {
153
+ return [roundNumber(value[0] + offset), value[1]];
154
+ }
155
+ return value.map((entry) => shiftLongitudes(entry, offset));
156
+ }
157
+ function feature(type, coordinates) {
158
+ return {
159
+ geometry: { coordinates, type },
160
+ properties: {},
161
+ type: "Feature"
162
+ };
163
+ }
164
+ function safeLayerId(value) {
165
+ return value.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 80);
166
+ }
167
+
168
+ export {
169
+ line,
170
+ circle,
171
+ marker,
172
+ polygon,
173
+ symbol,
174
+ compileStaticOverlays
175
+ };
@@ -1,21 +1,23 @@
1
1
  import {
2
+ StaticMapError,
2
3
  StaticMapRequestError,
4
+ staticMapErrorResponseSchema,
3
5
  staticMapRequestErrorResponseSchema,
4
6
  validateStaticScene
5
- } from "./chunk-EMCKSFZV.js";
7
+ } from "./chunk-TBI5AED6.js";
6
8
  import {
7
9
  isSafeHttpUrl,
8
10
  jsonByteLength,
9
11
  stableStringify
10
- } from "./chunk-NM2JSFUF.js";
12
+ } from "./chunk-6DWVCK64.js";
11
13
 
12
14
  // src/client.ts
13
15
  import { z } from "zod";
14
16
  var maxStaticMapResponseBytes = 64 * 1024;
15
17
  var staticMapIdempotencyKeyPattern = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
16
- var STATIC_MAP_RESULT_V2_MEDIA_TYPE = "application/vnd.tileflow.static-map-result+json;version=2";
18
+ var STATIC_MAP_RESULT_MEDIA_TYPE = "application/vnd.tileflow.static-map-result+json;version=1";
17
19
  var preparedStaticMapRequestBodies = /* @__PURE__ */ new WeakMap();
18
- var staticMapReadyResultSchema = z.object({
20
+ var staticMapReadyResultBaseSchema = z.object({
19
21
  cached: z.boolean(),
20
22
  hash: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
21
23
  imageUrl: z.string().trim().url().max(2048).refine(isSafeHttpUrl, { message: "Expected an http(s) URL without credentials" }),
@@ -53,9 +55,9 @@ var staticMapAttributionResultSchema = z.object({
53
55
  context.addIssue({ code: "custom", message: "Attribution result exceeds 32 KiB" });
54
56
  }
55
57
  });
56
- var staticMapHostedResultSchema = staticMapReadyResultSchema.extend({
58
+ var staticMapHostedResultSchema = staticMapReadyResultBaseSchema.extend({
57
59
  attribution: staticMapAttributionResultSchema,
58
- resultVersion: z.literal(2)
60
+ resultVersion: z.literal(1)
59
61
  }).strict();
60
62
  var staticMapProcessingResultSchema = z.object({
61
63
  operationId: z.string().min(20).max(68),
@@ -127,7 +129,7 @@ async function requestStaticMapUntilReady(request, options) {
127
129
  const fetcher = options.fetch ?? fetch;
128
130
  const createUrl = normalizeStaticMapEndpointUrl(options.createUrl);
129
131
  const headers = {
130
- Accept: STATIC_MAP_RESULT_V2_MEDIA_TYPE,
132
+ Accept: STATIC_MAP_RESULT_MEDIA_TYPE,
131
133
  "Content-Type": "application/json",
132
134
  "Idempotency-Key": idempotency.key
133
135
  };
@@ -145,10 +147,17 @@ async function requestStaticMapUntilReady(request, options) {
145
147
  });
146
148
  throwIfAborted(signal);
147
149
  if (!response.ok) {
148
- const requestError = response.status === 422 ? await readStaticMapRequestErrorResponse(response, 8 * 1024) : await discardBoundedResponse(response, 8 * 1024);
150
+ const errorBody = await readStaticMapErrorBody(response, 8 * 1024);
149
151
  throwIfAborted(signal);
150
- if (requestError) {
151
- throw new StaticMapRequestError(requestError, response.status);
152
+ if (response.status === 422) {
153
+ const requestError = staticMapRequestErrorResponseSchema.safeParse(errorBody);
154
+ if (requestError.success) {
155
+ throw new StaticMapRequestError(requestError.data, response.status);
156
+ }
157
+ }
158
+ const structured = staticMapErrorResponseSchema.safeParse(errorBody);
159
+ if (structured.success) {
160
+ throw new StaticMapError(structured.data, response.status);
152
161
  }
153
162
  throw new Error(`Tileflow static map failed (${response.status}).`);
154
163
  }
@@ -199,22 +208,14 @@ async function readJsonResponse(response) {
199
208
  throw new Error("Tileflow static map returned an invalid response: expected JSON");
200
209
  }
201
210
  }
202
- async function readStaticMapRequestErrorResponse(response, maximumBytes) {
211
+ async function readStaticMapErrorBody(response, maximumBytes) {
203
212
  try {
204
213
  const source = await readBoundedResponseText(response, maximumBytes, `${maximumBytes} bytes`);
205
- const parsed = staticMapRequestErrorResponseSchema.safeParse(JSON.parse(source));
206
- return parsed.success ? parsed.data : null;
214
+ return JSON.parse(source);
207
215
  } catch {
208
216
  return null;
209
217
  }
210
218
  }
211
- async function discardBoundedResponse(response, maximumBytes) {
212
- try {
213
- await readBoundedResponseText(response, maximumBytes, `${maximumBytes} bytes`);
214
- } catch {
215
- }
216
- return null;
217
- }
218
219
  function isStaticMapRequestFailure(value) {
219
220
  if (!("code" in value)) return false;
220
221
  const { ok: _ok, ...response } = value;
@@ -359,8 +360,7 @@ function hasControlCharacter(value) {
359
360
  }
360
361
 
361
362
  export {
362
- STATIC_MAP_RESULT_V2_MEDIA_TYPE,
363
- staticMapReadyResultSchema,
363
+ STATIC_MAP_RESULT_MEDIA_TYPE,
364
364
  staticMapAttributionEntrySchema,
365
365
  staticMapAttributionResultSchema,
366
366
  staticMapHostedResultSchema,
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  MAX_OVERLAY_LATITUDE,
3
+ isSupportedStaticSymbolCodePoint,
3
4
  jsonByteLength,
4
5
  normalizeStaticCoordinate,
5
6
  normalizeStaticOverlay,
@@ -8,90 +9,166 @@ import {
8
9
  staticSceneLimits,
9
10
  staticSceneSchema,
10
11
  stripUndefined
11
- } from "./chunk-NM2JSFUF.js";
12
+ } from "./chunk-6DWVCK64.js";
12
13
 
13
14
  // src/auto-fit.ts
15
+ import { z as z2 } from "zod";
16
+
17
+ // src/errors.ts
14
18
  import { z } from "zod";
15
- var autoFitOverlayReferenceSchema = z.object({
16
- id: z.string().min(1).max(64),
17
- index: z.number().int().min(0).max(23),
18
- type: z.enum(["circle", "line", "marker", "polygon"])
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
+ retryable: z.boolean().optional()
27
+ }).strip();
28
+ var StaticMapError = class extends Error {
29
+ code;
30
+ remainingUnits;
31
+ requestId;
32
+ response;
33
+ retryable;
34
+ status;
35
+ constructor(response, status) {
36
+ const parsed = staticMapErrorResponseSchema.parse(response);
37
+ super(parsed.error);
38
+ this.name = "StaticMapError";
39
+ this.code = parsed.code ?? null;
40
+ this.remainingUnits = parsed.remainingUnits ?? null;
41
+ this.requestId = parsed.requestId ?? null;
42
+ this.response = Object.freeze(parsed);
43
+ this.retryable = parsed.retryable ?? null;
44
+ this.status = status;
45
+ }
46
+ };
47
+ function hasNoControlCharacters(value) {
48
+ return !Array.from(value).some((character) => {
49
+ const codePoint = character.codePointAt(0) ?? 0;
50
+ return codePoint <= 31 || codePoint === 127;
51
+ });
52
+ }
53
+
54
+ // src/auto-fit.ts
55
+ var autoFitOverlayReferenceSchema = z2.object({
56
+ id: z2.string().min(1).max(64),
57
+ index: z2.number().int().min(0).max(23),
58
+ type: z2.enum(["circle", "line", "marker", "polygon", "symbol"])
19
59
  }).strict();
20
- var autoFitViewportSchema = z.object({
21
- height: z.number().int().min(64).max(1280),
22
- width: z.number().int().min(64).max(1280)
60
+ var autoFitViewportSchema = z2.object({
61
+ height: z2.number().int().min(64).max(1280),
62
+ width: z2.number().int().min(64).max(1280)
23
63
  }).strict();
24
- var autoFitDetailsSchema = z.object({
64
+ var autoFitDetailsSchema = z2.object({
25
65
  overlay: autoFitOverlayReferenceSchema.optional(),
26
66
  padding: staticPaddingSchema.optional(),
27
- projection: z.enum(["globe", "mercator"]).optional(),
67
+ projection: z2.enum(["globe", "mercator"]).optional(),
28
68
  requiredInsets: staticPaddingSchema.optional(),
29
- ringIndex: z.number().int().min(0).max(15).optional(),
30
- segmentIndex: z.number().int().min(0).max(1999).optional(),
69
+ ringIndex: z2.number().int().min(0).max(15).optional(),
70
+ segmentIndex: z2.number().int().min(0).max(1999).optional(),
31
71
  viewport: autoFitViewportSchema.optional()
32
72
  }).strict();
33
- var staticOverlayInvalidDetailsSchema = z.object({
34
- latitude: z.number().finite(),
35
- limit: z.literal(MAX_OVERLAY_LATITUDE),
73
+ var staticOverlayInvalidDetailsSchema = z2.object({
74
+ latitude: z2.number().finite(),
75
+ limit: z2.literal(MAX_OVERLAY_LATITUDE),
36
76
  overlay: autoFitOverlayReferenceSchema
37
77
  }).strict();
78
+ var staticLabelUnsupportedDetailsSchema = z2.object({
79
+ codePoint: z2.number().int().min(0).max(1114111),
80
+ overlay: autoFitOverlayReferenceSchema.extend({ type: z2.literal("symbol") }).strict()
81
+ }).strict();
82
+ var staticIconUnavailableDetailsSchema = z2.object({
83
+ icon: z2.string().max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u),
84
+ overlay: autoFitOverlayReferenceSchema.extend({ type: z2.literal("symbol") }).strict()
85
+ }).strict();
86
+ var staticSymbolTooLargeDetailsSchema = z2.object({
87
+ height: z2.number().finite().positive().max(16384),
88
+ limit: z2.literal(2048),
89
+ overlay: autoFitOverlayReferenceSchema.extend({ type: z2.literal("symbol") }).strict(),
90
+ width: z2.number().finite().positive().max(16384)
91
+ }).strict();
38
92
  var autoFitErrorBase = {
39
- error: z.string().min(1).max(512),
40
- retryable: z.literal(false)
93
+ error: z2.string().min(1).max(512),
94
+ retryable: z2.literal(false)
41
95
  };
42
- var staticMapRequestErrorResponseSchema = z.discriminatedUnion("code", [
43
- z.object({
96
+ var staticMapRequestErrorResponseSchema = z2.discriminatedUnion("code", [
97
+ z2.object({
44
98
  ...autoFitErrorBase,
45
- code: z.literal("AUTO_FIT_EMPTY"),
46
- reason: z.literal("NO_FITTABLE_OVERLAYS")
99
+ code: z2.literal("AUTO_FIT_EMPTY"),
100
+ reason: z2.literal("NO_FITTABLE_OVERLAYS")
47
101
  }).strict(),
48
- z.object({
102
+ z2.object({
49
103
  ...autoFitErrorBase,
50
- code: z.literal("AUTO_FIT_AMBIGUOUS"),
104
+ code: z2.literal("AUTO_FIT_AMBIGUOUS"),
51
105
  details: autoFitDetailsSchema,
52
- reason: z.literal("ANTIMERIDIAN_WRAP_REQUIRED")
106
+ reason: z2.literal("ANTIMERIDIAN_WRAP_REQUIRED")
53
107
  }).strict(),
54
- z.object({
108
+ z2.object({
55
109
  ...autoFitErrorBase,
56
- code: z.literal("AUTO_FIT_IMPOSSIBLE"),
110
+ code: z2.literal("AUTO_FIT_IMPOSSIBLE"),
57
111
  details: autoFitDetailsSchema.optional(),
58
- reason: z.enum([
112
+ reason: z2.enum([
59
113
  "GLOBE_NOT_SIMULTANEOUSLY_VISIBLE",
60
114
  "INSUFFICIENT_VIEWPORT",
61
115
  "CAMERA_UNRESOLVABLE",
62
116
  "PROJECTED_FOOTPRINT_OUTSIDE_VIEWPORT"
63
117
  ])
64
118
  }).strict(),
65
- z.object({
119
+ z2.object({
66
120
  ...autoFitErrorBase,
67
- code: z.literal("STATIC_OVERLAY_INVALID"),
121
+ code: z2.literal("STATIC_OVERLAY_INVALID"),
68
122
  details: staticOverlayInvalidDetailsSchema,
69
- reason: z.literal("OVERLAY_LATITUDE_OUT_OF_RANGE")
123
+ reason: z2.literal("OVERLAY_LATITUDE_OUT_OF_RANGE")
124
+ }).strict(),
125
+ z2.object({
126
+ ...autoFitErrorBase,
127
+ code: z2.literal("STATIC_MAP_LABEL_UNSUPPORTED"),
128
+ details: staticLabelUnsupportedDetailsSchema,
129
+ reason: z2.literal("UNSUPPORTED_GLYPH")
70
130
  }).strict(),
71
- z.object({
131
+ z2.object({
72
132
  ...autoFitErrorBase,
73
- code: z.literal("STATIC_MAP_ATTRIBUTION_UNSUPPORTED"),
74
- reason: z.enum(["UNSUPPORTED_DECLARATION", "UNSUPPORTED_GLYPH"])
133
+ code: z2.literal("STATIC_MAP_ICON_UNAVAILABLE"),
134
+ details: staticIconUnavailableDetailsSchema,
135
+ reason: z2.enum(["ICON_NOT_FOUND", "MANAGED_SPRITE_REQUIRED"])
75
136
  }).strict(),
76
- z.object({
137
+ z2.object({
77
138
  ...autoFitErrorBase,
78
- code: z.literal("STATIC_MAP_ATTRIBUTION_TOO_LARGE"),
79
- reason: z.literal("CONTENT_LIMIT_EXCEEDED")
139
+ code: z2.literal("STATIC_MAP_PLACEMENT_UNAVAILABLE"),
140
+ reason: z2.literal("MAP_REDEPLOY_REQUIRED")
80
141
  }).strict(),
81
- z.object({
142
+ z2.object({
82
143
  ...autoFitErrorBase,
83
- code: z.literal("STATIC_MAP_ATTRIBUTION_DOES_NOT_FIT"),
84
- reason: z.literal("BLOCK_DOES_NOT_FIT")
144
+ code: z2.literal("STATIC_MAP_SYMBOL_TOO_LARGE"),
145
+ details: staticSymbolTooLargeDetailsSchema,
146
+ reason: z2.literal("COMPOSED_IMAGE_TOO_LARGE")
147
+ }).strict(),
148
+ z2.object({
149
+ ...autoFitErrorBase,
150
+ code: z2.literal("STATIC_MAP_ATTRIBUTION_UNSUPPORTED"),
151
+ reason: z2.enum(["UNSUPPORTED_DECLARATION", "UNSUPPORTED_GLYPH"])
152
+ }).strict(),
153
+ z2.object({
154
+ ...autoFitErrorBase,
155
+ code: z2.literal("STATIC_MAP_ATTRIBUTION_TOO_LARGE"),
156
+ reason: z2.literal("CONTENT_LIMIT_EXCEEDED")
157
+ }).strict(),
158
+ z2.object({
159
+ ...autoFitErrorBase,
160
+ code: z2.literal("STATIC_MAP_ATTRIBUTION_DOES_NOT_FIT"),
161
+ reason: z2.literal("BLOCK_DOES_NOT_FIT")
85
162
  }).strict()
86
163
  ]);
87
- var StaticMapRequestError = class extends Error {
164
+ var StaticMapRequestError = class extends StaticMapError {
88
165
  code;
89
166
  details;
90
167
  reason;
91
168
  retryable = false;
92
169
  status;
93
170
  constructor(response, status = 422) {
94
- super(response.error);
171
+ super(response, status);
95
172
  this.name = "StaticMapRequestError";
96
173
  this.code = response.code;
97
174
  this.details = "details" in response ? response.details : void 0;
@@ -99,14 +176,14 @@ var StaticMapRequestError = class extends Error {
99
176
  this.status = status;
100
177
  }
101
178
  };
102
- var staticAutoFitPlanSchema = z.object({
103
- bounds: z.tuple([
104
- z.number().finite(),
105
- z.number().finite().min(-90).max(90),
106
- z.number().finite(),
107
- z.number().finite().min(-90).max(90)
179
+ var staticAutoFitPlanSchema = z2.object({
180
+ bounds: z2.tuple([
181
+ z2.number().finite(),
182
+ z2.number().finite().min(-90).max(90),
183
+ z2.number().finite(),
184
+ z2.number().finite().min(-90).max(90)
108
185
  ]),
109
- longitudeOffsets: z.array(z.number().int().min(-360).max(360)).max(24),
186
+ longitudeOffsets: z2.array(z2.number().int().min(-360).max(360)).max(24),
110
187
  nominalInsets: staticPaddingSchema,
111
188
  requiredInsets: staticPaddingSchema
112
189
  }).strict();
@@ -214,7 +291,7 @@ function failure(response) {
214
291
  return { ...response, ok: false };
215
292
  }
216
293
  function overlayCoordinates(overlay) {
217
- if (overlay.type === "circle" || overlay.type === "marker") {
294
+ if (overlay.type === "circle" || overlay.type === "marker" || overlay.type === "symbol") {
218
295
  return [overlay.coordinate];
219
296
  }
220
297
  if (overlay.type === "line") {
@@ -223,7 +300,8 @@ function overlayCoordinates(overlay) {
223
300
  return overlay.coordinates.flat();
224
301
  }
225
302
  function findAmbiguousSegment(overlay) {
226
- if (overlay.type === "circle" || overlay.type === "marker") return null;
303
+ if (overlay.type === "circle" || overlay.type === "marker" || overlay.type === "symbol")
304
+ return null;
227
305
  const paths = overlay.type === "line" ? [overlay.coordinates] : overlay.coordinates;
228
306
  for (const [ringIndex, path] of paths.entries()) {
229
307
  for (let segmentIndex = 0; segmentIndex < path.length - 1; segmentIndex += 1) {
@@ -243,6 +321,7 @@ function nominalOverlayExpansion(overlay) {
243
321
  if (overlay.type === "circle" || overlay.type === "marker") {
244
322
  return overlay.radius + overlay.strokeWidth;
245
323
  }
324
+ if (overlay.type === "symbol") return 0;
246
325
  if (overlay.type === "line") {
247
326
  return overlay.width / 2 + 0.5;
248
327
  }
@@ -335,8 +414,8 @@ function findStaticOverlayLatitudeFailure(input) {
335
414
  if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
336
415
  const overlay = candidate;
337
416
  const type = overlay.type;
338
- if (!["circle", "line", "marker", "polygon"].includes(String(type))) continue;
339
- const coordinates = type === "circle" || type === "marker" ? [overlay.coordinate] : overlayCoordinateCandidates(type, overlay.coordinates);
417
+ if (!["circle", "line", "marker", "polygon", "symbol"].includes(String(type))) continue;
418
+ const coordinates = type === "circle" || type === "marker" || type === "symbol" ? [overlay.coordinate] : overlayCoordinateCandidates(type, overlay.coordinates);
340
419
  for (const coordinate of coordinates) {
341
420
  if (!Array.isArray(coordinate) || coordinate.length !== 2 || typeof coordinate[1] !== "number" || !Number.isFinite(coordinate[1]) || Math.abs(coordinate[1]) <= MAX_OVERLAY_LATITUDE) {
342
421
  continue;
@@ -362,6 +441,38 @@ function findStaticOverlayLatitudeFailure(input) {
362
441
  }
363
442
  return null;
364
443
  }
444
+ function findStaticSymbolLabelGlyphFailure(input) {
445
+ if (!input || typeof input !== "object" || Array.isArray(input)) return null;
446
+ const overlays = input.overlays;
447
+ if (!Array.isArray(overlays)) return null;
448
+ for (const [index, candidate] of overlays.entries()) {
449
+ if (index >= staticSceneLimits.maxOverlays) break;
450
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
451
+ const overlay = candidate;
452
+ if (overlay.type !== "symbol") continue;
453
+ const label = overlay.label;
454
+ const text = typeof label === "string" ? label : label && typeof label === "object" && !Array.isArray(label) ? label.text : void 0;
455
+ if (typeof text !== "string") continue;
456
+ const unsupported = Array.from(text).find(
457
+ (character) => !isSupportedStaticSymbolCodePoint(character.codePointAt(0))
458
+ );
459
+ if (!unsupported) continue;
460
+ const codePoint = unsupported.codePointAt(0);
461
+ const id = typeof overlay.id === "string" && overlay.id.trim() ? overlay.id.trim() : `overlay-${index + 1}`;
462
+ return {
463
+ code: "STATIC_MAP_LABEL_UNSUPPORTED",
464
+ details: {
465
+ codePoint,
466
+ overlay: { id: id.slice(0, 64), index, type: "symbol" }
467
+ },
468
+ error: `Overlay overlays.${index} label contains unsupported glyph U+${codePoint.toString(16).toUpperCase().padStart(4, "0")}`,
469
+ ok: false,
470
+ reason: "UNSUPPORTED_GLYPH",
471
+ retryable: false
472
+ };
473
+ }
474
+ return null;
475
+ }
365
476
  function overlayCoordinateCandidates(type, value) {
366
477
  if (!Array.isArray(value)) return [];
367
478
  if (type === "line") {
@@ -376,6 +487,8 @@ function overlayCoordinateCandidates(type, value) {
376
487
  function validateStaticScene(input) {
377
488
  const overlayLatitudeFailure = findStaticOverlayLatitudeFailure(input);
378
489
  if (overlayLatitudeFailure) return overlayLatitudeFailure;
490
+ const labelGlyphFailure = findStaticSymbolLabelGlyphFailure(input);
491
+ if (labelGlyphFailure) return labelGlyphFailure;
379
492
  const parsed = staticSceneSchema.safeParse(input);
380
493
  if (!parsed.success) {
381
494
  return {
@@ -402,6 +515,11 @@ function normalizeStaticScene(scene) {
402
515
  const { ok: _ok, ...response } = overlayLatitudeFailure;
403
516
  throw new StaticMapRequestError(response);
404
517
  }
518
+ const labelGlyphFailure = findStaticSymbolLabelGlyphFailure(scene);
519
+ if (labelGlyphFailure) {
520
+ const { ok: _ok, ...response } = labelGlyphFailure;
521
+ throw new StaticMapRequestError(response);
522
+ }
405
523
  const normalized = normalizeParsedStaticScene(staticSceneSchema.parse(scene));
406
524
  if (normalized.camera.type === "auto") {
407
525
  const analysis = analyzeStaticAutoFit(normalized);
@@ -468,6 +586,8 @@ function normalizeAutoPadding(padding, size) {
468
586
  }
469
587
 
470
588
  export {
589
+ staticMapErrorResponseSchema,
590
+ StaticMapError,
471
591
  staticMapRequestErrorResponseSchema,
472
592
  StaticMapRequestError,
473
593
  staticAutoFitPlanSchema,