@tileflow/core 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @tileflow/core
2
+
3
+ Typed Tileflow config helpers and the MapLibre style compiler.
4
+
5
+ ```ts
6
+ import { defineTileflow } from "@tileflow/core";
7
+
8
+ export default defineTileflow({
9
+ maps: {
10
+ madrid: {
11
+ colors: {
12
+ canvas: "#F8F6F1",
13
+ land: "#F2EEE6",
14
+ water: "#B6CBD8",
15
+ nature: "#AEBB9B",
16
+ road: "#DED6C8",
17
+ text: "#2E2E2E",
18
+ accent: "#C6A15B",
19
+ },
20
+ },
21
+ },
22
+ });
23
+ ```
24
+
25
+ Docs: https://tileflow.dev/docs
@@ -0,0 +1,111 @@
1
+ type TileflowDensity = "clean" | "balanced" | "dense";
2
+ type TileflowRoads = "hidden" | "soft" | "standard" | "detailed";
3
+ type TileflowLabels = "none" | "essential" | "balanced" | "full";
4
+ type TileflowPoi = "none" | "minimal" | "balanced" | "full";
5
+ type TileflowColors = {
6
+ canvas: string;
7
+ land: string;
8
+ water: string;
9
+ nature: string;
10
+ road: string;
11
+ text: string;
12
+ accent: string;
13
+ };
14
+ type LegacyTileflowColorAliases = {
15
+ background?: string;
16
+ park?: string;
17
+ building?: string;
18
+ };
19
+ type TileflowColorConfig = Partial<TileflowColors> & LegacyTileflowColorAliases;
20
+ type TileflowFontWeight = "regular" | "medium" | "semibold" | "bold";
21
+ type TileflowTypography = {
22
+ font?: string;
23
+ weight?: TileflowFontWeight;
24
+ };
25
+ type TileflowSourceLayers = {
26
+ landuse?: string;
27
+ park?: string;
28
+ water?: string;
29
+ road?: string;
30
+ building?: string;
31
+ boundary?: string;
32
+ place?: string;
33
+ poi?: string;
34
+ };
35
+ type TileflowTiles = {
36
+ sourceId?: string;
37
+ tileset?: string;
38
+ url?: string;
39
+ attribution?: string;
40
+ sourceLayers?: TileflowSourceLayers;
41
+ };
42
+ type TileflowConfig = {
43
+ name?: string;
44
+ tileset?: string;
45
+ colors?: TileflowColorConfig;
46
+ typography?: TileflowTypography;
47
+ density?: TileflowDensity;
48
+ roads?: TileflowRoads;
49
+ labels?: TileflowLabels;
50
+ poi?: TileflowPoi;
51
+ glyphs?: string;
52
+ sprite?: string;
53
+ tiles?: TileflowTiles;
54
+ /**
55
+ * Domains allowed to load this map in production (e.g. "example.com" or
56
+ * "*.example.com"). When set, tile and session requests from other
57
+ * websites are rejected. Local development (localhost, private network
58
+ * addresses) is always allowed. Leave unset to allow any domain.
59
+ */
60
+ allowedOrigins?: string[];
61
+ };
62
+ type TileflowMapConfig = TileflowConfig;
63
+ type TileflowTilesetConfig = {
64
+ id?: string;
65
+ name?: string;
66
+ attribution?: string;
67
+ sourceLayers?: TileflowSourceLayers;
68
+ };
69
+ type TileflowProjectConfig = {
70
+ tilesets?: Record<string, TileflowTilesetConfig>;
71
+ maps: Record<string, TileflowMapConfig>;
72
+ };
73
+ type TileflowStyleOptions = {
74
+ tileBaseUrl?: string;
75
+ tileset?: string;
76
+ };
77
+ type TileflowManifest = {
78
+ version: 1;
79
+ tileBaseUrl: string;
80
+ styles: Record<string, string>;
81
+ maps: Record<string, string>;
82
+ };
83
+ type MapLibreStyle = {
84
+ version: 8;
85
+ name: string;
86
+ glyphs?: string;
87
+ sprite?: string;
88
+ sources: Record<string, Record<string, unknown>>;
89
+ layers: Array<Record<string, unknown>>;
90
+ metadata?: Record<string, unknown>;
91
+ };
92
+ type ValidationMessage = {
93
+ level: "error" | "warning";
94
+ path: string;
95
+ message: string;
96
+ };
97
+ type ValidationResult = {
98
+ valid: boolean;
99
+ messages: ValidationMessage[];
100
+ };
101
+ declare function defineTileflow(config: TileflowProjectConfig): TileflowProjectConfig;
102
+ declare function validateConfig(input: unknown): ValidationResult;
103
+ declare function createStyle(input?: TileflowConfig, options?: TileflowStyleOptions): MapLibreStyle;
104
+ declare function createStyleFromProject(projectConfig: TileflowProjectConfig, mapName: string, options?: TileflowStyleOptions): MapLibreStyle;
105
+ declare function createManifest(projectConfig: TileflowProjectConfig, options?: {
106
+ styleBaseUrl?: string;
107
+ tileBaseUrl?: string;
108
+ }): TileflowManifest;
109
+ declare function getDefaultColors(): TileflowColors;
110
+
111
+ export { type MapLibreStyle, type TileflowColorConfig, type TileflowColors, type TileflowConfig, type TileflowDensity, type TileflowFontWeight, type TileflowLabels, type TileflowManifest, type TileflowMapConfig, type TileflowPoi, type TileflowProjectConfig, type TileflowRoads, type TileflowSourceLayers, type TileflowStyleOptions, type TileflowTiles, type TileflowTilesetConfig, type TileflowTypography, type ValidationMessage, type ValidationResult, createManifest, createStyle, createStyleFromProject, defineTileflow, getDefaultColors, validateConfig };
package/dist/index.js ADDED
@@ -0,0 +1,658 @@
1
+ // src/index.ts
2
+ import { z } from "zod";
3
+ var hexColorSchema = z.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
4
+ message: "Expected a hex color like #F8F6F1 or #F8F6F1FF"
5
+ });
6
+ var configSchema = z.object({
7
+ name: z.string().min(1).optional(),
8
+ tileset: z.string().trim().min(1).optional(),
9
+ colors: z.object({
10
+ canvas: hexColorSchema.optional(),
11
+ land: hexColorSchema.optional(),
12
+ background: hexColorSchema.optional(),
13
+ water: hexColorSchema.optional(),
14
+ nature: hexColorSchema.optional(),
15
+ text: hexColorSchema.optional(),
16
+ accent: hexColorSchema.optional(),
17
+ park: hexColorSchema.optional(),
18
+ road: hexColorSchema.optional(),
19
+ building: hexColorSchema.optional()
20
+ }).optional(),
21
+ typography: z.object({
22
+ font: z.string().trim().min(1).optional(),
23
+ weight: z.enum(["regular", "medium", "semibold", "bold"]).optional()
24
+ }).optional(),
25
+ density: z.enum(["clean", "balanced", "dense"]).optional(),
26
+ roads: z.enum(["hidden", "soft", "standard", "detailed"]).optional(),
27
+ labels: z.enum(["none", "essential", "balanced", "full"]).optional(),
28
+ poi: z.enum(["none", "minimal", "balanced", "full"]).optional(),
29
+ glyphs: z.string().min(1).optional(),
30
+ sprite: z.string().min(1).optional(),
31
+ tiles: z.object({
32
+ sourceId: z.string().min(1).optional(),
33
+ tileset: z.string().trim().min(1).optional(),
34
+ url: z.url().optional(),
35
+ attribution: z.string().optional(),
36
+ sourceLayers: z.object({
37
+ landuse: z.string().optional(),
38
+ park: z.string().optional(),
39
+ water: z.string().optional(),
40
+ road: z.string().optional(),
41
+ building: z.string().optional(),
42
+ boundary: z.string().optional(),
43
+ place: z.string().optional(),
44
+ poi: z.string().optional()
45
+ }).optional()
46
+ }).optional(),
47
+ allowedOrigins: z.array(z.string().trim().min(1).max(253)).max(20).optional()
48
+ });
49
+ var tilesetConfigSchema = z.object({
50
+ id: z.string().trim().min(1).optional(),
51
+ name: z.string().trim().min(1).optional(),
52
+ attribution: z.string().optional(),
53
+ sourceLayers: z.object({
54
+ landuse: z.string().optional(),
55
+ park: z.string().optional(),
56
+ water: z.string().optional(),
57
+ road: z.string().optional(),
58
+ building: z.string().optional(),
59
+ boundary: z.string().optional(),
60
+ place: z.string().optional(),
61
+ poi: z.string().optional()
62
+ }).optional()
63
+ });
64
+ var projectConfigSchema = z.object({
65
+ tilesets: z.record(z.string(), tilesetConfigSchema).optional(),
66
+ maps: z.record(z.string(), configSchema).refine((maps) => {
67
+ return Object.keys(maps).length > 0;
68
+ }, "Expected at least one map")
69
+ });
70
+ var defaultSourceLayers = {
71
+ landuse: "landuse",
72
+ park: "park",
73
+ water: "water",
74
+ road: "transportation",
75
+ building: "building",
76
+ boundary: "boundary",
77
+ place: "place",
78
+ poi: "poi"
79
+ };
80
+ var defaultColors = {
81
+ canvas: "#F8F6F1",
82
+ land: "#F2EEE6",
83
+ water: "#B6CBD8",
84
+ nature: "#AEBB9B",
85
+ road: "#DED6C8",
86
+ text: "#2E2E2E",
87
+ accent: "#C6A15B"
88
+ };
89
+ var defaultConfig = {
90
+ density: "clean",
91
+ roads: "soft",
92
+ labels: "essential",
93
+ poi: "minimal"
94
+ };
95
+ var defaultTypography = {
96
+ font: "Noto Sans",
97
+ weight: "regular"
98
+ };
99
+ var fontWeightLabels = {
100
+ regular: "Regular",
101
+ medium: "Medium",
102
+ semibold: "Semibold",
103
+ bold: "Bold"
104
+ };
105
+ var defaultTileBaseUrl = "https://api.tileflow.dev";
106
+ var defaultTileset = "world";
107
+ function defineTileflow(config) {
108
+ return config;
109
+ }
110
+ function validateConfig(input) {
111
+ const parsed = isProjectConfigLike(input) ? projectConfigSchema.safeParse(input) : configSchema.safeParse(input);
112
+ if (parsed.success) {
113
+ return { valid: true, messages: [] };
114
+ }
115
+ return {
116
+ valid: false,
117
+ messages: parsed.error.issues.map((issue) => ({
118
+ level: "error",
119
+ path: issue.path.join(".") || "config",
120
+ message: issue.message
121
+ }))
122
+ };
123
+ }
124
+ function createStyle(input = {}, options = {}) {
125
+ const parsed = configSchema.safeParse(input);
126
+ if (!parsed.success) {
127
+ const message = parsed.error.issues.map((issue) => `${issue.path.join(".") || "config"}: ${issue.message}`).join("; ");
128
+ throw new Error(`Invalid Tileflow config. ${message}`);
129
+ }
130
+ const config = parsed.data;
131
+ const density = config.density ?? defaultConfig.density;
132
+ const roads = config.roads ?? defaultConfig.roads;
133
+ const labels = config.labels ?? defaultConfig.labels;
134
+ const poi = config.poi ?? defaultConfig.poi;
135
+ const colors = resolveColors(config.colors);
136
+ const sourceId = config.tiles?.sourceId ?? "tileflow";
137
+ const tileBaseUrl = normalizeBaseUrl(options.tileBaseUrl);
138
+ const tileset = options.tileset ?? config.tileset ?? config.tiles?.tileset ?? defaultTileset;
139
+ const tilesUrl = config.tiles?.url ?? `${tileBaseUrl}/tiles/${tileset}/tiles.json`;
140
+ const typography = resolveTypography(config.typography, defaultTypography);
141
+ const sourceLayers = resolveSourceLayers(
142
+ {
143
+ ...defaultSourceLayers,
144
+ ...config.tiles?.sourceLayers
145
+ },
146
+ void 0
147
+ );
148
+ const layers = openMapTilesLayers(sourceId, sourceLayers, colors, {
149
+ density,
150
+ labels,
151
+ poi,
152
+ roads,
153
+ typography
154
+ });
155
+ const style = {
156
+ version: 8,
157
+ name: config.name ?? "Tileflow",
158
+ glyphs: config.glyphs ?? `${tileBaseUrl}/fonts/{fontstack}/{range}.pbf`,
159
+ ...config.sprite ? { sprite: config.sprite } : {},
160
+ sources: {
161
+ [sourceId]: {
162
+ type: "vector",
163
+ url: tilesUrl,
164
+ attribution: config.tiles?.attribution ?? "\xA9 OpenMapTiles \xA9 OpenStreetMap contributors"
165
+ }
166
+ },
167
+ layers,
168
+ metadata: {
169
+ "tileflow:density": density,
170
+ "tileflow:roads": roads,
171
+ "tileflow:labels": labels,
172
+ "tileflow:poi": poi,
173
+ "tileflow:typography": typography,
174
+ "tileflow:colors": colors,
175
+ "tileflow:tileBaseUrl": tileBaseUrl,
176
+ "tileflow:tileset": tileset,
177
+ "tileflow:tiles": "tileflow"
178
+ }
179
+ };
180
+ return style;
181
+ }
182
+ function createStyleFromProject(projectConfig, mapName, options = {}) {
183
+ const config = projectConfig.maps[mapName];
184
+ if (!config) {
185
+ throw new Error(`Unknown Tileflow map: ${mapName}`);
186
+ }
187
+ const tilesetName = options.tileset ?? config.tileset ?? config.tiles?.tileset;
188
+ const tilesetConfig = tilesetName ? projectConfig.tilesets?.[tilesetName] : void 0;
189
+ const resolvedTileset = tilesetConfig?.id ?? tilesetName ?? defaultTileset;
190
+ return createStyle(
191
+ {
192
+ ...config,
193
+ tileset: resolvedTileset,
194
+ tiles: {
195
+ ...config.tiles,
196
+ tileset: resolvedTileset,
197
+ attribution: config.tiles?.attribution ?? tilesetConfig?.attribution,
198
+ sourceLayers: resolveSourceLayers(
199
+ config.tiles?.sourceLayers,
200
+ tilesetConfig?.sourceLayers
201
+ )
202
+ }
203
+ },
204
+ {
205
+ ...options,
206
+ tileset: resolvedTileset
207
+ }
208
+ );
209
+ }
210
+ function createManifest(projectConfig, options = {}) {
211
+ const styleBaseUrl = normalizeStyleBaseUrl(options.styleBaseUrl);
212
+ const tileBaseUrl = normalizeBaseUrl(options.tileBaseUrl);
213
+ const maps = Object.keys(projectConfig.maps);
214
+ return {
215
+ version: 1,
216
+ tileBaseUrl,
217
+ styles: Object.fromEntries(
218
+ maps.map((mapName) => [
219
+ mapName,
220
+ `${styleBaseUrl}/styles/${mapName}.json`
221
+ ])
222
+ ),
223
+ maps: Object.fromEntries(
224
+ maps.map((mapName) => [
225
+ mapName,
226
+ `${styleBaseUrl}/styles/${mapName}.json`
227
+ ])
228
+ )
229
+ };
230
+ }
231
+ function getDefaultColors() {
232
+ return defaultColors;
233
+ }
234
+ function resolveColors(overrides = {}) {
235
+ const colors = {
236
+ ...defaultColors,
237
+ canvas: overrides.canvas ?? overrides.background ?? defaultColors.canvas,
238
+ land: overrides.land ?? defaultColors.land,
239
+ water: overrides.water ?? defaultColors.water,
240
+ nature: overrides.nature ?? overrides.park ?? defaultColors.nature,
241
+ road: overrides.road ?? defaultColors.road,
242
+ text: overrides.text ?? defaultColors.text,
243
+ accent: overrides.accent ?? defaultColors.accent,
244
+ building: overrides.building
245
+ };
246
+ const building = colors.building ?? mix(colors.land, colors.road, 0.46);
247
+ return {
248
+ canvas: cssColor(colors.canvas),
249
+ land: cssColor(colors.land),
250
+ water: cssColor(colors.water),
251
+ nature: cssColor(colors.nature),
252
+ road: cssColor(colors.road),
253
+ text: cssColor(colors.text),
254
+ accent: cssColor(colors.accent),
255
+ building: cssColor(building),
256
+ roadCasing: mix(colors.road, colors.text, 0.2),
257
+ roadMajor: mix(colors.road, colors.accent, 0.16),
258
+ roadMinor: cssColor(colors.road),
259
+ textHalo: alpha(colors.canvas, 0.84),
260
+ textMuted: mix(colors.text, colors.canvas, 0.42)
261
+ };
262
+ }
263
+ function resolveTypography(overrides = {}, defaults = defaultTypography) {
264
+ return {
265
+ ...defaults,
266
+ ...overrides
267
+ };
268
+ }
269
+ function resolveSourceLayers(mapLayers, tilesetLayers) {
270
+ return {
271
+ ...defaultSourceLayers,
272
+ ...tilesetLayers,
273
+ ...mapLayers
274
+ };
275
+ }
276
+ function normalizeBaseUrl(value) {
277
+ return normalizeUrl(value ?? defaultTileBaseUrl);
278
+ }
279
+ function normalizeStyleBaseUrl(value) {
280
+ return normalizeUrl(value ?? "");
281
+ }
282
+ function normalizeUrl(value) {
283
+ return value.replace(/\/+$/, "");
284
+ }
285
+ function isProjectConfigLike(input) {
286
+ return Boolean(
287
+ input && typeof input === "object" && "maps" in input && typeof input.maps === "object"
288
+ );
289
+ }
290
+ function textFont(typography) {
291
+ return [`${typography.font} ${fontWeightLabels[typography.weight]}`];
292
+ }
293
+ function fillLayer(id, source, sourceLayer, color, opacity) {
294
+ return {
295
+ id,
296
+ type: "fill",
297
+ source,
298
+ "source-layer": sourceLayer,
299
+ paint: {
300
+ "fill-color": color,
301
+ "fill-opacity": opacity
302
+ }
303
+ };
304
+ }
305
+ function openMapTilesLayers(source, sourceLayers, colors, options) {
306
+ return [
307
+ {
308
+ id: "background",
309
+ type: "background",
310
+ paint: {
311
+ "background-color": colors.canvas
312
+ }
313
+ },
314
+ fillLayer("landuse", source, sourceLayers.landuse, colors.land, 0.42),
315
+ fillLayer(
316
+ "parks",
317
+ source,
318
+ sourceLayers.park,
319
+ colors.nature,
320
+ parkOpacity(options.density)
321
+ ),
322
+ fillLayer("water", source, sourceLayers.water, colors.water, 1),
323
+ ...buildingLayers(source, sourceLayers.building, colors, options.density),
324
+ ...roadLayers(source, sourceLayers.road, colors, options.roads),
325
+ ...boundaryLayers(source, sourceLayers.boundary, colors),
326
+ ...labelLayers(
327
+ source,
328
+ sourceLayers.place,
329
+ colors,
330
+ options.labels,
331
+ options.typography
332
+ ),
333
+ ...poiLayers(
334
+ source,
335
+ sourceLayers.poi,
336
+ colors,
337
+ options.poi,
338
+ options.typography
339
+ )
340
+ ];
341
+ }
342
+ function buildingLayers(source, sourceLayer, colors, density) {
343
+ if (density === "clean") {
344
+ return [
345
+ {
346
+ id: "buildings-soft",
347
+ type: "fill",
348
+ source,
349
+ "source-layer": sourceLayer,
350
+ minzoom: 14,
351
+ paint: {
352
+ "fill-color": colors.building,
353
+ "fill-opacity": 0.38
354
+ }
355
+ }
356
+ ];
357
+ }
358
+ return [
359
+ {
360
+ id: "buildings",
361
+ type: "fill",
362
+ source,
363
+ "source-layer": sourceLayer,
364
+ minzoom: 13,
365
+ paint: {
366
+ "fill-color": colors.building,
367
+ "fill-opacity": density === "dense" ? 0.62 : 0.5
368
+ }
369
+ },
370
+ {
371
+ id: "buildings-outline",
372
+ type: "line",
373
+ source,
374
+ "source-layer": sourceLayer,
375
+ minzoom: 15,
376
+ paint: {
377
+ "line-color": mix(colors.building, colors.text, 0.2),
378
+ "line-opacity": 0.24,
379
+ "line-width": 0.6
380
+ }
381
+ }
382
+ ];
383
+ }
384
+ function roadLayers(source, sourceLayer, colors, roads) {
385
+ if (roads === "hidden") {
386
+ return [];
387
+ }
388
+ const opacity = roads === "soft" ? 0.72 : roads === "standard" ? 0.84 : 0.96;
389
+ const casingOpacity = roads === "detailed" ? 0.3 : 0.16;
390
+ const majorRoadFilter = [
391
+ "match",
392
+ ["get", "class"],
393
+ ["motorway", "trunk", "primary", "secondary"],
394
+ true,
395
+ false
396
+ ];
397
+ const minorRoadFilter = ["!", majorRoadFilter];
398
+ return [
399
+ {
400
+ id: "roads-tunnels",
401
+ type: "line",
402
+ source,
403
+ "source-layer": sourceLayer,
404
+ minzoom: 10,
405
+ filter: ["==", ["get", "brunnel"], "tunnel"],
406
+ paint: {
407
+ "line-color": colors.roadMinor,
408
+ "line-dasharray": [0.6, 0.8],
409
+ "line-opacity": opacity * 0.62,
410
+ "line-width": [
411
+ "interpolate",
412
+ ["linear"],
413
+ ["zoom"],
414
+ 10,
415
+ 0.25,
416
+ 14,
417
+ 1,
418
+ 16,
419
+ 2.4
420
+ ]
421
+ }
422
+ },
423
+ {
424
+ id: "roads-casing",
425
+ type: "line",
426
+ source,
427
+ "source-layer": sourceLayer,
428
+ minzoom: 6,
429
+ paint: {
430
+ "line-color": colors.roadCasing,
431
+ "line-opacity": casingOpacity,
432
+ "line-width": [
433
+ "interpolate",
434
+ ["linear"],
435
+ ["zoom"],
436
+ 6,
437
+ 0.4,
438
+ 12,
439
+ 1.2,
440
+ 16,
441
+ 4
442
+ ]
443
+ }
444
+ },
445
+ {
446
+ id: "roads-minor",
447
+ type: "line",
448
+ source,
449
+ "source-layer": sourceLayer,
450
+ minzoom: 6,
451
+ filter: minorRoadFilter,
452
+ paint: {
453
+ "line-color": colors.roadMinor,
454
+ "line-opacity": opacity * 0.88,
455
+ "line-width": [
456
+ "interpolate",
457
+ ["linear"],
458
+ ["zoom"],
459
+ 6,
460
+ 0.2,
461
+ 12,
462
+ 0.8,
463
+ 16,
464
+ 3
465
+ ]
466
+ }
467
+ },
468
+ {
469
+ id: "roads-major",
470
+ type: "line",
471
+ source,
472
+ "source-layer": sourceLayer,
473
+ minzoom: 5,
474
+ filter: majorRoadFilter,
475
+ paint: {
476
+ "line-color": colors.roadMajor,
477
+ "line-opacity": opacity,
478
+ "line-width": [
479
+ "interpolate",
480
+ ["linear"],
481
+ ["zoom"],
482
+ 5,
483
+ 0.35,
484
+ 12,
485
+ 1.2,
486
+ 16,
487
+ 4
488
+ ]
489
+ }
490
+ },
491
+ {
492
+ id: "roads-bridges",
493
+ type: "line",
494
+ source,
495
+ "source-layer": sourceLayer,
496
+ minzoom: 10,
497
+ filter: ["==", ["get", "brunnel"], "bridge"],
498
+ paint: {
499
+ "line-color": mix(colors.roadMajor, colors.canvas, 0.14),
500
+ "line-opacity": opacity,
501
+ "line-width": [
502
+ "interpolate",
503
+ ["linear"],
504
+ ["zoom"],
505
+ 10,
506
+ 0.45,
507
+ 14,
508
+ 1.6,
509
+ 16,
510
+ 4.4
511
+ ]
512
+ }
513
+ }
514
+ ];
515
+ }
516
+ function boundaryLayers(source, sourceLayer, colors) {
517
+ return [
518
+ {
519
+ id: "boundaries",
520
+ type: "line",
521
+ source,
522
+ "source-layer": sourceLayer,
523
+ minzoom: 2,
524
+ paint: {
525
+ "line-color": mix(colors.text, colors.canvas, 0.7),
526
+ "line-opacity": 0.35,
527
+ "line-width": ["interpolate", ["linear"], ["zoom"], 2, 0.35, 8, 0.8]
528
+ }
529
+ }
530
+ ];
531
+ }
532
+ function labelLayers(source, sourceLayer, colors, labels, typography) {
533
+ if (labels === "none") {
534
+ return [];
535
+ }
536
+ const maxRank = labels === "essential" ? 3 : labels === "balanced" ? 7 : 12;
537
+ return [
538
+ {
539
+ id: "place-labels",
540
+ type: "symbol",
541
+ source,
542
+ "source-layer": sourceLayer,
543
+ minzoom: 3,
544
+ filter: ["<=", ["coalesce", ["get", "rank"], 10], maxRank],
545
+ layout: {
546
+ "text-field": ["coalesce", ["get", "name:en"], ["get", "name"]],
547
+ "text-font": textFont(typography),
548
+ "text-size": [
549
+ "interpolate",
550
+ ["linear"],
551
+ ["zoom"],
552
+ 3,
553
+ 10,
554
+ 8,
555
+ 13,
556
+ 14,
557
+ 17
558
+ ],
559
+ "text-letter-spacing": 0,
560
+ "text-padding": 4
561
+ },
562
+ paint: {
563
+ "text-color": colors.text,
564
+ "text-halo-color": colors.textHalo,
565
+ "text-halo-width": 1.2
566
+ }
567
+ }
568
+ ];
569
+ }
570
+ function poiLayers(source, sourceLayer, colors, poi, typography) {
571
+ if (poi === "none") {
572
+ return [];
573
+ }
574
+ const minzoom = poi === "minimal" ? 15 : poi === "balanced" ? 13 : 11;
575
+ return [
576
+ {
577
+ id: "poi-labels",
578
+ type: "symbol",
579
+ source,
580
+ "source-layer": sourceLayer,
581
+ minzoom,
582
+ layout: {
583
+ "text-field": ["coalesce", ["get", "name:en"], ["get", "name"]],
584
+ "text-font": textFont(typography),
585
+ "text-size": ["interpolate", ["linear"], ["zoom"], minzoom, 10, 17, 12],
586
+ "text-letter-spacing": 0,
587
+ "text-padding": 2
588
+ },
589
+ paint: {
590
+ "text-color": mix(colors.textMuted, colors.accent, 0.22),
591
+ "text-halo-color": alpha(colors.canvas, 0.78),
592
+ "text-halo-width": 1
593
+ }
594
+ }
595
+ ];
596
+ }
597
+ function parkOpacity(density) {
598
+ return density === "clean" ? 0.64 : density === "balanced" ? 0.78 : 0.9;
599
+ }
600
+ function alpha(color, opacity) {
601
+ const { a, b, g, r } = hexToRgba(color);
602
+ return `rgba(${r}, ${g}, ${b}, ${roundAlpha(a * opacity)})`;
603
+ }
604
+ function mix(hexA, hexB, amount) {
605
+ const a = hexToRgb(hexA);
606
+ const b = hexToRgb(hexB);
607
+ const r = Math.round(a.r + (b.r - a.r) * amount);
608
+ const g = Math.round(a.g + (b.g - a.g) * amount);
609
+ const bValue = Math.round(a.b + (b.b - a.b) * amount);
610
+ return rgbToHex(r, g, bValue);
611
+ }
612
+ function hexToRgb(hex) {
613
+ const { b, g, r } = hexToRgba(hex);
614
+ return { b, g, r };
615
+ }
616
+ function hexToRgba(hex) {
617
+ const rgbMatch = hex.match(
618
+ /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/
619
+ );
620
+ if (rgbMatch) {
621
+ return {
622
+ a: roundAlpha(Number(rgbMatch[4] ?? 1)),
623
+ r: Math.round(Number(rgbMatch[1])),
624
+ g: Math.round(Number(rgbMatch[2])),
625
+ b: Math.round(Number(rgbMatch[3]))
626
+ };
627
+ }
628
+ const value = hex.replace("#", "");
629
+ const normalized = value.length === 3 || value.length === 4 ? value.split("").map((character) => character + character).join("") : value;
630
+ return {
631
+ a: normalized.length === 8 ? roundAlpha(Number.parseInt(normalized.slice(6, 8), 16) / 255) : 1,
632
+ r: Number.parseInt(normalized.slice(0, 2), 16),
633
+ g: Number.parseInt(normalized.slice(2, 4), 16),
634
+ b: Number.parseInt(normalized.slice(4, 6), 16)
635
+ };
636
+ }
637
+ function cssColor(hex) {
638
+ const value = hex.replace("#", "");
639
+ if (value.length !== 4 && value.length !== 8) {
640
+ return hex;
641
+ }
642
+ const { a, b, g, r } = hexToRgba(hex);
643
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
644
+ }
645
+ function roundAlpha(value) {
646
+ return Math.round(value * 1e3) / 1e3;
647
+ }
648
+ function rgbToHex(r, g, b) {
649
+ return `#${[r, g, b].map((value) => value.toString(16).padStart(2, "0")).join("")}`;
650
+ }
651
+ export {
652
+ createManifest,
653
+ createStyle,
654
+ createStyleFromProject,
655
+ defineTileflow,
656
+ getDefaultColors,
657
+ validateConfig
658
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@tileflow/core",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Typed Tileflow config and MapLibre style compiler.",
5
+ "type": "module",
6
+ "homepage": "https://tileflow.dev",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/tileflow/tileflow.git",
10
+ "directory": "packages/core"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/tileflow/tileflow/issues"
14
+ },
15
+ "keywords": [
16
+ "tileflow",
17
+ "maps",
18
+ "maplibre",
19
+ "tiles",
20
+ "styles"
21
+ ],
22
+ "main": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "default": "./dist/index.js"
32
+ }
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "zod": "^4.4.3"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup src/index.ts --format esm --dts --clean",
42
+ "typecheck": "tsc -p tsconfig.json --noEmit",
43
+ "lint": "tsc -p tsconfig.json --noEmit"
44
+ }
45
+ }