@expofp/offline 0.0.0-experimental.cf063fc

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,284 @@
1
+ import { layers } from '@protomaps/basemaps';
2
+ /**
3
+ * The offline basemap style, built to look like the online one.
4
+ *
5
+ * Online the SDK serves OpenFreeMap's Positron/Dark pair recolored to the
6
+ * CARTO palettes (`basemap-palette.ts` in the floorplan): Positron for the
7
+ * light theme, Dark Matter for the dark one. Both are OpenMapTiles-schema
8
+ * styles; offline tiles come from a Protomaps basemap build, a different
9
+ * schema, so the *style* cannot be shared — only the palette. This module
10
+ * therefore renders the upstream Protomaps layer set (`@protomaps/basemaps`,
11
+ * kept current with the tile schema) through flavors whose colors are lifted
12
+ * from the two CARTO styles.
13
+ *
14
+ * Parity is visual, not pixel-exact: the two schemas classify roads, landuse
15
+ * and labels differently, so features are colored to the nearest CARTO
16
+ * equivalent rather than reproduced stop for stop.
17
+ */
18
+ /**
19
+ * Backgrounds the two CARTO styles paint their land on. Fills that CARTO makes
20
+ * translucent are pre-blended over these, because the Protomaps layers paint
21
+ * them opaque.
22
+ */
23
+ const POSITRON_BACKGROUND = '#fafaf8';
24
+ const DARK_MATTER_BACKGROUND = '#0e0e0e';
25
+ /**
26
+ * Protomaps draws buildings at 50% opacity; these are the fills that land on
27
+ * the CARTO building color once blended over the background.
28
+ *
29
+ * The target is CARTO's `building-top` (`#ededed` light, `#393939` dark), not
30
+ * its `building`: the latter is the extruded wall — `transparent` in Dark
31
+ * Matter — visible only as a sliver along the offset edge, so matching it
32
+ * would paint every roof wrong.
33
+ */
34
+ const POSITRON_BUILDING_FILL = '#e0e0e2';
35
+ const DARK_MATTER_BUILDING_FILL = '#646464';
36
+ /**
37
+ * A single font stack for every label.
38
+ *
39
+ * The Protomaps layers ask for Regular, Medium and Italic; pinning all three to
40
+ * Regular keeps the offline archive at one downloaded glyph set instead of
41
+ * three, at the cost of unemphasized city and shield labels. The CARTO styles'
42
+ * own label weights are light throughout, so the flattening is barely visible.
43
+ */
44
+ const GLYPH_FONTSTACK = 'Noto Sans Regular';
45
+ /** The one font stack every label in {@link createOfflineMapStyle} asks for. */
46
+ export const OFFLINE_GLYPH_FONTSTACK = GLYPH_FONTSTACK;
47
+ /** CARTO Positron's road casing (`#ddd`) and its paler low-zoom variant. */
48
+ const LIGHT_ROAD_CASING = '#dddddd';
49
+ const LIGHT_ROAD_CASING_LOW_ZOOM = '#e6e6e6';
50
+ /** Positron road fills: `#fdfdfd` for minor roads, pure white for the rest. */
51
+ const LIGHT_ROAD_MINOR_FILL = '#fdfdfd';
52
+ const LIGHT_ROAD_MAJOR_FILL = '#ffffff';
53
+ /** Positron's translucent green landuse and grey residential, pre-blended. */
54
+ const LIGHT_GREEN_FILL = '#f2f6f1';
55
+ const LIGHT_GREY_FILL = '#f6f6f5';
56
+ /**
57
+ * The Protomaps flavor recolored to CARTO Positron.
58
+ *
59
+ * `pois` is deliberately absent: it is optional, and leaving it out drops the
60
+ * colored POI icon layers entirely — the CARTO styles show no POI icons, so
61
+ * emitting them would be the loudest departure from the online look.
62
+ */
63
+ export const POSITRON_FLAVOR = {
64
+ background: POSITRON_BACKGROUND,
65
+ earth: POSITRON_BACKGROUND,
66
+ park_a: LIGHT_GREEN_FILL,
67
+ park_b: LIGHT_GREEN_FILL,
68
+ hospital: LIGHT_GREY_FILL,
69
+ industrial: LIGHT_GREY_FILL,
70
+ school: LIGHT_GREY_FILL,
71
+ wood_a: LIGHT_GREEN_FILL,
72
+ wood_b: LIGHT_GREEN_FILL,
73
+ pedestrian: '#f4f4f2',
74
+ scrub_a: LIGHT_GREEN_FILL,
75
+ scrub_b: LIGHT_GREEN_FILL,
76
+ glacier: '#f7f7f7',
77
+ sand: '#f7f6f2',
78
+ beach: '#f7f6f2',
79
+ aerodrome: '#f4f4f4',
80
+ runway: '#e8e8e8',
81
+ water: '#d4dadc',
82
+ zoo: LIGHT_GREEN_FILL,
83
+ military: LIGHT_GREEN_FILL,
84
+ tunnel_other_casing: LIGHT_ROAD_CASING,
85
+ tunnel_minor_casing: LIGHT_ROAD_CASING,
86
+ tunnel_link_casing: LIGHT_ROAD_CASING,
87
+ tunnel_major_casing: LIGHT_ROAD_CASING,
88
+ tunnel_highway_casing: LIGHT_ROAD_CASING,
89
+ tunnel_other: '#eeeeee',
90
+ tunnel_minor: '#eeeeee',
91
+ tunnel_link: '#eeeeee',
92
+ tunnel_major: '#eeeeee',
93
+ tunnel_highway: '#eeeeee',
94
+ pier: '#f0f0ee',
95
+ buildings: POSITRON_BUILDING_FILL,
96
+ minor_service_casing: LIGHT_ROAD_CASING,
97
+ minor_casing: LIGHT_ROAD_CASING,
98
+ link_casing: LIGHT_ROAD_CASING,
99
+ major_casing_late: LIGHT_ROAD_CASING,
100
+ highway_casing_late: LIGHT_ROAD_CASING,
101
+ other: LIGHT_ROAD_MINOR_FILL,
102
+ minor_service: LIGHT_ROAD_MINOR_FILL,
103
+ // Minor roads fade from casing grey to their fill as they widen, the way
104
+ // Positron's thin low-zoom casings read before the white fill takes over.
105
+ minor_a: LIGHT_ROAD_CASING_LOW_ZOOM,
106
+ minor_b: LIGHT_ROAD_MINOR_FILL,
107
+ link: LIGHT_ROAD_MAJOR_FILL,
108
+ major_casing_early: LIGHT_ROAD_CASING,
109
+ major: LIGHT_ROAD_MAJOR_FILL,
110
+ highway_casing_early: LIGHT_ROAD_CASING,
111
+ highway: LIGHT_ROAD_MAJOR_FILL,
112
+ railway: '#dddddd',
113
+ boundaries: '#e1c5c7',
114
+ bridges_other_casing: LIGHT_ROAD_CASING,
115
+ bridges_minor_casing: LIGHT_ROAD_CASING,
116
+ bridges_link_casing: LIGHT_ROAD_CASING,
117
+ bridges_major_casing: LIGHT_ROAD_CASING,
118
+ bridges_highway_casing: LIGHT_ROAD_CASING,
119
+ bridges_other: LIGHT_ROAD_MINOR_FILL,
120
+ bridges_minor: LIGHT_ROAD_MINOR_FILL,
121
+ bridges_link: LIGHT_ROAD_MAJOR_FILL,
122
+ bridges_major: LIGHT_ROAD_MAJOR_FILL,
123
+ bridges_highway: LIGHT_ROAD_MAJOR_FILL,
124
+ roads_label_minor: '#838383',
125
+ roads_label_minor_halo: '#ffffff',
126
+ roads_label_major: '#838383',
127
+ roads_label_major_halo: '#ffffff',
128
+ ocean_label: '#abb6be',
129
+ subplace_label: '#697b89',
130
+ subplace_label_halo: '#ffffff',
131
+ city_label: '#697b89',
132
+ city_label_halo: '#ffffff',
133
+ state_label: '#97a4ae',
134
+ state_label_halo: POSITRON_BACKGROUND,
135
+ country_label: '#8a99a4',
136
+ address_label: '#a3a3a3',
137
+ address_label_halo: '#ffffff',
138
+ regular: GLYPH_FONTSTACK,
139
+ bold: GLYPH_FONTSTACK,
140
+ italic: GLYPH_FONTSTACK,
141
+ landcover: {
142
+ grassland: LIGHT_GREEN_FILL,
143
+ barren: '#f7f6f2',
144
+ urban_area: LIGHT_GREY_FILL,
145
+ farmland: LIGHT_GREEN_FILL,
146
+ glacier: '#f7f7f7',
147
+ scrub: LIGHT_GREEN_FILL,
148
+ forest: LIGHT_GREEN_FILL,
149
+ },
150
+ };
151
+ /** Dark Matter's road bands: minor and secondary flat, primary and motorway lifted. */
152
+ const DARK_ROAD_MINOR_FILL = '#414758';
153
+ const DARK_ROAD_MAJOR_FILL = '#535666';
154
+ const DARK_ROAD_HIGHWAY_FILL = '#494949';
155
+ const DARK_ROAD_SERVICE_FILL = '#0b0b0b';
156
+ /** Dark Matter outlines its bigger roads; minor roads get no visible casing. */
157
+ const DARK_ROAD_CASING = '#232323';
158
+ const DARK_TUNNEL_CASING = '#1a1a1a';
159
+ /**
160
+ * The Protomaps flavor recolored to CARTO Dark Matter.
161
+ *
162
+ * Dark Matter tints no landuse at all — its parks, landcover and residential
163
+ * blocks are all painted the background color — so every land field here
164
+ * collapses onto {@link DARK_MATTER_BACKGROUND}. That is the intended look, not
165
+ * an omission.
166
+ */
167
+ export const DARK_MATTER_FLAVOR = {
168
+ background: DARK_MATTER_BACKGROUND,
169
+ earth: DARK_MATTER_BACKGROUND,
170
+ park_a: DARK_MATTER_BACKGROUND,
171
+ park_b: DARK_MATTER_BACKGROUND,
172
+ hospital: DARK_MATTER_BACKGROUND,
173
+ industrial: DARK_MATTER_BACKGROUND,
174
+ school: DARK_MATTER_BACKGROUND,
175
+ wood_a: DARK_MATTER_BACKGROUND,
176
+ wood_b: DARK_MATTER_BACKGROUND,
177
+ pedestrian: DARK_MATTER_BACKGROUND,
178
+ scrub_a: DARK_MATTER_BACKGROUND,
179
+ scrub_b: DARK_MATTER_BACKGROUND,
180
+ glacier: DARK_MATTER_BACKGROUND,
181
+ sand: DARK_MATTER_BACKGROUND,
182
+ beach: DARK_MATTER_BACKGROUND,
183
+ aerodrome: DARK_MATTER_BACKGROUND,
184
+ runway: '#111111',
185
+ water: '#2c353c',
186
+ zoo: DARK_MATTER_BACKGROUND,
187
+ military: DARK_MATTER_BACKGROUND,
188
+ tunnel_other_casing: DARK_TUNNEL_CASING,
189
+ tunnel_minor_casing: DARK_TUNNEL_CASING,
190
+ tunnel_link_casing: DARK_TUNNEL_CASING,
191
+ tunnel_major_casing: DARK_ROAD_CASING,
192
+ tunnel_highway_casing: DARK_ROAD_CASING,
193
+ tunnel_other: '#161616',
194
+ tunnel_minor: '#161616',
195
+ tunnel_link: '#161616',
196
+ tunnel_major: DARK_ROAD_MINOR_FILL,
197
+ tunnel_highway: DARK_ROAD_MINOR_FILL,
198
+ pier: DARK_TUNNEL_CASING,
199
+ buildings: DARK_MATTER_BUILDING_FILL,
200
+ minor_service_casing: '#1c1c1c',
201
+ // Dark Matter paints the minor casing the same color as the fill, so minor
202
+ // roads read as one flat band rather than a lined one.
203
+ minor_casing: DARK_ROAD_MINOR_FILL,
204
+ link_casing: DARK_ROAD_CASING,
205
+ major_casing_late: DARK_ROAD_CASING,
206
+ highway_casing_late: DARK_ROAD_CASING,
207
+ other: DARK_ROAD_SERVICE_FILL,
208
+ minor_service: DARK_ROAD_SERVICE_FILL,
209
+ minor_a: DARK_ROAD_MINOR_FILL,
210
+ minor_b: DARK_ROAD_MINOR_FILL,
211
+ link: DARK_ROAD_MINOR_FILL,
212
+ major_casing_early: DARK_ROAD_CASING,
213
+ major: DARK_ROAD_MAJOR_FILL,
214
+ highway_casing_early: DARK_ROAD_CASING,
215
+ highway: DARK_ROAD_HIGHWAY_FILL,
216
+ railway: '#1a1a1a',
217
+ boundaries: '#5c5e5e',
218
+ // Deliberate deviation: Dark Matter fills most bridge decks near-black
219
+ // (`#0b0b0b`) and lets the casing carry them, which on the Protomaps layer
220
+ // set reads as a bridge that vanishes. Mirroring the road colors keeps
221
+ // bridges legible at the cost of an exact match on a feature that is rare
222
+ // inside a venue's bbox.
223
+ bridges_other_casing: DARK_ROAD_CASING,
224
+ bridges_minor_casing: DARK_ROAD_CASING,
225
+ bridges_link_casing: DARK_ROAD_CASING,
226
+ bridges_major_casing: DARK_ROAD_CASING,
227
+ bridges_highway_casing: DARK_ROAD_CASING,
228
+ bridges_other: DARK_ROAD_SERVICE_FILL,
229
+ bridges_minor: DARK_ROAD_MINOR_FILL,
230
+ bridges_link: DARK_ROAD_MINOR_FILL,
231
+ bridges_major: DARK_ROAD_MAJOR_FILL,
232
+ bridges_highway: DARK_ROAD_HIGHWAY_FILL,
233
+ roads_label_minor: '#b5b4b4',
234
+ roads_label_minor_halo: '#111111',
235
+ roads_label_major: '#b5b4b4',
236
+ roads_label_major_halo: '#111111',
237
+ ocean_label: '#6d7b81',
238
+ subplace_label: '#ccd0e4',
239
+ subplace_label_halo: '#222222',
240
+ city_label: '#ccd0e4',
241
+ city_label_halo: '#222222',
242
+ state_label: '#cbe6e6',
243
+ state_label_halo: '#222222',
244
+ country_label: '#9d9d9d',
245
+ address_label: '#515151',
246
+ address_label_halo: '#111111',
247
+ regular: GLYPH_FONTSTACK,
248
+ bold: GLYPH_FONTSTACK,
249
+ italic: GLYPH_FONTSTACK,
250
+ landcover: {
251
+ grassland: DARK_MATTER_BACKGROUND,
252
+ barren: DARK_MATTER_BACKGROUND,
253
+ urban_area: DARK_MATTER_BACKGROUND,
254
+ farmland: DARK_MATTER_BACKGROUND,
255
+ glacier: DARK_MATTER_BACKGROUND,
256
+ scrub: DARK_MATTER_BACKGROUND,
257
+ forest: DARK_MATTER_BACKGROUND,
258
+ },
259
+ };
260
+ const FLAVORS = {
261
+ light: POSITRON_FLAVOR,
262
+ dark: DARK_MATTER_FLAVOR,
263
+ };
264
+ /**
265
+ * Build the offline style document.
266
+ *
267
+ * URLs stay relative to the style so the archive is relocatable; the SDK
268
+ * absolutizes them at load time (see `MapLibreConfigSchema`).
269
+ */
270
+ export function createOfflineMapStyle(options) {
271
+ return {
272
+ version: 8,
273
+ sources: {
274
+ protomaps: {
275
+ type: 'vector',
276
+ attribution: '<a href="https://github.com/protomaps/basemaps">Protomaps</a> © <a href="https://openstreetmap.org">OpenStreetMap</a>',
277
+ url: `pmtiles://${options.tiles}`,
278
+ },
279
+ },
280
+ sprite: options.sprite,
281
+ glyphs: options.glyphs,
282
+ layers: layers('protomaps', FLAVORS[options.theme], { lang: 'en' }),
283
+ };
284
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Locates the `@expofp/floorplan` browser build directory on disk and
3
+ * returns a `file://` URL string (with trailing slash) pointing at it.
4
+ *
5
+ * Throws if the package or its `bundle.json` cannot be found.
6
+ */
7
+ export declare function resolveFloorplanDir(): Promise<string>;
8
+ //# sourceMappingURL=resolve-floorplan-dir.d.ts.map
@@ -0,0 +1,34 @@
1
+ import { createRequire } from 'node:module';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { importFsPromises } from '@expofp/utils';
5
+ /**
6
+ * Locates the `@expofp/floorplan` browser build directory on disk and
7
+ * returns a `file://` URL string (with trailing slash) pointing at it.
8
+ *
9
+ * Throws if the package or its `bundle.json` cannot be found.
10
+ */
11
+ export async function resolveFloorplanDir() {
12
+ const require = createRequire(import.meta.url);
13
+ let pkgJsonPath;
14
+ try {
15
+ pkgJsonPath = require.resolve('@expofp/floorplan/package.json');
16
+ }
17
+ catch {
18
+ throw new Error('Could not find @expofp/floorplan. ' +
19
+ 'Ensure it is installed (it should be a dependency of @expofp/offline).');
20
+ }
21
+ const pkgDir = path.dirname(pkgJsonPath);
22
+ const browserDir = path.join(pkgDir, 'dist', 'browser');
23
+ const bundleJsonPath = path.join(browserDir, 'bundle.json');
24
+ const fs = await importFsPromises();
25
+ try {
26
+ await fs.access(bundleJsonPath);
27
+ }
28
+ catch {
29
+ throw new Error(`Found @expofp/floorplan at ${pkgDir} but dist/browser/bundle.json is missing. ` +
30
+ 'Ensure the package has been built (pnpm nx build-browser @expofp/floorplan).');
31
+ }
32
+ // Return file:// URL with trailing slash so new URL(file, base) works
33
+ return pathToFileURL(browserDir).href + '/';
34
+ }
@@ -0,0 +1,5 @@
1
+ import { type Manifest } from '@expofp/config';
2
+ import { type Ref } from '@expofp/resolve';
3
+ import { type OfflineOptions } from './types.js';
4
+ export declare function saveOfflineZip(manifest: Manifest | Ref<Manifest>, path: string, options: OfflineOptions): Promise<void>;
5
+ //# sourceMappingURL=save-offline-zip.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { importFsPromises } from '@expofp/utils';
2
+ import debug from 'debug';
3
+ import { buildOfflineZip } from './generate-offline-data.js';
4
+ const log = debug('efp:offline:save-offline-zip');
5
+ export async function saveOfflineZip(manifest, path, options) {
6
+ if (typeof process === 'undefined' || !process.versions?.node) {
7
+ throw new Error('saveOfflineZip can only be used in a Node.js environment');
8
+ }
9
+ if (!path) {
10
+ throw new Error('Path is required to save the ZIP file');
11
+ }
12
+ if (!path.endsWith('.zip')) {
13
+ throw new Error('The specified path must end with .zip');
14
+ }
15
+ log('Saving offline ZIP bundle to', path);
16
+ const blob = await buildOfflineZip(manifest, options);
17
+ const arrayBuffer = await blob.arrayBuffer();
18
+ const fs = await importFsPromises();
19
+ await fs.writeFile(path, Buffer.from(arrayBuffer));
20
+ log('Saved offline ZIP bundle to', path);
21
+ }
@@ -0,0 +1,21 @@
1
+ export type LocalData = LocalDataFetch | LocalDataText;
2
+ export interface LocalDataFetch {
3
+ url: URL;
4
+ targetFilePath: string;
5
+ /** An expected-missing resource (fabricated variant URLs): skip, don't fail, when the download fails. */
6
+ optional?: boolean;
7
+ }
8
+ export interface LocalDataText {
9
+ text: string;
10
+ targetFilePath: string;
11
+ }
12
+ export interface OfflineOptions {
13
+ signal?: AbortSignal;
14
+ runtimeBaseUrl: string;
15
+ offlineMap?: OfflineMapOptions;
16
+ inferOfflineMap?: boolean;
17
+ }
18
+ export interface OfflineMapOptions {
19
+ mapSource?: string;
20
+ }
21
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@expofp/offline",
3
+ "version": "0.0.0-experimental.cf063fc",
4
+ "type": "module",
5
+ "description": "CLI tool for creating offline copies of ExpoFP floor plans",
6
+ "homepage": "https://developer.expofp.com/",
7
+ "license": "MIT",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "main": "./dist/index.js",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ "./package.json": "./package.json",
16
+ ".": {
17
+ "@expofp/source": "./src/index.ts",
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "default": "./dist/index.js"
21
+ }
22
+ },
23
+ "bin": {
24
+ "expofp-offline": "./dist/cli.js"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "!**/*.tsbuildinfo",
29
+ "!**/*.map"
30
+ ],
31
+ "dependencies": {
32
+ "@protomaps/basemaps": "^5.7.2",
33
+ "color": "^3.1.2",
34
+ "debug": "^4.4.3",
35
+ "tslib": "^2.3.0",
36
+ "@expofp/config": "0.0.0-experimental.cf063fc",
37
+ "@expofp/floorplan": "0.0.0-experimental.cf063fc",
38
+ "@expofp/geometry": "0.0.0-experimental.cf063fc",
39
+ "@expofp/resolve": "0.0.0-experimental.cf063fc",
40
+ "@expofp/schema": "0.0.0-experimental.cf063fc",
41
+ "@expofp/utils": "0.0.0-experimental.cf063fc"
42
+ },
43
+ "devDependencies": {
44
+ "@types/color": "^3.0.7"
45
+ }
46
+ }