100 lines
3.8 KiB
TypeScript
100 lines
3.8 KiB
TypeScript
// Map style registry + rewriting of OpenFreeMap styles for self-hosted assets.
|
|
// Styles, fonts and sprites are served locally from /public (populated by
|
|
// scripts/download-map-assets.mjs); only vector tiles (/planet) and the ne2_shaded
|
|
// raster tiles stay remote on tiles.openfreemap.org.
|
|
|
|
export interface MapStyle {
|
|
id: string;
|
|
label: string;
|
|
}
|
|
|
|
export const STYLES: MapStyle[] = [
|
|
{ id: 'liberty', label: 'Liberty' },
|
|
{ id: 'bright', label: 'Bright' },
|
|
{ id: 'positron', label: 'Positron' },
|
|
{ id: 'dark', label: 'Dark' },
|
|
{ id: 'fiord', label: 'Fiord' },
|
|
];
|
|
|
|
export const DEFAULT_STYLE_ID = 'fiord';
|
|
|
|
// localStorage key for the persisted style choice.
|
|
export const STYLE_STORAGE_KEY = 'flight-ops-style';
|
|
|
|
const OFM_FONTS_PREFIX = 'https://tiles.openfreemap.org/fonts/';
|
|
const OFM_SPRITE_URL = 'https://tiles.openfreemap.org/sprites/ofm_f384/ofm';
|
|
// Sprite files are extracted flat into public/sprites/, so the sprite base URL
|
|
// (MapLibre appends .json / @2x.png) points straight at them.
|
|
export const LOCAL_SPRITE_URL = '/sprites/ofm';
|
|
|
|
// OFM styles ship no attribution; ODbL requires visible OSM credit.
|
|
export const ATTRIBUTION = '© OpenStreetMap contributors © OpenFreeMap';
|
|
|
|
export function isStyleId(id: string): boolean {
|
|
return STYLES.some((s) => s.id === id);
|
|
}
|
|
|
|
export function styleUrl(id: string): string {
|
|
return `/styles/${id}.json`;
|
|
}
|
|
|
|
// Remote fallback base for styles when the local asset pack is absent (fresh clone
|
|
// without `npm run assets:download` / no volume mount). The raw OFM styles already
|
|
// point glyphs/sprites at remote URLs, so they work as-is — only vector tiles were
|
|
// ever remote. A future self-hosted tile pack plugs into the same availability
|
|
// mechanism below (the manifest describes the whole local asset set).
|
|
export const REMOTE_STYLE_BASE = 'https://tiles.openfreemap.org/styles';
|
|
|
|
export function styleUrlFor(id: string, localAssetsAvailable: boolean): string {
|
|
return localAssetsAvailable ? `/styles/${id}.json` : `${REMOTE_STYLE_BASE}/${id}`;
|
|
}
|
|
|
|
// The manifest is written LAST by scripts/download-map-assets.mjs, so its presence
|
|
// means the whole pack (styles + fonts + sprites — and any future asset class) is in
|
|
// place. Probed once per page life via a module-level cached promise: any non-200 or
|
|
// network error → false (fall back to remote styles).
|
|
const MANIFEST_URL = '/map-assets-manifest.json';
|
|
let assetsProbePromise: Promise<boolean> | null = null;
|
|
|
|
export function localAssetsAvailable(): Promise<boolean> {
|
|
if (!assetsProbePromise) {
|
|
assetsProbePromise = fetch(MANIFEST_URL, { cache: 'no-store' })
|
|
.then((res) => res.ok)
|
|
.catch(() => false);
|
|
}
|
|
return assetsProbePromise;
|
|
}
|
|
|
|
type Source = Record<string, unknown>;
|
|
|
|
// Takes a raw OFM style object and returns a new one with glyphs/sprite pointed at the
|
|
// self-hosted copies under /fonts and /sprites. Only those two fields are rewritten —
|
|
// the vector tile url (/planet) and raster tiles (/natural_earth/...) stay remote.
|
|
// Sources without an attribution get one added (ODbL requires visible OSM credit).
|
|
export function rewriteStyle(raw: unknown): unknown {
|
|
if (typeof raw !== 'object' || raw === null) return raw;
|
|
const style = { ...(raw as Record<string, unknown>) };
|
|
|
|
const glyphs = style.glyphs;
|
|
if (typeof glyphs === 'string' && glyphs.startsWith(OFM_FONTS_PREFIX)) {
|
|
style.glyphs = '/fonts/' + glyphs.slice(OFM_FONTS_PREFIX.length);
|
|
}
|
|
|
|
if (style.sprite === OFM_SPRITE_URL) {
|
|
style.sprite = LOCAL_SPRITE_URL;
|
|
}
|
|
|
|
const sources = style.sources;
|
|
if (sources && typeof sources === 'object') {
|
|
const next: Record<string, unknown> = { ...(sources as Source) };
|
|
for (const [id, src] of Object.entries(next)) {
|
|
if (src && typeof src === 'object' && !('attribution' in (src as Source))) {
|
|
next[id] = { ...(src as Source), attribution: ATTRIBUTION };
|
|
}
|
|
}
|
|
style.sources = next;
|
|
}
|
|
|
|
return style;
|
|
}
|