@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 +25 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +658 -0
- package/package.json +45 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|