@expofp/offline 3.15.0 → 3.16.1
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.
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Box, projectLocalToGps } from '@expofp/geometry';
|
|
2
2
|
import debug from 'debug';
|
|
3
|
+
import { resolveMapTheme } from './map-theme.js';
|
|
4
|
+
import { createOfflineMapStyle, OFFLINE_GLYPH_FONTSTACK } from './offline-map-style.js';
|
|
3
5
|
const log = debug('efp:offline:generate-offline-map-data');
|
|
4
6
|
const MAPS_BASE_PATH = 'maps/';
|
|
5
7
|
const MAP_STYLE_PATH = `${MAPS_BASE_PATH}style.json`;
|
|
@@ -20,23 +22,24 @@ const MAP_BOUNDS_MIN_PADDING_FRACTION = 0.05;
|
|
|
20
22
|
const OFFLINE_PADDING_SAFETY = 1.5;
|
|
21
23
|
const DEFAULT_MAP_SOURCE_BASE = 'https://build.protomaps.com';
|
|
22
24
|
const DEFAULT_MAP_SOURCE_LOOKBACK_DAYS = 7;
|
|
23
|
-
|
|
25
|
+
// Protomaps ships one sprite per palette; the icons are baked light-on-dark or
|
|
26
|
+
// dark-on-light, so the sprite has to follow the theme. (The `white` set this
|
|
27
|
+
// replaced was white-on-white against the light basemap.)
|
|
28
|
+
const PROTOMAPS_SPRITE_BASE = 'https://protomaps.github.io/basemaps-assets/sprites/v4';
|
|
24
29
|
const PROTOMAPS_FONT_BASE = 'https://protomaps.github.io/basemaps-assets/fonts';
|
|
25
|
-
const
|
|
30
|
+
const MAP_GLYPHS_STYLE_URL = './fonts/{fontstack}/{range}.pbf';
|
|
26
31
|
const GLYPH_RANGES = createGlyphRanges(0, 8447);
|
|
27
|
-
const ENGLISH_LABEL_FIELD = [
|
|
28
|
-
'coalesce',
|
|
29
|
-
['get', 'name:en'],
|
|
30
|
-
['get', 'name_en'],
|
|
31
|
-
['get', 'name:latin'],
|
|
32
|
-
['get', 'name'],
|
|
33
|
-
];
|
|
34
32
|
const tempDirExitCleanups = new Map();
|
|
35
33
|
export function generateOfflineMapData(options) {
|
|
36
34
|
const maplibre = {
|
|
37
35
|
styleUrl: `./${MAP_STYLE_PATH}`,
|
|
38
36
|
offline: true,
|
|
39
37
|
};
|
|
38
|
+
// The drawing decides the palette and the drawing is frozen inside the
|
|
39
|
+
// archive, so the theme is resolved once here and baked into the style —
|
|
40
|
+
// the runtime has no basis to re-decide it offline.
|
|
41
|
+
const theme = deriveMapTheme(options.fpSvgContext);
|
|
42
|
+
log('Resolved offline basemap theme', theme);
|
|
40
43
|
async function* files() {
|
|
41
44
|
const bbox = derivePaddedVenueBbox(options.fpSvgContext);
|
|
42
45
|
log('Extracting PMTiles for bbox', bbox.join(','));
|
|
@@ -47,10 +50,15 @@ export function generateOfflineMapData(options) {
|
|
|
47
50
|
targetFilePath: MAP_PMTILES_PATH,
|
|
48
51
|
};
|
|
49
52
|
yield {
|
|
50
|
-
text: JSON.stringify(
|
|
53
|
+
text: JSON.stringify(createOfflineMapStyle({
|
|
54
|
+
theme,
|
|
55
|
+
tiles: MAP_PMTILES_STYLE_URL,
|
|
56
|
+
sprite: `./sprites/${spriteName(theme)}`,
|
|
57
|
+
glyphs: MAP_GLYPHS_STYLE_URL,
|
|
58
|
+
}), null, 2),
|
|
51
59
|
targetFilePath: MAP_STYLE_PATH,
|
|
52
60
|
};
|
|
53
|
-
yield* getSpriteAssets();
|
|
61
|
+
yield* getSpriteAssets(theme);
|
|
54
62
|
yield* getGlyphAssets();
|
|
55
63
|
}
|
|
56
64
|
finally {
|
|
@@ -291,22 +299,28 @@ async function runPmtiles(args, signal) {
|
|
|
291
299
|
});
|
|
292
300
|
});
|
|
293
301
|
}
|
|
294
|
-
function
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
302
|
+
function deriveMapTheme(ctx) {
|
|
303
|
+
const fpGeo = ctx.__fpGeo;
|
|
304
|
+
const fpSettings = ctx.__fpSettings;
|
|
305
|
+
return resolveMapTheme(fpGeo?.properties?.config?.style, fpSettings?.backgroundColor);
|
|
306
|
+
}
|
|
307
|
+
function spriteName(theme) {
|
|
308
|
+
return `protomaps-${theme}`;
|
|
309
|
+
}
|
|
310
|
+
function* getSpriteAssets(theme) {
|
|
311
|
+
const source = `${PROTOMAPS_SPRITE_BASE}/${theme}`;
|
|
312
|
+
const targetBase = `${MAPS_BASE_PATH}sprites/${spriteName(theme)}`;
|
|
313
|
+
yield { url: new URL(`${source}.json`), targetFilePath: `${targetBase}.json` };
|
|
314
|
+
yield { url: new URL(`${source}.png`), targetFilePath: `${targetBase}.png` };
|
|
315
|
+
yield { url: new URL(`${source}@2x.json`), targetFilePath: `${targetBase}@2x.json` };
|
|
316
|
+
yield { url: new URL(`${source}@2x.png`), targetFilePath: `${targetBase}@2x.png` };
|
|
303
317
|
}
|
|
304
318
|
function* getGlyphAssets() {
|
|
305
|
-
const encodedFontstack = encodeURIComponent(
|
|
319
|
+
const encodedFontstack = encodeURIComponent(OFFLINE_GLYPH_FONTSTACK);
|
|
306
320
|
for (const range of GLYPH_RANGES) {
|
|
307
321
|
yield {
|
|
308
322
|
url: new URL(`${PROTOMAPS_FONT_BASE}/${encodedFontstack}/${range}.pbf`),
|
|
309
|
-
targetFilePath: `${MAPS_BASE_PATH}fonts/${
|
|
323
|
+
targetFilePath: `${MAPS_BASE_PATH}fonts/${OFFLINE_GLYPH_FONTSTACK}/${range}.pbf`,
|
|
310
324
|
};
|
|
311
325
|
}
|
|
312
326
|
}
|
|
@@ -317,187 +331,6 @@ function createGlyphRanges(min, max) {
|
|
|
317
331
|
}
|
|
318
332
|
return ranges;
|
|
319
333
|
}
|
|
320
|
-
function createMapStyle() {
|
|
321
|
-
return {
|
|
322
|
-
version: 8,
|
|
323
|
-
sources: {
|
|
324
|
-
protomaps: {
|
|
325
|
-
type: 'vector',
|
|
326
|
-
attribution: '<a href="https://github.com/protomaps/basemaps">Protomaps</a> © <a href="https://openstreetmap.org">OpenStreetMap</a>',
|
|
327
|
-
url: `pmtiles://${MAP_PMTILES_STYLE_URL}`,
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
sprite: './sprites/protomaps-white',
|
|
331
|
-
glyphs: './fonts/{fontstack}/{range}.pbf',
|
|
332
|
-
layers: [
|
|
333
|
-
{
|
|
334
|
-
id: 'background',
|
|
335
|
-
type: 'background',
|
|
336
|
-
paint: { 'background-color': '#f5f1ef' },
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
id: 'earth',
|
|
340
|
-
type: 'fill',
|
|
341
|
-
source: 'protomaps',
|
|
342
|
-
'source-layer': 'earth',
|
|
343
|
-
paint: { 'fill-color': '#f5f1ef' },
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
id: 'landuse',
|
|
347
|
-
type: 'fill',
|
|
348
|
-
source: 'protomaps',
|
|
349
|
-
'source-layer': 'landuse',
|
|
350
|
-
paint: {
|
|
351
|
-
'fill-color': [
|
|
352
|
-
'match',
|
|
353
|
-
['get', 'kind'],
|
|
354
|
-
['park', 'nature_reserve', 'forest', 'wood', 'grass'],
|
|
355
|
-
'#bdebb3',
|
|
356
|
-
['hospital'],
|
|
357
|
-
'#f2dfdf',
|
|
358
|
-
['school', 'university'],
|
|
359
|
-
'#e6dccf',
|
|
360
|
-
['industrial'],
|
|
361
|
-
'#e2e3eb',
|
|
362
|
-
['commercial'],
|
|
363
|
-
'#f5efdf',
|
|
364
|
-
'#e3d9d4',
|
|
365
|
-
],
|
|
366
|
-
'fill-opacity': 0.75,
|
|
367
|
-
},
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
id: 'water',
|
|
371
|
-
type: 'fill',
|
|
372
|
-
source: 'protomaps',
|
|
373
|
-
'source-layer': 'water',
|
|
374
|
-
paint: { 'fill-color': '#99ddff' },
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
id: 'buildings',
|
|
378
|
-
type: 'fill',
|
|
379
|
-
source: 'protomaps',
|
|
380
|
-
'source-layer': 'buildings',
|
|
381
|
-
minzoom: 13,
|
|
382
|
-
paint: { 'fill-color': '#ded6d3', 'fill-opacity': 0.75 },
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
id: 'roads-minor-casing',
|
|
386
|
-
type: 'line',
|
|
387
|
-
source: 'protomaps',
|
|
388
|
-
'source-layer': 'roads',
|
|
389
|
-
minzoom: 12,
|
|
390
|
-
filter: ['in', ['get', 'kind'], ['literal', ['minor_road', 'path', 'other']]],
|
|
391
|
-
paint: {
|
|
392
|
-
'line-color': '#d2d6df',
|
|
393
|
-
'line-width': ['interpolate', ['linear'], ['zoom'], 12, 1, 16, 3.5, 19, 10],
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
id: 'roads-major-casing',
|
|
398
|
-
type: 'line',
|
|
399
|
-
source: 'protomaps',
|
|
400
|
-
'source-layer': 'roads',
|
|
401
|
-
filter: ['==', ['get', 'kind'], 'major_road'],
|
|
402
|
-
paint: {
|
|
403
|
-
'line-color': '#d2d6df',
|
|
404
|
-
'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1.4, 14, 4.5, 18, 14],
|
|
405
|
-
},
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
id: 'roads-highway-casing',
|
|
409
|
-
type: 'line',
|
|
410
|
-
source: 'protomaps',
|
|
411
|
-
'source-layer': 'roads',
|
|
412
|
-
filter: ['==', ['get', 'kind'], 'highway'],
|
|
413
|
-
paint: {
|
|
414
|
-
'line-color': '#f9fafc',
|
|
415
|
-
'line-width': ['interpolate', ['linear'], ['zoom'], 8, 1.6, 14, 5, 18, 16],
|
|
416
|
-
},
|
|
417
|
-
},
|
|
418
|
-
{
|
|
419
|
-
id: 'roads-minor',
|
|
420
|
-
type: 'line',
|
|
421
|
-
source: 'protomaps',
|
|
422
|
-
'source-layer': 'roads',
|
|
423
|
-
minzoom: 12,
|
|
424
|
-
filter: ['in', ['get', 'kind'], ['literal', ['minor_road', 'path', 'other']]],
|
|
425
|
-
paint: {
|
|
426
|
-
'line-color': '#ffffff',
|
|
427
|
-
'line-width': ['interpolate', ['linear'], ['zoom'], 12, 0.5, 16, 2.5, 19, 8],
|
|
428
|
-
},
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
id: 'roads-major',
|
|
432
|
-
type: 'line',
|
|
433
|
-
source: 'protomaps',
|
|
434
|
-
'source-layer': 'roads',
|
|
435
|
-
filter: ['==', ['get', 'kind'], 'major_road'],
|
|
436
|
-
paint: {
|
|
437
|
-
'line-color': '#ffffff',
|
|
438
|
-
'line-width': ['interpolate', ['linear'], ['zoom'], 8, 0.8, 14, 3, 18, 12],
|
|
439
|
-
},
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
id: 'roads-highway',
|
|
443
|
-
type: 'line',
|
|
444
|
-
source: 'protomaps',
|
|
445
|
-
'source-layer': 'roads',
|
|
446
|
-
filter: ['==', ['get', 'kind'], 'highway'],
|
|
447
|
-
paint: {
|
|
448
|
-
'line-color': '#ffb366',
|
|
449
|
-
'line-width': ['interpolate', ['linear'], ['zoom'], 8, 0.8, 14, 3, 18, 12],
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
id: 'road-labels',
|
|
454
|
-
type: 'symbol',
|
|
455
|
-
source: 'protomaps',
|
|
456
|
-
'source-layer': 'roads',
|
|
457
|
-
minzoom: 14,
|
|
458
|
-
filter: ['has', 'name'],
|
|
459
|
-
layout: {
|
|
460
|
-
'symbol-placement': 'line',
|
|
461
|
-
'text-field': ENGLISH_LABEL_FIELD,
|
|
462
|
-
'text-font': [GLYPH_FONTSTACK],
|
|
463
|
-
'text-size': ['interpolate', ['linear'], ['zoom'], 14, 10, 18, 13],
|
|
464
|
-
},
|
|
465
|
-
paint: {
|
|
466
|
-
'text-color': '#000000',
|
|
467
|
-
'text-halo-color': '#ffffff',
|
|
468
|
-
'text-halo-width': 1,
|
|
469
|
-
},
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
id: 'place-labels',
|
|
473
|
-
type: 'symbol',
|
|
474
|
-
source: 'protomaps',
|
|
475
|
-
'source-layer': 'places',
|
|
476
|
-
filter: ['has', 'name'],
|
|
477
|
-
layout: {
|
|
478
|
-
'icon-image': [
|
|
479
|
-
'step',
|
|
480
|
-
['zoom'],
|
|
481
|
-
['case', ['==', ['get', 'capital'], 'yes'], 'capital', 'townspot'],
|
|
482
|
-
8,
|
|
483
|
-
'',
|
|
484
|
-
],
|
|
485
|
-
'icon-size': 0.7,
|
|
486
|
-
'text-field': ENGLISH_LABEL_FIELD,
|
|
487
|
-
'text-font': [GLYPH_FONTSTACK],
|
|
488
|
-
'text-size': ['interpolate', ['linear'], ['zoom'], 6, 11, 14, 16],
|
|
489
|
-
'text-anchor': 'center',
|
|
490
|
-
'text-offset': [0, 0.8],
|
|
491
|
-
},
|
|
492
|
-
paint: {
|
|
493
|
-
'text-color': '#333333',
|
|
494
|
-
'text-halo-color': '#ffffff',
|
|
495
|
-
'text-halo-width': 1.25,
|
|
496
|
-
},
|
|
497
|
-
},
|
|
498
|
-
],
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
334
|
// Builds the two-anchor affine bridge (p0 -> anchor a, p2 -> anchor b) used to
|
|
502
335
|
// project the SVG-area corners into GPS. All GeoConfig parsing / SVG extraction /
|
|
503
336
|
// anchor validation stays at the callsite; only the pure projection math lives in
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type MapTheme } from '@expofp/schema';
|
|
2
|
+
/**
|
|
3
|
+
* The palette implied by the drawing's background color — the fallback for
|
|
4
|
+
* drawings that predate `__fpGeo.properties.config.style`.
|
|
5
|
+
*
|
|
6
|
+
* `'none'` is a real authored value and is not a parseable color, so it and
|
|
7
|
+
* anything else unparseable resolve to the light palette.
|
|
8
|
+
*/
|
|
9
|
+
export declare function deriveMapThemeFromBackground(backgroundColor: string | undefined): MapTheme;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the basemap palette: the one the drawing declares in its geo config,
|
|
12
|
+
* else the one its background color implies.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveMapTheme(drawingTheme: MapTheme | undefined, backgroundColor: string | undefined): MapTheme;
|
|
15
|
+
//# sourceMappingURL=map-theme.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import Color from 'color';
|
|
2
|
+
/**
|
|
3
|
+
* Which basemap palette a drawing is authored for.
|
|
4
|
+
*
|
|
5
|
+
* A deliberate duplicate of the floorplan's
|
|
6
|
+
* `components/Maplibre/utils/map-theme.ts`. The two must agree: online the SDK
|
|
7
|
+
* resolves the palette at runtime, offline the generator resolves it once and
|
|
8
|
+
* bakes the answer into the archived style, and a drawing must not change
|
|
9
|
+
* appearance just because it was taken offline.
|
|
10
|
+
*
|
|
11
|
+
* The background-color fallback below is on its way out; when it goes, both
|
|
12
|
+
* copies collapse to reading the declared style and this file loses its only
|
|
13
|
+
* non-trivial logic.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Below this HSL lightness (0–100) a background counts as dark. Matches the
|
|
17
|
+
* floorplan's threshold — see the note there on why HSL and not WCAG luminance.
|
|
18
|
+
*/
|
|
19
|
+
const DARK_BACKGROUND_LIGHTNESS = 50;
|
|
20
|
+
function isMapTheme(value) {
|
|
21
|
+
return value === 'light' || value === 'dark';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The palette implied by the drawing's background color — the fallback for
|
|
25
|
+
* drawings that predate `__fpGeo.properties.config.style`.
|
|
26
|
+
*
|
|
27
|
+
* `'none'` is a real authored value and is not a parseable color, so it and
|
|
28
|
+
* anything else unparseable resolve to the light palette.
|
|
29
|
+
*/
|
|
30
|
+
export function deriveMapThemeFromBackground(backgroundColor) {
|
|
31
|
+
if (!backgroundColor || backgroundColor === 'none')
|
|
32
|
+
return 'light';
|
|
33
|
+
try {
|
|
34
|
+
return Color(backgroundColor).lightness() < DARK_BACKGROUND_LIGHTNESS ? 'dark' : 'light';
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return 'light';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the basemap palette: the one the drawing declares in its geo config,
|
|
42
|
+
* else the one its background color implies.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveMapTheme(drawingTheme, backgroundColor) {
|
|
45
|
+
if (isMapTheme(drawingTheme))
|
|
46
|
+
return drawingTheme;
|
|
47
|
+
return deriveMapThemeFromBackground(backgroundColor);
|
|
48
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type MapTheme } from '@expofp/schema';
|
|
2
|
+
import { type Flavor } from '@protomaps/basemaps';
|
|
3
|
+
/** The one font stack every label in {@link createOfflineMapStyle} asks for. */
|
|
4
|
+
export declare const OFFLINE_GLYPH_FONTSTACK = "Noto Sans Regular";
|
|
5
|
+
/**
|
|
6
|
+
* The Protomaps flavor recolored to CARTO Positron.
|
|
7
|
+
*
|
|
8
|
+
* `pois` is deliberately absent: it is optional, and leaving it out drops the
|
|
9
|
+
* colored POI icon layers entirely — the CARTO styles show no POI icons, so
|
|
10
|
+
* emitting them would be the loudest departure from the online look.
|
|
11
|
+
*/
|
|
12
|
+
export declare const POSITRON_FLAVOR: Flavor;
|
|
13
|
+
/**
|
|
14
|
+
* The Protomaps flavor recolored to CARTO Dark Matter.
|
|
15
|
+
*
|
|
16
|
+
* Dark Matter tints no landuse at all — its parks, landcover and residential
|
|
17
|
+
* blocks are all painted the background color — so every land field here
|
|
18
|
+
* collapses onto {@link DARK_MATTER_BACKGROUND}. That is the intended look, not
|
|
19
|
+
* an omission.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DARK_MATTER_FLAVOR: Flavor;
|
|
22
|
+
export interface OfflineMapStyleOptions {
|
|
23
|
+
/** Basemap palette the drawing is authored for. */
|
|
24
|
+
theme: MapTheme;
|
|
25
|
+
/** Tile source URL, relative to the style document. */
|
|
26
|
+
tiles: string;
|
|
27
|
+
/** Sprite base URL, relative to the style document. */
|
|
28
|
+
sprite: string;
|
|
29
|
+
/** Glyph template URL, relative to the style document. */
|
|
30
|
+
glyphs: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build the offline style document.
|
|
34
|
+
*
|
|
35
|
+
* URLs stay relative to the style so the archive is relocatable; the SDK
|
|
36
|
+
* absolutizes them at load time (see `MapLibreConfigSchema`).
|
|
37
|
+
*/
|
|
38
|
+
export declare function createOfflineMapStyle(options: OfflineMapStyleOptions): {
|
|
39
|
+
version: number;
|
|
40
|
+
sources: {
|
|
41
|
+
protomaps: {
|
|
42
|
+
type: string;
|
|
43
|
+
attribution: string;
|
|
44
|
+
url: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
sprite: string;
|
|
48
|
+
glyphs: string;
|
|
49
|
+
layers: LayerSpecification[];
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=offline-map-style.d.ts.map
|
|
@@ -0,0 +1,283 @@
|
|
|
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 the CARTO pair (`map-helpers.ts` in the floorplan):
|
|
6
|
+
* Positron for the light theme, Dark Matter for the dark one. Those are
|
|
7
|
+
* OpenMapTiles-schema styles; offline tiles come from a Protomaps basemap
|
|
8
|
+
* build, a different schema, so the *style* cannot be shared — only the
|
|
9
|
+
* palette. This module therefore renders the upstream Protomaps layer set
|
|
10
|
+
* (`@protomaps/basemaps`, kept current with the tile schema) through flavors
|
|
11
|
+
* whose colors are lifted from the two CARTO styles.
|
|
12
|
+
*
|
|
13
|
+
* Parity is visual, not pixel-exact: the two schemas classify roads, landuse
|
|
14
|
+
* and labels differently, so features are colored to the nearest CARTO
|
|
15
|
+
* equivalent rather than reproduced stop for stop.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Backgrounds the two CARTO styles paint their land on. Fills that CARTO makes
|
|
19
|
+
* translucent are pre-blended over these, because the Protomaps layers paint
|
|
20
|
+
* them opaque.
|
|
21
|
+
*/
|
|
22
|
+
const POSITRON_BACKGROUND = '#fafaf8';
|
|
23
|
+
const DARK_MATTER_BACKGROUND = '#0e0e0e';
|
|
24
|
+
/**
|
|
25
|
+
* Protomaps draws buildings at 50% opacity; these are the fills that land on
|
|
26
|
+
* the CARTO building color once blended over the background.
|
|
27
|
+
*
|
|
28
|
+
* The target is CARTO's `building-top` (`#ededed` light, `#393939` dark), not
|
|
29
|
+
* its `building`: the latter is the extruded wall — `transparent` in Dark
|
|
30
|
+
* Matter — visible only as a sliver along the offset edge, so matching it
|
|
31
|
+
* would paint every roof wrong.
|
|
32
|
+
*/
|
|
33
|
+
const POSITRON_BUILDING_FILL = '#e0e0e2';
|
|
34
|
+
const DARK_MATTER_BUILDING_FILL = '#646464';
|
|
35
|
+
/**
|
|
36
|
+
* A single font stack for every label.
|
|
37
|
+
*
|
|
38
|
+
* The Protomaps layers ask for Regular, Medium and Italic; pinning all three to
|
|
39
|
+
* Regular keeps the offline archive at one downloaded glyph set instead of
|
|
40
|
+
* three, at the cost of unemphasized city and shield labels. The CARTO styles'
|
|
41
|
+
* own label weights are light throughout, so the flattening is barely visible.
|
|
42
|
+
*/
|
|
43
|
+
const GLYPH_FONTSTACK = 'Noto Sans Regular';
|
|
44
|
+
/** The one font stack every label in {@link createOfflineMapStyle} asks for. */
|
|
45
|
+
export const OFFLINE_GLYPH_FONTSTACK = GLYPH_FONTSTACK;
|
|
46
|
+
/** CARTO Positron's road casing (`#ddd`) and its paler low-zoom variant. */
|
|
47
|
+
const LIGHT_ROAD_CASING = '#dddddd';
|
|
48
|
+
const LIGHT_ROAD_CASING_LOW_ZOOM = '#e6e6e6';
|
|
49
|
+
/** Positron road fills: `#fdfdfd` for minor roads, pure white for the rest. */
|
|
50
|
+
const LIGHT_ROAD_MINOR_FILL = '#fdfdfd';
|
|
51
|
+
const LIGHT_ROAD_MAJOR_FILL = '#ffffff';
|
|
52
|
+
/** Positron's translucent green landuse and grey residential, pre-blended. */
|
|
53
|
+
const LIGHT_GREEN_FILL = '#f2f6f1';
|
|
54
|
+
const LIGHT_GREY_FILL = '#f6f6f5';
|
|
55
|
+
/**
|
|
56
|
+
* The Protomaps flavor recolored to CARTO Positron.
|
|
57
|
+
*
|
|
58
|
+
* `pois` is deliberately absent: it is optional, and leaving it out drops the
|
|
59
|
+
* colored POI icon layers entirely — the CARTO styles show no POI icons, so
|
|
60
|
+
* emitting them would be the loudest departure from the online look.
|
|
61
|
+
*/
|
|
62
|
+
export const POSITRON_FLAVOR = {
|
|
63
|
+
background: POSITRON_BACKGROUND,
|
|
64
|
+
earth: POSITRON_BACKGROUND,
|
|
65
|
+
park_a: LIGHT_GREEN_FILL,
|
|
66
|
+
park_b: LIGHT_GREEN_FILL,
|
|
67
|
+
hospital: LIGHT_GREY_FILL,
|
|
68
|
+
industrial: LIGHT_GREY_FILL,
|
|
69
|
+
school: LIGHT_GREY_FILL,
|
|
70
|
+
wood_a: LIGHT_GREEN_FILL,
|
|
71
|
+
wood_b: LIGHT_GREEN_FILL,
|
|
72
|
+
pedestrian: '#f4f4f2',
|
|
73
|
+
scrub_a: LIGHT_GREEN_FILL,
|
|
74
|
+
scrub_b: LIGHT_GREEN_FILL,
|
|
75
|
+
glacier: '#f7f7f7',
|
|
76
|
+
sand: '#f7f6f2',
|
|
77
|
+
beach: '#f7f6f2',
|
|
78
|
+
aerodrome: '#f4f4f4',
|
|
79
|
+
runway: '#e8e8e8',
|
|
80
|
+
water: '#d4dadc',
|
|
81
|
+
zoo: LIGHT_GREEN_FILL,
|
|
82
|
+
military: LIGHT_GREEN_FILL,
|
|
83
|
+
tunnel_other_casing: LIGHT_ROAD_CASING,
|
|
84
|
+
tunnel_minor_casing: LIGHT_ROAD_CASING,
|
|
85
|
+
tunnel_link_casing: LIGHT_ROAD_CASING,
|
|
86
|
+
tunnel_major_casing: LIGHT_ROAD_CASING,
|
|
87
|
+
tunnel_highway_casing: LIGHT_ROAD_CASING,
|
|
88
|
+
tunnel_other: '#eeeeee',
|
|
89
|
+
tunnel_minor: '#eeeeee',
|
|
90
|
+
tunnel_link: '#eeeeee',
|
|
91
|
+
tunnel_major: '#eeeeee',
|
|
92
|
+
tunnel_highway: '#eeeeee',
|
|
93
|
+
pier: '#f0f0ee',
|
|
94
|
+
buildings: POSITRON_BUILDING_FILL,
|
|
95
|
+
minor_service_casing: LIGHT_ROAD_CASING,
|
|
96
|
+
minor_casing: LIGHT_ROAD_CASING,
|
|
97
|
+
link_casing: LIGHT_ROAD_CASING,
|
|
98
|
+
major_casing_late: LIGHT_ROAD_CASING,
|
|
99
|
+
highway_casing_late: LIGHT_ROAD_CASING,
|
|
100
|
+
other: LIGHT_ROAD_MINOR_FILL,
|
|
101
|
+
minor_service: LIGHT_ROAD_MINOR_FILL,
|
|
102
|
+
// Minor roads fade from casing grey to their fill as they widen, the way
|
|
103
|
+
// Positron's thin low-zoom casings read before the white fill takes over.
|
|
104
|
+
minor_a: LIGHT_ROAD_CASING_LOW_ZOOM,
|
|
105
|
+
minor_b: LIGHT_ROAD_MINOR_FILL,
|
|
106
|
+
link: LIGHT_ROAD_MAJOR_FILL,
|
|
107
|
+
major_casing_early: LIGHT_ROAD_CASING,
|
|
108
|
+
major: LIGHT_ROAD_MAJOR_FILL,
|
|
109
|
+
highway_casing_early: LIGHT_ROAD_CASING,
|
|
110
|
+
highway: LIGHT_ROAD_MAJOR_FILL,
|
|
111
|
+
railway: '#dddddd',
|
|
112
|
+
boundaries: '#e1c5c7',
|
|
113
|
+
bridges_other_casing: LIGHT_ROAD_CASING,
|
|
114
|
+
bridges_minor_casing: LIGHT_ROAD_CASING,
|
|
115
|
+
bridges_link_casing: LIGHT_ROAD_CASING,
|
|
116
|
+
bridges_major_casing: LIGHT_ROAD_CASING,
|
|
117
|
+
bridges_highway_casing: LIGHT_ROAD_CASING,
|
|
118
|
+
bridges_other: LIGHT_ROAD_MINOR_FILL,
|
|
119
|
+
bridges_minor: LIGHT_ROAD_MINOR_FILL,
|
|
120
|
+
bridges_link: LIGHT_ROAD_MAJOR_FILL,
|
|
121
|
+
bridges_major: LIGHT_ROAD_MAJOR_FILL,
|
|
122
|
+
bridges_highway: LIGHT_ROAD_MAJOR_FILL,
|
|
123
|
+
roads_label_minor: '#838383',
|
|
124
|
+
roads_label_minor_halo: '#ffffff',
|
|
125
|
+
roads_label_major: '#838383',
|
|
126
|
+
roads_label_major_halo: '#ffffff',
|
|
127
|
+
ocean_label: '#abb6be',
|
|
128
|
+
subplace_label: '#697b89',
|
|
129
|
+
subplace_label_halo: '#ffffff',
|
|
130
|
+
city_label: '#697b89',
|
|
131
|
+
city_label_halo: '#ffffff',
|
|
132
|
+
state_label: '#97a4ae',
|
|
133
|
+
state_label_halo: POSITRON_BACKGROUND,
|
|
134
|
+
country_label: '#8a99a4',
|
|
135
|
+
address_label: '#a3a3a3',
|
|
136
|
+
address_label_halo: '#ffffff',
|
|
137
|
+
regular: GLYPH_FONTSTACK,
|
|
138
|
+
bold: GLYPH_FONTSTACK,
|
|
139
|
+
italic: GLYPH_FONTSTACK,
|
|
140
|
+
landcover: {
|
|
141
|
+
grassland: LIGHT_GREEN_FILL,
|
|
142
|
+
barren: '#f7f6f2',
|
|
143
|
+
urban_area: LIGHT_GREY_FILL,
|
|
144
|
+
farmland: LIGHT_GREEN_FILL,
|
|
145
|
+
glacier: '#f7f7f7',
|
|
146
|
+
scrub: LIGHT_GREEN_FILL,
|
|
147
|
+
forest: LIGHT_GREEN_FILL,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
/** Dark Matter's road bands: minor and secondary flat, primary and motorway lifted. */
|
|
151
|
+
const DARK_ROAD_MINOR_FILL = '#414758';
|
|
152
|
+
const DARK_ROAD_MAJOR_FILL = '#535666';
|
|
153
|
+
const DARK_ROAD_HIGHWAY_FILL = '#494949';
|
|
154
|
+
const DARK_ROAD_SERVICE_FILL = '#0b0b0b';
|
|
155
|
+
/** Dark Matter outlines its bigger roads; minor roads get no visible casing. */
|
|
156
|
+
const DARK_ROAD_CASING = '#232323';
|
|
157
|
+
const DARK_TUNNEL_CASING = '#1a1a1a';
|
|
158
|
+
/**
|
|
159
|
+
* The Protomaps flavor recolored to CARTO Dark Matter.
|
|
160
|
+
*
|
|
161
|
+
* Dark Matter tints no landuse at all — its parks, landcover and residential
|
|
162
|
+
* blocks are all painted the background color — so every land field here
|
|
163
|
+
* collapses onto {@link DARK_MATTER_BACKGROUND}. That is the intended look, not
|
|
164
|
+
* an omission.
|
|
165
|
+
*/
|
|
166
|
+
export const DARK_MATTER_FLAVOR = {
|
|
167
|
+
background: DARK_MATTER_BACKGROUND,
|
|
168
|
+
earth: DARK_MATTER_BACKGROUND,
|
|
169
|
+
park_a: DARK_MATTER_BACKGROUND,
|
|
170
|
+
park_b: DARK_MATTER_BACKGROUND,
|
|
171
|
+
hospital: DARK_MATTER_BACKGROUND,
|
|
172
|
+
industrial: DARK_MATTER_BACKGROUND,
|
|
173
|
+
school: DARK_MATTER_BACKGROUND,
|
|
174
|
+
wood_a: DARK_MATTER_BACKGROUND,
|
|
175
|
+
wood_b: DARK_MATTER_BACKGROUND,
|
|
176
|
+
pedestrian: DARK_MATTER_BACKGROUND,
|
|
177
|
+
scrub_a: DARK_MATTER_BACKGROUND,
|
|
178
|
+
scrub_b: DARK_MATTER_BACKGROUND,
|
|
179
|
+
glacier: DARK_MATTER_BACKGROUND,
|
|
180
|
+
sand: DARK_MATTER_BACKGROUND,
|
|
181
|
+
beach: DARK_MATTER_BACKGROUND,
|
|
182
|
+
aerodrome: DARK_MATTER_BACKGROUND,
|
|
183
|
+
runway: '#111111',
|
|
184
|
+
water: '#2c353c',
|
|
185
|
+
zoo: DARK_MATTER_BACKGROUND,
|
|
186
|
+
military: DARK_MATTER_BACKGROUND,
|
|
187
|
+
tunnel_other_casing: DARK_TUNNEL_CASING,
|
|
188
|
+
tunnel_minor_casing: DARK_TUNNEL_CASING,
|
|
189
|
+
tunnel_link_casing: DARK_TUNNEL_CASING,
|
|
190
|
+
tunnel_major_casing: DARK_ROAD_CASING,
|
|
191
|
+
tunnel_highway_casing: DARK_ROAD_CASING,
|
|
192
|
+
tunnel_other: '#161616',
|
|
193
|
+
tunnel_minor: '#161616',
|
|
194
|
+
tunnel_link: '#161616',
|
|
195
|
+
tunnel_major: DARK_ROAD_MINOR_FILL,
|
|
196
|
+
tunnel_highway: DARK_ROAD_MINOR_FILL,
|
|
197
|
+
pier: DARK_TUNNEL_CASING,
|
|
198
|
+
buildings: DARK_MATTER_BUILDING_FILL,
|
|
199
|
+
minor_service_casing: '#1c1c1c',
|
|
200
|
+
// Dark Matter paints the minor casing the same color as the fill, so minor
|
|
201
|
+
// roads read as one flat band rather than a lined one.
|
|
202
|
+
minor_casing: DARK_ROAD_MINOR_FILL,
|
|
203
|
+
link_casing: DARK_ROAD_CASING,
|
|
204
|
+
major_casing_late: DARK_ROAD_CASING,
|
|
205
|
+
highway_casing_late: DARK_ROAD_CASING,
|
|
206
|
+
other: DARK_ROAD_SERVICE_FILL,
|
|
207
|
+
minor_service: DARK_ROAD_SERVICE_FILL,
|
|
208
|
+
minor_a: DARK_ROAD_MINOR_FILL,
|
|
209
|
+
minor_b: DARK_ROAD_MINOR_FILL,
|
|
210
|
+
link: DARK_ROAD_MINOR_FILL,
|
|
211
|
+
major_casing_early: DARK_ROAD_CASING,
|
|
212
|
+
major: DARK_ROAD_MAJOR_FILL,
|
|
213
|
+
highway_casing_early: DARK_ROAD_CASING,
|
|
214
|
+
highway: DARK_ROAD_HIGHWAY_FILL,
|
|
215
|
+
railway: '#1a1a1a',
|
|
216
|
+
boundaries: '#5c5e5e',
|
|
217
|
+
// Deliberate deviation: Dark Matter fills most bridge decks near-black
|
|
218
|
+
// (`#0b0b0b`) and lets the casing carry them, which on the Protomaps layer
|
|
219
|
+
// set reads as a bridge that vanishes. Mirroring the road colors keeps
|
|
220
|
+
// bridges legible at the cost of an exact match on a feature that is rare
|
|
221
|
+
// inside a venue's bbox.
|
|
222
|
+
bridges_other_casing: DARK_ROAD_CASING,
|
|
223
|
+
bridges_minor_casing: DARK_ROAD_CASING,
|
|
224
|
+
bridges_link_casing: DARK_ROAD_CASING,
|
|
225
|
+
bridges_major_casing: DARK_ROAD_CASING,
|
|
226
|
+
bridges_highway_casing: DARK_ROAD_CASING,
|
|
227
|
+
bridges_other: DARK_ROAD_SERVICE_FILL,
|
|
228
|
+
bridges_minor: DARK_ROAD_MINOR_FILL,
|
|
229
|
+
bridges_link: DARK_ROAD_MINOR_FILL,
|
|
230
|
+
bridges_major: DARK_ROAD_MAJOR_FILL,
|
|
231
|
+
bridges_highway: DARK_ROAD_HIGHWAY_FILL,
|
|
232
|
+
roads_label_minor: '#b5b4b4',
|
|
233
|
+
roads_label_minor_halo: '#111111',
|
|
234
|
+
roads_label_major: '#b5b4b4',
|
|
235
|
+
roads_label_major_halo: '#111111',
|
|
236
|
+
ocean_label: '#6d7b81',
|
|
237
|
+
subplace_label: '#ccd0e4',
|
|
238
|
+
subplace_label_halo: '#222222',
|
|
239
|
+
city_label: '#ccd0e4',
|
|
240
|
+
city_label_halo: '#222222',
|
|
241
|
+
state_label: '#cbe6e6',
|
|
242
|
+
state_label_halo: '#222222',
|
|
243
|
+
country_label: '#9d9d9d',
|
|
244
|
+
address_label: '#515151',
|
|
245
|
+
address_label_halo: '#111111',
|
|
246
|
+
regular: GLYPH_FONTSTACK,
|
|
247
|
+
bold: GLYPH_FONTSTACK,
|
|
248
|
+
italic: GLYPH_FONTSTACK,
|
|
249
|
+
landcover: {
|
|
250
|
+
grassland: DARK_MATTER_BACKGROUND,
|
|
251
|
+
barren: DARK_MATTER_BACKGROUND,
|
|
252
|
+
urban_area: DARK_MATTER_BACKGROUND,
|
|
253
|
+
farmland: DARK_MATTER_BACKGROUND,
|
|
254
|
+
glacier: DARK_MATTER_BACKGROUND,
|
|
255
|
+
scrub: DARK_MATTER_BACKGROUND,
|
|
256
|
+
forest: DARK_MATTER_BACKGROUND,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
const FLAVORS = {
|
|
260
|
+
light: POSITRON_FLAVOR,
|
|
261
|
+
dark: DARK_MATTER_FLAVOR,
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Build the offline style document.
|
|
265
|
+
*
|
|
266
|
+
* URLs stay relative to the style so the archive is relocatable; the SDK
|
|
267
|
+
* absolutizes them at load time (see `MapLibreConfigSchema`).
|
|
268
|
+
*/
|
|
269
|
+
export function createOfflineMapStyle(options) {
|
|
270
|
+
return {
|
|
271
|
+
version: 8,
|
|
272
|
+
sources: {
|
|
273
|
+
protomaps: {
|
|
274
|
+
type: 'vector',
|
|
275
|
+
attribution: '<a href="https://github.com/protomaps/basemaps">Protomaps</a> © <a href="https://openstreetmap.org">OpenStreetMap</a>',
|
|
276
|
+
url: `pmtiles://${options.tiles}`,
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
sprite: options.sprite,
|
|
280
|
+
glyphs: options.glyphs,
|
|
281
|
+
layers: layers('protomaps', FLAVORS[options.theme], { lang: 'en' }),
|
|
282
|
+
};
|
|
283
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expofp/offline",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI tool for creating offline copies of ExpoFP floor plans",
|
|
6
6
|
"homepage": "https://developer.expofp.com/",
|
|
@@ -29,13 +29,18 @@
|
|
|
29
29
|
"!**/*.map"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"@protomaps/basemaps": "^5.7.2",
|
|
33
|
+
"color": "^3.1.2",
|
|
32
34
|
"debug": "^4.4.3",
|
|
33
35
|
"tslib": "^2.3.0",
|
|
34
|
-
"@expofp/
|
|
35
|
-
"@expofp/
|
|
36
|
-
"@expofp/
|
|
37
|
-
"@expofp/
|
|
38
|
-
"@expofp/
|
|
39
|
-
"@expofp/
|
|
36
|
+
"@expofp/floorplan": "3.16.1",
|
|
37
|
+
"@expofp/geometry": "3.16.1",
|
|
38
|
+
"@expofp/resolve": "3.16.1",
|
|
39
|
+
"@expofp/utils": "3.16.1",
|
|
40
|
+
"@expofp/config": "3.16.1",
|
|
41
|
+
"@expofp/schema": "3.16.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/color": "^3.0.7"
|
|
40
45
|
}
|
|
41
46
|
}
|