- scripts/custom-layers.mjs: hand-authored, self-documenting config for custom zones (polygons) and points of interest, shipped with example Tulsa POIs and district zones plus a copy-paste template - osm-data.mjs: merges custom layers into static/data/custom/<id>.geojson and includes them in the _index.json manifest with category, layerType, and color metadata - MapView.svelte: renders each manifest entry as an independent toggleable Leaflet layer with per-layer styling and an on-map legend panel - +page.svelte: loads all data sources (OSM + custom) from the manifest
70 lines
2.0 KiB
Svelte
70 lines
2.0 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import MapView, { type MapLayer } from '$lib/components/MapView.svelte';
|
|
|
|
type AreaManifest = MapLayer;
|
|
|
|
let areas = $state<MapLayer[]>([]);
|
|
let loading = $state(true);
|
|
let manifestError = $state<string | null>(null);
|
|
|
|
onMount(async () => {
|
|
try {
|
|
const res = await fetch('/data/_index.json');
|
|
if (!res.ok) throw new Error(`manifest HTTP ${res.status}`);
|
|
areas = (await res.json()) as MapLayer[];
|
|
} catch (e) {
|
|
manifestError = (e as Error).message;
|
|
}
|
|
loading = false;
|
|
});
|
|
|
|
const totalFeatures = $derived(areas.reduce((n, a) => n + a.featureCount, 0));
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Navigator — OSM Map</title>
|
|
</svelte:head>
|
|
|
|
<div class="app">
|
|
<div class="controls">
|
|
<strong>({areas.length}) data sources</strong>
|
|
{#if totalFeatures > 0}
|
|
<span class="stats">{totalFeatures} features</span>
|
|
{/if}
|
|
<span class="hint">Toggle layers on the map (top-right).</span>
|
|
</div>
|
|
|
|
{#if manifestError}
|
|
<div class="error">
|
|
Could not load data manifest (<code>/data/_index.json</code>).<br />
|
|
Run <code>npm run build:data</code> to fetch OSM data and merge custom
|
|
layers, then rebuild.
|
|
<p class="detail">{manifestError}</p>
|
|
</div>
|
|
{:else if areas.length}
|
|
<MapView layers={areas} title="Navigator" />
|
|
{:else if !loading}
|
|
<div class="error">
|
|
No built data found. Run <code>npm run build:data</code> to fetch OSM
|
|
data and/or add zones & POIs in <code>scripts/custom-layers.mjs</code>.
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.app { display: grid; grid-template-rows: auto 1fr; height: 100vh; }
|
|
.controls {
|
|
display: flex; align-items: center; gap: 1.25rem;
|
|
padding: 0.75rem 1.25rem;
|
|
background: #1e293b; color: #e2e8f0;
|
|
font: 0.85rem system-ui, sans-serif; z-index: 500;
|
|
}
|
|
.controls strong { color: #fff; }
|
|
.stats { color: #94a3b8; }
|
|
.hint { color: #64748b; margin-left: auto; }
|
|
.error {
|
|
padding: 3rem 2rem; font: 0.95rem/1.5 system-ui, sans-serif; color: #b91c1c;
|
|
}
|
|
.detail { color: #64748b; font-size: 0.8rem; }
|
|
</style> |