@tileflow/static 0.1.0-alpha.20 → 0.1.0-alpha.21
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 +38 -1
- package/dist/{chunk-QCR76BUO.js → chunk-A23RC4BE.js} +27 -3
- package/dist/{chunk-LGEYA6FQ.js → chunk-KPT7PASB.js} +22 -6
- package/dist/{chunk-YUG3PBK2.js → chunk-MGQCHCU3.js} +35 -5
- package/dist/chunk-NHQK5UZ2.js +455 -0
- package/dist/{chunk-HKYA7ZIU.js → chunk-S3WXE5P4.js} +32 -12
- package/dist/client.d.ts +1 -1
- package/dist/client.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +11 -5
- package/dist/manifest.d.ts +27 -1
- package/dist/manifest.js +3 -3
- package/dist/overlays.d.ts +5 -3
- package/dist/overlays.js +2 -2
- package/dist/{scene-contract-DzsUeJ_V.d.ts → scene-contract-DZMhLDnf.d.ts} +19 -1
- package/dist/scene.d.ts +182 -5
- package/dist/scene.js +8 -2
- package/package.json +1 -1
- package/dist/chunk-ROBEGDDY.js +0 -63
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
const scene = validateStaticScene({
|
|
21
21
|
map: 'madrid',
|
|
22
22
|
theme: 'dark',
|
|
23
|
-
camera: {type: '
|
|
23
|
+
camera: {type: 'auto', padding: 24},
|
|
24
24
|
size: {width: 1200, height: 800},
|
|
25
25
|
overlays: [marker({coordinate: [-3.7038, 40.4168]})],
|
|
26
26
|
});
|
|
@@ -33,6 +33,43 @@ await precacheStaticMap(scene.scene, {
|
|
|
33
33
|
});
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
## Automatic framing
|
|
37
|
+
|
|
38
|
+
`camera.type: 'auto'` fits the complete nominal footprint of every declared marker, circle, line,
|
|
39
|
+
and polygon. It works with Mercator and Globe styles when the geometry can be shown. It never clips
|
|
40
|
+
silently.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
camera: {
|
|
44
|
+
type: 'auto',
|
|
45
|
+
padding: {top: 24, right: 240, bottom: 24, left: 24},
|
|
46
|
+
maxZoom: 16,
|
|
47
|
+
bearing: 0,
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Padding uses logical CSS pixels. A number applies to every side; omitted sides in an object are
|
|
52
|
+
zero. Without `padding`, Tileflow uses `min(32, ceil(5% * min(width, height)))`. `maxZoom` defaults
|
|
53
|
+
to 16 and `bearing` to 0. A single position still honors asymmetric padding, so its apparent center
|
|
54
|
+
can move away from the viewport center.
|
|
55
|
+
|
|
56
|
+
Point-like overlays around ±180° use the shortest longitude interval. Tileflow never reinterprets
|
|
57
|
+
vertices inside a line or polygon: an ambiguous crossing returns an error. Represent a correctly
|
|
58
|
+
cut `MultiLineString` or `MultiPolygon` as several `line` or `polygon` overlays with identical
|
|
59
|
+
styles; auto-fit may move each complete component to an adjacent world copy.
|
|
60
|
+
|
|
61
|
+
Current overlay primitives use MapLibre's GeoJSON pipeline and require
|
|
62
|
+
`abs(latitude) <= MAX_OVERLAY_LATITUDE` (`85.051129`). Tileflow rejects larger latitudes instead of
|
|
63
|
+
clamping them. A Globe basemap may display polar regions even though these overlay primitives cannot
|
|
64
|
+
yet be drawn there.
|
|
65
|
+
|
|
66
|
+
Deterministic request failures return `422`, set `retryable: false`, and preserve a stable code,
|
|
67
|
+
reason, and bounded details. `validateStaticScene` returns that document; request helpers throw
|
|
68
|
+
`StaticMapRequestError` either locally or from the Hosted response. The reasons distinguish an empty
|
|
69
|
+
scene, an ambiguous antimeridian path, an out-of-range overlay latitude, an insufficient viewport,
|
|
70
|
+
a Globe visibility conflict, an unresolvable camera, and failed post-fit containment. Correct the
|
|
71
|
+
scene; do not retry it unchanged.
|
|
72
|
+
|
|
36
73
|
The package has four deliberately separate surfaces, all available from the root for compatibility:
|
|
37
74
|
|
|
38
75
|
- scene validation and normalization: `staticSceneSchema`, `validateStaticScene`, and
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
StaticMapRequestError,
|
|
3
|
+
staticMapRequestErrorResponseSchema,
|
|
2
4
|
validateStaticScene
|
|
3
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NHQK5UZ2.js";
|
|
4
6
|
import {
|
|
5
7
|
isSafeHttpUrl,
|
|
6
8
|
stableStringify
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-MGQCHCU3.js";
|
|
8
10
|
|
|
9
11
|
// src/client.ts
|
|
10
12
|
import { z } from "zod";
|
|
@@ -44,6 +46,10 @@ function createStaticMapIdempotencyKey() {
|
|
|
44
46
|
function prepareStaticMapRequest(scene) {
|
|
45
47
|
const validation = validateStaticScene(scene);
|
|
46
48
|
if (!validation.ok) {
|
|
49
|
+
if (isStaticMapRequestFailure(validation)) {
|
|
50
|
+
const { ok: _ok, ...response } = validation;
|
|
51
|
+
throw new StaticMapRequestError(response);
|
|
52
|
+
}
|
|
47
53
|
throw new Error(`Invalid Tileflow static scene: ${validation.error}`);
|
|
48
54
|
}
|
|
49
55
|
const prepared = Object.freeze({
|
|
@@ -103,8 +109,11 @@ async function requestStaticMapUntilReady(request, options) {
|
|
|
103
109
|
});
|
|
104
110
|
throwIfAborted(signal);
|
|
105
111
|
if (!response.ok) {
|
|
106
|
-
await discardBoundedResponse(response, 8 * 1024);
|
|
112
|
+
const requestError = response.status === 422 ? await readStaticMapRequestErrorResponse(response, 8 * 1024) : await discardBoundedResponse(response, 8 * 1024);
|
|
107
113
|
throwIfAborted(signal);
|
|
114
|
+
if (requestError) {
|
|
115
|
+
throw new StaticMapRequestError(requestError, response.status);
|
|
116
|
+
}
|
|
108
117
|
throw new Error(`Tileflow static map failed (${response.status}).`);
|
|
109
118
|
}
|
|
110
119
|
const json = await readJsonResponse(response);
|
|
@@ -148,11 +157,26 @@ async function readJsonResponse(response) {
|
|
|
148
157
|
throw new Error("Tileflow static map returned an invalid response: expected JSON");
|
|
149
158
|
}
|
|
150
159
|
}
|
|
160
|
+
async function readStaticMapRequestErrorResponse(response, maximumBytes) {
|
|
161
|
+
try {
|
|
162
|
+
const source = await readBoundedResponseText(response, maximumBytes, `${maximumBytes} bytes`);
|
|
163
|
+
const parsed = staticMapRequestErrorResponseSchema.safeParse(JSON.parse(source));
|
|
164
|
+
return parsed.success ? parsed.data : null;
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
151
169
|
async function discardBoundedResponse(response, maximumBytes) {
|
|
152
170
|
try {
|
|
153
171
|
await readBoundedResponseText(response, maximumBytes, `${maximumBytes} bytes`);
|
|
154
172
|
} catch {
|
|
155
173
|
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
function isStaticMapRequestFailure(value) {
|
|
177
|
+
if (!("code" in value)) return false;
|
|
178
|
+
const { ok: _ok, ...response } = value;
|
|
179
|
+
return staticMapRequestErrorResponseSchema.safeParse(response).success;
|
|
156
180
|
}
|
|
157
181
|
async function readBoundedResponseText(response, maximumBytes, displayLimit) {
|
|
158
182
|
if (!response.body) return "";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
normalizeStaticOverlay
|
|
3
|
-
|
|
2
|
+
normalizeStaticOverlay,
|
|
3
|
+
roundNumber
|
|
4
|
+
} from "./chunk-MGQCHCU3.js";
|
|
4
5
|
|
|
5
6
|
// src/overlays.ts
|
|
6
7
|
function line(input) {
|
|
@@ -15,15 +16,23 @@ function marker(input) {
|
|
|
15
16
|
function polygon(input) {
|
|
16
17
|
return normalizeStaticOverlay({ ...input, type: "polygon" });
|
|
17
18
|
}
|
|
18
|
-
function compileStaticOverlays(overlays) {
|
|
19
|
+
function compileStaticOverlays(overlays, options = {}) {
|
|
20
|
+
if (options.longitudeOffsets !== void 0 && (options.longitudeOffsets.length !== overlays.length || options.longitudeOffsets.some(
|
|
21
|
+
(offset) => !Number.isInteger(offset) || Math.abs(offset) > 360 || offset % 360 !== 0
|
|
22
|
+
))) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"Static overlay longitude offsets must contain one -360, 0, or 360 per overlay"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
19
27
|
const sources = {};
|
|
20
28
|
const layers = [];
|
|
21
29
|
for (const [index, overlay] of overlays.entries()) {
|
|
30
|
+
const longitudeOffset = options.longitudeOffsets?.[index] ?? 0;
|
|
22
31
|
const id = safeLayerId(`overlay-${index + 1}-${overlay.id ?? overlay.type}`);
|
|
23
32
|
const sourceId = `${id}-source`;
|
|
24
33
|
if (overlay.type === "line") {
|
|
25
34
|
sources[sourceId] = {
|
|
26
|
-
data: feature("LineString", overlay.coordinates),
|
|
35
|
+
data: feature("LineString", shiftLongitudes(overlay.coordinates, longitudeOffset)),
|
|
27
36
|
type: "geojson"
|
|
28
37
|
};
|
|
29
38
|
layers.push({
|
|
@@ -41,7 +50,7 @@ function compileStaticOverlays(overlays) {
|
|
|
41
50
|
}
|
|
42
51
|
if (overlay.type === "polygon") {
|
|
43
52
|
sources[sourceId] = {
|
|
44
|
-
data: feature("Polygon", overlay.coordinates),
|
|
53
|
+
data: feature("Polygon", shiftLongitudes(overlay.coordinates, longitudeOffset)),
|
|
45
54
|
type: "geojson"
|
|
46
55
|
};
|
|
47
56
|
layers.push({
|
|
@@ -68,7 +77,7 @@ function compileStaticOverlays(overlays) {
|
|
|
68
77
|
continue;
|
|
69
78
|
}
|
|
70
79
|
sources[sourceId] = {
|
|
71
|
-
data: feature("Point", overlay.coordinate),
|
|
80
|
+
data: feature("Point", shiftLongitudes(overlay.coordinate, longitudeOffset)),
|
|
72
81
|
type: "geojson"
|
|
73
82
|
};
|
|
74
83
|
layers.push({
|
|
@@ -86,6 +95,13 @@ function compileStaticOverlays(overlays) {
|
|
|
86
95
|
}
|
|
87
96
|
return { layers, sources };
|
|
88
97
|
}
|
|
98
|
+
function shiftLongitudes(value, offset) {
|
|
99
|
+
if (!Array.isArray(value)) return value;
|
|
100
|
+
if (value.length === 2 && typeof value[0] === "number" && typeof value[1] === "number") {
|
|
101
|
+
return [roundNumber(value[0] + offset), value[1]];
|
|
102
|
+
}
|
|
103
|
+
return value.map((entry) => shiftLongitudes(entry, offset));
|
|
104
|
+
}
|
|
89
105
|
function feature(type, coordinates) {
|
|
90
106
|
return {
|
|
91
107
|
geometry: { coordinates, type },
|
|
@@ -49,6 +49,7 @@ function base64Url(bytes) {
|
|
|
49
49
|
// src/scene-contract.ts
|
|
50
50
|
import { z } from "zod";
|
|
51
51
|
var staticSceneSchemaVersion = 1;
|
|
52
|
+
var MAX_OVERLAY_LATITUDE = 85.051129;
|
|
52
53
|
var staticSceneLimits = {
|
|
53
54
|
maxDimension: 1280,
|
|
54
55
|
maxGeoJsonBytes: 96e3,
|
|
@@ -60,6 +61,12 @@ var staticSceneLimits = {
|
|
|
60
61
|
var coordinateSchema = z.tuple([z.number().finite(), z.number().finite()]).refine(([lng, lat]) => lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90, {
|
|
61
62
|
message: "Expected [lng, lat] within world bounds"
|
|
62
63
|
});
|
|
64
|
+
var overlayCoordinateSchema = coordinateSchema.refine(
|
|
65
|
+
([, latitude]) => Math.abs(latitude) <= MAX_OVERLAY_LATITUDE,
|
|
66
|
+
{
|
|
67
|
+
message: `Static overlay latitude must be within \xB1${MAX_OVERLAY_LATITUDE}\xB0`
|
|
68
|
+
}
|
|
69
|
+
);
|
|
63
70
|
var colorSchema = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, {
|
|
64
71
|
message: "Expected a hex color like #C6A15B"
|
|
65
72
|
});
|
|
@@ -99,9 +106,30 @@ var boundsCameraSchema = z.object({
|
|
|
99
106
|
}).refine((camera) => camera.bounds[3] > camera.bounds[1], {
|
|
100
107
|
message: "Bounds north must be greater than south"
|
|
101
108
|
});
|
|
109
|
+
var staticPaddingSchema = z.object({
|
|
110
|
+
bottom: z.number().int().nonnegative(),
|
|
111
|
+
left: z.number().int().nonnegative(),
|
|
112
|
+
right: z.number().int().nonnegative(),
|
|
113
|
+
top: z.number().int().nonnegative()
|
|
114
|
+
}).strict();
|
|
115
|
+
var autoPaddingInputSchema = z.union([
|
|
116
|
+
z.number().int().nonnegative(),
|
|
117
|
+
z.object({
|
|
118
|
+
bottom: z.number().int().nonnegative().optional(),
|
|
119
|
+
left: z.number().int().nonnegative().optional(),
|
|
120
|
+
right: z.number().int().nonnegative().optional(),
|
|
121
|
+
top: z.number().int().nonnegative().optional()
|
|
122
|
+
}).strict()
|
|
123
|
+
]);
|
|
124
|
+
var autoCameraSchema = z.object({
|
|
125
|
+
bearing: z.number().finite().min(-180).max(180).optional(),
|
|
126
|
+
maxZoom: z.number().finite().min(0).max(22).optional(),
|
|
127
|
+
padding: autoPaddingInputSchema.optional(),
|
|
128
|
+
type: z.literal("auto")
|
|
129
|
+
});
|
|
102
130
|
var lineOverlaySchema = z.object({
|
|
103
131
|
color: colorSchema.default("#C6A15B"),
|
|
104
|
-
coordinates: z.array(
|
|
132
|
+
coordinates: z.array(overlayCoordinateSchema).min(2).max(staticSceneLimits.maxPathCoordinates),
|
|
105
133
|
id: z.string().trim().min(1).max(64).optional(),
|
|
106
134
|
opacity: z.number().finite().min(0).max(1).default(1),
|
|
107
135
|
type: z.literal("line"),
|
|
@@ -109,7 +137,7 @@ var lineOverlaySchema = z.object({
|
|
|
109
137
|
});
|
|
110
138
|
var circleOverlaySchema = z.object({
|
|
111
139
|
color: colorSchema.default("#C6A15B"),
|
|
112
|
-
coordinate:
|
|
140
|
+
coordinate: overlayCoordinateSchema,
|
|
113
141
|
id: z.string().trim().min(1).max(64).optional(),
|
|
114
142
|
opacity: z.number().finite().min(0).max(1).default(1),
|
|
115
143
|
radius: z.number().finite().min(1).max(64).default(6),
|
|
@@ -119,14 +147,14 @@ var circleOverlaySchema = z.object({
|
|
|
119
147
|
});
|
|
120
148
|
var markerOverlaySchema = z.object({
|
|
121
149
|
color: colorSchema.default("#C6A15B"),
|
|
122
|
-
coordinate:
|
|
150
|
+
coordinate: overlayCoordinateSchema,
|
|
123
151
|
id: z.string().trim().min(1).max(64).optional(),
|
|
124
152
|
radius: z.number().finite().min(2).max(64).default(8),
|
|
125
153
|
strokeColor: colorSchema.default("#ffffff"),
|
|
126
154
|
strokeWidth: z.number().finite().min(0).max(16).default(2),
|
|
127
155
|
type: z.literal("marker")
|
|
128
156
|
});
|
|
129
|
-
var polygonRingSchema = z.array(
|
|
157
|
+
var polygonRingSchema = z.array(overlayCoordinateSchema).min(4).max(staticSceneLimits.maxPathCoordinates).refine(
|
|
130
158
|
(ring) => {
|
|
131
159
|
const first = ring[0];
|
|
132
160
|
const last = ring[ring.length - 1];
|
|
@@ -150,7 +178,7 @@ var staticOverlaySchema = z.discriminatedUnion("type", [
|
|
|
150
178
|
polygonOverlaySchema
|
|
151
179
|
]);
|
|
152
180
|
var staticSceneSchema = z.object({
|
|
153
|
-
camera: z.discriminatedUnion("type", [centerCameraSchema, boundsCameraSchema]),
|
|
181
|
+
camera: z.discriminatedUnion("type", [centerCameraSchema, boundsCameraSchema, autoCameraSchema]),
|
|
154
182
|
map: portableIdSchema,
|
|
155
183
|
overlays: z.array(staticOverlaySchema).max(staticSceneLimits.maxOverlays).default([]),
|
|
156
184
|
size: sizeSchema,
|
|
@@ -190,7 +218,9 @@ export {
|
|
|
190
218
|
hashStableValue,
|
|
191
219
|
isSafeHttpUrl,
|
|
192
220
|
staticSceneSchemaVersion,
|
|
221
|
+
MAX_OVERLAY_LATITUDE,
|
|
193
222
|
staticSceneLimits,
|
|
223
|
+
staticPaddingSchema,
|
|
194
224
|
staticOverlaySchema,
|
|
195
225
|
staticSceneSchema,
|
|
196
226
|
normalizeStaticOverlay,
|