diff --git a/src/components/tiles/valhalla-layers-toggle.spec.tsx b/src/components/tiles/valhalla-layers-toggle.spec.tsx index 1c627cf0..f1e79deb 100644 --- a/src/components/tiles/valhalla-layers-toggle.spec.tsx +++ b/src/components/tiles/valhalla-layers-toggle.spec.tsx @@ -1,15 +1,14 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { render, screen, waitFor, act } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { LayerSpecification } from 'maplibre-gl'; import { ValhallaLayersToggle } from './valhalla-layers-toggle'; -import { - VALHALLA_SOURCE_ID, - VALHALLA_LAYERS, - VALHALLA_EDGES_LAYER_ID, - VALHALLA_NODES_LAYER_ID, - VALHALLA_SHORTCUTS_LAYER_ID, -} from './valhalla-layers'; +import { VALHALLA_SOURCE_ID, getValhallaStyle } from './valhalla-layers'; + +vi.mock('./valhalla-layers', () => ({ + VALHALLA_SOURCE_ID: 'valhalla-tiles', + getValhallaStyle: vi.fn(), +})); const createMockMap = () => { const sources: Record = {}; @@ -20,19 +19,11 @@ const createMockMap = () => { addSource: vi.fn((id: string, spec: unknown) => { sources[id] = spec; }), - removeSource: vi.fn((id: string) => { - delete sources[id]; - }), getLayer: vi.fn((id: string) => layers[id]), addLayer: vi.fn((layer: { id: string }) => { layers[layer.id] = layer; }), - removeLayer: vi.fn((id: string) => { - delete layers[id]; - }), setLayoutProperty: vi.fn(), - on: vi.fn(), - off: vi.fn(), _sources: sources, _layers: layers, }; @@ -64,10 +55,27 @@ describe('ValhallaLayersToggle', () => { mockMap = createMockMap(); mockMapReady = true; vi.clearAllMocks(); - }); - afterEach(() => { - vi.restoreAllMocks(); + vi.mocked(getValhallaStyle).mockResolvedValue({ + sources: { + [VALHALLA_SOURCE_ID]: { + type: 'vector', + tiles: ['https://example.com/tile'], + }, + }, + layers: [ + { + id: 'edges', + type: 'line', + source: VALHALLA_SOURCE_ID, + }, + { + id: 'nodes', + type: 'circle', + source: VALHALLA_SOURCE_ID, + }, + ], + }); }); describe('rendering', () => { @@ -102,59 +110,50 @@ describe('ValhallaLayersToggle', () => { }); describe('toggle functionality', () => { - it('should add source when toggled on', async () => { + it('should fetch valhalla style when toggled', async () => { const user = userEvent.setup(); render(); - const toggle = screen.getByRole('switch'); - await user.click(toggle); + await user.click(screen.getByRole('switch')); - expect(mockMap.addSource).toHaveBeenCalledWith( - VALHALLA_SOURCE_ID, - expect.objectContaining({ - type: 'vector', - tiles: expect.any(Array), - }) - ); - }); - - it('should add all layers when toggled on', async () => { - const user = userEvent.setup(); - render(); - - const toggle = screen.getByRole('switch'); - await user.click(toggle); - - expect(mockMap.addLayer).toHaveBeenCalledTimes(VALHALLA_LAYERS.length); - for (const layer of VALHALLA_LAYERS) { - expect(mockMap.addLayer).toHaveBeenCalledWith(layer); - } + await waitFor(() => { + expect(getValhallaStyle).toHaveBeenCalledTimes(1); + }); }); - it('should remove all layers when toggled off', async () => { + it('should add source when toggled on', async () => { const user = userEvent.setup(); render(); const toggle = screen.getByRole('switch'); await user.click(toggle); - await user.click(toggle); - expect(mockMap.removeLayer).toHaveBeenCalledWith(VALHALLA_EDGES_LAYER_ID); - expect(mockMap.removeLayer).toHaveBeenCalledWith( - VALHALLA_SHORTCUTS_LAYER_ID - ); - expect(mockMap.removeLayer).toHaveBeenCalledWith(VALHALLA_NODES_LAYER_ID); + await waitFor(() => { + expect(mockMap.addSource).toHaveBeenCalledWith( + VALHALLA_SOURCE_ID, + expect.objectContaining({ + type: 'vector', + tiles: expect.any(Array), + }) + ); + }); }); - it('should remove source when toggled off', async () => { + it('should add all style layers when toggled on', async () => { const user = userEvent.setup(); render(); const toggle = screen.getByRole('switch'); await user.click(toggle); - await user.click(toggle); - expect(mockMap.removeSource).toHaveBeenCalledWith(VALHALLA_SOURCE_ID); + await waitFor(() => { + expect(mockMap.addLayer).toHaveBeenCalledWith( + expect.objectContaining({ id: 'edges' }) + ); + expect(mockMap.addLayer).toHaveBeenCalledWith( + expect.objectContaining({ id: 'nodes' }) + ); + }); }); it('should not add source if already exists', async () => { @@ -166,23 +165,30 @@ describe('ValhallaLayersToggle', () => { const toggle = screen.getByRole('switch'); await user.click(toggle); - expect(mockMap.addSource).not.toHaveBeenCalled(); + await waitFor(() => { + expect(mockMap.addSource).not.toHaveBeenCalled(); + }); }); it('should not add layer if already exists', async () => { const user = userEvent.setup(); - mockMap._layers[VALHALLA_EDGES_LAYER_ID] = { - id: VALHALLA_EDGES_LAYER_ID, - }; + mockMap._layers.edges = { id: 'edges' }; render(); const toggle = screen.getByRole('switch'); await user.click(toggle); - expect(mockMap.addLayer).toHaveBeenCalledTimes( - VALHALLA_LAYERS.length - 1 + await waitFor(() => { + expect(mockMap.addLayer).toHaveBeenCalledWith( + expect.objectContaining({ id: 'nodes' }) + ); + }); + + const addLayerIds = mockMap.addLayer.mock.calls.map( + (call: [{ id: string }]) => call[0].id ); + expect(addLayerIds).not.toContain('edges'); }); it('should update checked state when toggled', async () => { @@ -198,72 +204,6 @@ describe('ValhallaLayersToggle', () => { }); }); - describe('style change handling', () => { - it('should register styledata event listener', () => { - render(); - - expect(mockMap.on).toHaveBeenCalledWith( - 'styledata', - expect.any(Function) - ); - }); - - it('should unregister styledata event listener on unmount', () => { - const { unmount } = render( - - ); - - unmount(); - - expect(mockMap.off).toHaveBeenCalledWith( - 'styledata', - expect.any(Function) - ); - }); - - it('should sync enabled state with source existence on style change', async () => { - render(); - - const styleDataHandler = mockMap.on.mock.calls.find( - (call) => call[0] === 'styledata' - )?.[1]; - - mockMap._sources[VALHALLA_SOURCE_ID] = { type: 'vector' }; - - await act(async () => { - styleDataHandler?.(); - }); - - await waitFor(() => { - expect(screen.getByRole('switch')).toBeChecked(); - }); - }); - - it('should set enabled to false when source is removed on style change', async () => { - const user = userEvent.setup(); - render(); - - const toggle = screen.getByRole('switch'); - await user.click(toggle); - - expect(toggle).toBeChecked(); - - const styleDataHandler = mockMap.on.mock.calls.find( - (call) => call[0] === 'styledata' - )?.[1]; - - delete mockMap._sources[VALHALLA_SOURCE_ID]; - - await act(async () => { - styleDataHandler?.(); - }); - - await waitFor(() => { - expect(screen.getByRole('switch')).not.toBeChecked(); - }); - }); - }); - describe('custom layer re-application on enable', () => { it('should re-add a custom layer referencing valhalla-tiles when toggled on', async () => { const user = userEvent.setup(); @@ -283,9 +223,11 @@ describe('ValhallaLayersToggle', () => { const toggle = screen.getByRole('switch'); await user.click(toggle); - expect(mockMap.addLayer).toHaveBeenCalledWith( - expect.objectContaining({ id: 'custom-valhalla-layer' }) - ); + await waitFor(() => { + expect(mockMap.addLayer).toHaveBeenCalledWith( + expect.objectContaining({ id: 'custom-valhalla-layer' }) + ); + }); }); it('should set visibility none for an invisible custom valhalla layer when re-added', async () => { @@ -306,11 +248,13 @@ describe('ValhallaLayersToggle', () => { const toggle = screen.getByRole('switch'); await user.click(toggle); - expect(mockMap.setLayoutProperty).toHaveBeenCalledWith( - 'custom-hidden-layer', - 'visibility', - 'none' - ); + await waitFor(() => { + expect(mockMap.setLayoutProperty).toHaveBeenCalledWith( + 'custom-hidden-layer', + 'visibility', + 'none' + ); + }); }); it('should not re-add a custom layer that uses a different source', async () => { @@ -364,7 +308,7 @@ describe('ValhallaLayersToggle', () => { expect(addLayerIds).not.toContain('already-present-custom'); }); - it('should call map.removeLayer for custom valhalla layers when Valhalla is toggled off', async () => { + it('should set custom valhalla layer visibility to none when toggled off', async () => { const user = userEvent.setup(); const customLayers = [ { @@ -379,17 +323,67 @@ describe('ValhallaLayersToggle', () => { render(); + const toggle = screen.getByRole('switch'); + await user.click(toggle); + await user.click(toggle); + + await waitFor(() => { + expect(mockMap.setLayoutProperty).toHaveBeenCalledWith( + 'custom-valhalla-layer', + 'visibility', + 'none' + ); + }); + }); + + it('should toggle built-in valhalla style layer visibility', async () => { + const user = userEvent.setup(); + mockMap._layers.edges = { id: 'edges' }; + mockMap._layers.shortcuts = { id: 'shortcuts' }; + mockMap._layers.nodes = { id: 'nodes' }; + + render(); + const toggle = screen.getByRole('switch'); await user.click(toggle); - mockMap.removeLayer.mockClear(); + await waitFor(() => { + expect(mockMap.setLayoutProperty).toHaveBeenCalledWith( + 'edges', + 'visibility', + 'visible' + ); + expect(mockMap.setLayoutProperty).toHaveBeenCalledWith( + 'shortcuts', + 'visibility', + 'visible' + ); + expect(mockMap.setLayoutProperty).toHaveBeenCalledWith( + 'nodes', + 'visibility', + 'visible' + ); + }); await user.click(toggle); - const removeLayerIds = mockMap.removeLayer.mock.calls.map( - (call: [string]) => call[0] - ); - expect(removeLayerIds).toContain('custom-valhalla-layer'); + await waitFor(() => { + expect(mockMap.setLayoutProperty).toHaveBeenCalledWith( + 'edges', + 'visibility', + 'none' + ); + expect(mockMap.setLayoutProperty).toHaveBeenCalledWith( + 'shortcuts', + 'visibility', + 'none' + ); + expect(mockMap.setLayoutProperty).toHaveBeenCalledWith( + 'nodes', + 'visibility', + 'none' + ); + }); }); }); }); diff --git a/src/components/tiles/valhalla-layers-toggle.tsx b/src/components/tiles/valhalla-layers-toggle.tsx index 9e23fd7e..35d50d12 100644 --- a/src/components/tiles/valhalla-layers-toggle.tsx +++ b/src/components/tiles/valhalla-layers-toggle.tsx @@ -1,14 +1,10 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { useMap } from 'react-map-gl/maplibre'; -import type { LayerSpecification } from 'maplibre-gl'; +import type { LayerSpecification, SourceSpecification } from 'maplibre-gl'; import { useCommonStore } from '@/stores/common-store'; import { Switch } from '@/components/ui/switch'; import { Label } from '@/components/ui/label'; -import { - VALHALLA_SOURCE_ID, - VALHALLA_LAYERS, - getValhallaSourceSpec, -} from './valhalla-layers'; +import { VALHALLA_SOURCE_ID, getValhallaStyle } from './valhalla-layers'; interface ValhallaLayersToggleProps { customLayers: { layer: LayerSpecification; visible: boolean }[]; @@ -21,77 +17,51 @@ export const ValhallaLayersToggle = ({ const mapReady = useCommonStore((state) => state.mapReady); const [enabled, setEnabled] = useState(false); - useEffect(() => { - if (!mainMap) return; - - const map = mainMap.getMap(); - - const handleStyleData = () => { - const hasSource = !!map.getSource(VALHALLA_SOURCE_ID); - setEnabled(hasSource); - }; - - map.on('styledata', handleStyleData); - - return () => { - map.off('styledata', handleStyleData); - }; - }, [mainMap]); - - const handleToggle = (checked: boolean) => { + const handleToggle = async (checked: boolean) => { if (!mainMap || !mapReady) return; const map = mainMap.getMap(); setEnabled(checked); - if (checked) { - if (!map.getSource(VALHALLA_SOURCE_ID)) { - map.addSource(VALHALLA_SOURCE_ID, getValhallaSourceSpec()); + const style = await getValhallaStyle(); + + Object.entries(style.sources).forEach(([sourceId, source]) => { + if (!map.getSource(sourceId)) { + map.addSource(sourceId, source as SourceSpecification); } - for (const layer of VALHALLA_LAYERS) { - if (!map.getLayer(layer.id)) { - map.addLayer(layer); - } + }); + + style.layers.forEach((layer: LayerSpecification) => { + if (!map.getLayer(layer.id)) { + map.addLayer(layer); } - for (const entry of customLayers) { - const layerSource = - 'source' in entry.layer ? entry.layer.source : undefined; - if ( - layerSource === VALHALLA_SOURCE_ID && - !map.getLayer(entry.layer.id) - ) { + }); + + for (const entry of customLayers) { + const layerSource = + 'source' in entry.layer ? entry.layer.source : undefined; + + if (layerSource === VALHALLA_SOURCE_ID) { + if (!map.getLayer(entry.layer.id)) { try { map.addLayer(entry.layer); - if (!entry.visible) { - map.setLayoutProperty(entry.layer.id, 'visibility', 'none'); - } } catch { - // skip + continue; } } + map.setLayoutProperty( + entry.layer.id, + 'visibility', + checked && entry.visible ? 'visible' : 'none' + ); } - } else { - for (const layer of VALHALLA_LAYERS) { - if (map.getLayer(layer.id)) { - map.removeLayer(layer.id); - } - } - - for (const entry of customLayers) { - const layerSource = - 'source' in entry.layer ? entry.layer.source : undefined; - if ( - layerSource === VALHALLA_SOURCE_ID && - map.getLayer(entry.layer.id) - ) { - map.removeLayer(entry.layer.id); - } - } + } - if (map.getSource(VALHALLA_SOURCE_ID)) { - map.removeSource(VALHALLA_SOURCE_ID); + ['edges', 'shortcuts', 'nodes'].forEach((id) => { + if (map.getLayer(id)) { + map.setLayoutProperty(id, 'visibility', checked ? 'visible' : 'none'); } - } + }); }; if (!mapReady) { diff --git a/src/components/tiles/valhalla-layers.spec.ts b/src/components/tiles/valhalla-layers.spec.ts index 600e0e02..d7381065 100644 --- a/src/components/tiles/valhalla-layers.spec.ts +++ b/src/components/tiles/valhalla-layers.spec.ts @@ -1,20 +1,13 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import type { - LineLayerSpecification, - CircleLayerSpecification, - VectorSourceSpecification, -} from 'maplibre-gl'; +import type { VectorSourceSpecification } from 'maplibre-gl'; import { VALHALLA_SOURCE_ID, VALHALLA_EDGES_LAYER_ID, VALHALLA_SHORTCUTS_LAYER_ID, VALHALLA_NODES_LAYER_ID, - VALHALLA_EDGES_LAYER, - VALHALLA_SHORTCUTS_LAYER, - VALHALLA_NODES_LAYER, - VALHALLA_LAYERS, getValhallaTileUrl, getValhallaSourceSpec, + getValhallaStyle, } from './valhalla-layers'; vi.mock('@/utils/base-url', () => ({ @@ -37,109 +30,53 @@ describe('valhalla-layers', () => { expect(VALHALLA_SHORTCUTS_LAYER_ID).toBe('valhalla-shortcuts'); expect(VALHALLA_NODES_LAYER_ID).toBe('valhalla-nodes'); }); - - it('should export VALHALLA_LAYERS array with all layers', () => { - expect(VALHALLA_LAYERS).toHaveLength(3); - expect(VALHALLA_LAYERS).toContain(VALHALLA_EDGES_LAYER); - expect(VALHALLA_LAYERS).toContain(VALHALLA_SHORTCUTS_LAYER); - expect(VALHALLA_LAYERS).toContain(VALHALLA_NODES_LAYER); - }); - }); - - describe('VALHALLA_EDGES_LAYER', () => { - const edgesLayer = VALHALLA_EDGES_LAYER as LineLayerSpecification; - - it('should have correct id', () => { - expect(edgesLayer.id).toBe(VALHALLA_EDGES_LAYER_ID); - }); - - it('should be a line type layer', () => { - expect(edgesLayer.type).toBe('line'); - }); - - it('should reference correct source', () => { - expect(edgesLayer.source).toBe(VALHALLA_SOURCE_ID); - }); - - it('should have edges source-layer', () => { - expect(edgesLayer['source-layer']).toBe('edges'); - }); - - it('should have correct zoom range', () => { - expect(edgesLayer.minzoom).toBe(7); - expect(edgesLayer.maxzoom).toBe(22); - }); - - it('should have visible layout', () => { - expect(edgesLayer.layout).toEqual({ visibility: 'visible' }); - }); - - it('should have paint properties', () => { - expect(edgesLayer.paint).toBeDefined(); - expect(edgesLayer.paint).toHaveProperty('line-color'); - expect(edgesLayer.paint).toHaveProperty('line-width'); - expect(edgesLayer.paint).toHaveProperty('line-opacity'); - }); }); - //Shortcut is now a separate map layer. - //It uses the same styling as edges. - describe('VALHALLA_SHORTCUTS_LAYER', () => { - const shortcutsLayer = VALHALLA_SHORTCUTS_LAYER as LineLayerSpecification; - - it('should have correct id', () => { - expect(shortcutsLayer.id).toBe(VALHALLA_SHORTCUTS_LAYER_ID); - }); - - it('should be a line type layer', () => { - expect(shortcutsLayer.type).toBe('line'); - }); - - it('should reference correct source', () => { - expect(shortcutsLayer.source).toBe(VALHALLA_SOURCE_ID); - }); - - it('should have shortcuts source-layer', () => { - expect(shortcutsLayer['source-layer']).toBe('shortcuts'); - }); - - it('should clone edges paint and layout styling', () => { - expect(shortcutsLayer.paint).toEqual(VALHALLA_EDGES_LAYER.paint); - expect(shortcutsLayer.layout).toEqual(VALHALLA_EDGES_LAYER.layout); - }); - }); - - describe('VALHALLA_NODES_LAYER', () => { - const nodesLayer = VALHALLA_NODES_LAYER as CircleLayerSpecification; - - it('should have correct id', () => { - expect(nodesLayer.id).toBe(VALHALLA_NODES_LAYER_ID); - }); - - it('should be a circle type layer', () => { - expect(nodesLayer.type).toBe('circle'); - }); - - it('should reference correct source', () => { - expect(nodesLayer.source).toBe(VALHALLA_SOURCE_ID); - }); - - it('should have nodes source-layer', () => { - expect(nodesLayer['source-layer']).toBe('nodes'); - }); - - it('should have correct zoom range', () => { - expect(nodesLayer.minzoom).toBe(16); - expect(nodesLayer.maxzoom).toBe(22); - }); - - it('should have paint properties', () => { - expect(nodesLayer.paint).toBeDefined(); - expect(nodesLayer.paint).toHaveProperty('circle-radius'); - expect(nodesLayer.paint).toHaveProperty('circle-color'); - expect(nodesLayer.paint).toHaveProperty('circle-stroke-color'); - expect(nodesLayer.paint).toHaveProperty('circle-stroke-width'); - expect(nodesLayer.paint).toHaveProperty('circle-opacity'); + describe('getValhallaStyle', () => { + it('should fetch valhalla default style and update valhalla source tiles', async () => { + const mockStyle = { + version: 8, + sources: { + valhalla: { + type: 'vector', + tiles: ['https://old.example.com/{z}/{x}/{y}.pbf'], + }, + }, + layers: [], + }; + + global.fetch = vi.fn().mockResolvedValue({ + json: () => Promise.resolve(mockStyle), + }) as unknown as typeof fetch; + + const style = await getValhallaStyle(); + + expect(global.fetch).toHaveBeenCalledWith( + 'https://raw.githubusercontent.com/valhalla/valhalla/refs/heads/master/docs/docs/api/tile/default_style.json' + ); + expect(style.sources.valhalla.tiles).toEqual([getValhallaTileUrl()]); + }); + + it('should keep style unchanged when valhalla source is not present', async () => { + const mockStyle = { + version: 8, + sources: { + streets: { + type: 'vector', + tiles: ['https://example.com/{z}/{x}/{y}.pbf'], + }, + }, + layers: [], + }; + + global.fetch = vi.fn().mockResolvedValue({ + json: () => Promise.resolve(mockStyle), + }) as unknown as typeof fetch; + + const style = await getValhallaStyle(); + + expect(style).toEqual(mockStyle); + expect(style.sources).not.toHaveProperty('valhalla'); }); }); diff --git a/src/components/tiles/valhalla-layers.ts b/src/components/tiles/valhalla-layers.ts index aa0a1e41..a353d2b4 100644 --- a/src/components/tiles/valhalla-layers.ts +++ b/src/components/tiles/valhalla-layers.ts @@ -1,4 +1,4 @@ -import type { LayerSpecification, SourceSpecification } from 'maplibre-gl'; +import type { SourceSpecification } from 'maplibre-gl'; import { getBaseUrl, normalizeBaseUrl } from '@/utils/base-url'; export const VALHALLA_SOURCE_ID = 'valhalla-tiles'; @@ -26,110 +26,13 @@ export function getValhallaSourceSpec(): SourceSpecification { }; } -export const VALHALLA_EDGES_LAYER: LayerSpecification = { - id: VALHALLA_EDGES_LAYER_ID, - type: 'line', - source: VALHALLA_SOURCE_ID, - 'source-layer': 'edges', - minzoom: 7, - maxzoom: 22, - filter: ['all'], - layout: { visibility: 'visible' }, - paint: { - 'line-color': [ - 'match', - ['get', 'tile_level'], - 0, - '#ff0000', - 1, - '#ff8800', - 2, - '#ffdd00', - '#ff00ff', - ], - 'line-width': [ - 'interpolate', - ['exponential', 1.5], - ['zoom'], - 12, - ['match', ['get', 'tile_level'], 0, 3, 1, 2, 2, 1, 2], - 14, - ['match', ['get', 'tile_level'], 0, 4, 1, 3, 2, 2, 3], - 16, - ['match', ['get', 'tile_level'], 0, 6, 1, 4, 2, 3, 4], - 18, - ['match', ['get', 'tile_level'], 0, 8, 1, 6, 2, 4, 6], - 20, - ['match', ['get', 'tile_level'], 0, 10, 1, 8, 2, 6, 8], - 22, - ['match', ['get', 'tile_level'], 0, 12, 1, 10, 2, 8, 10], - ], - 'line-opacity': 0.8, - }, -}; - -// Shortcuts is now a separate tile layer. -// and It uses the same line style as edges. -export const VALHALLA_SHORTCUTS_LAYER: LayerSpecification = { - id: VALHALLA_SHORTCUTS_LAYER_ID, - type: 'line', - source: VALHALLA_SOURCE_ID, - 'source-layer': 'shortcuts', - minzoom: 7, - maxzoom: 22, - filter: ['all'], - layout: { visibility: 'visible' }, - paint: { - 'line-color': [ - 'match', - ['get', 'tile_level'], - 0, - '#ff0000', - 1, - '#ff8800', - 2, - '#ffdd00', - '#ff00ff', - ], - 'line-width': [ - 'interpolate', - ['exponential', 1.5], - ['zoom'], - 12, - ['match', ['get', 'tile_level'], 0, 3, 1, 2, 2, 1, 2], - 14, - ['match', ['get', 'tile_level'], 0, 4, 1, 3, 2, 2, 3], - 16, - ['match', ['get', 'tile_level'], 0, 6, 1, 4, 2, 3, 4], - 18, - ['match', ['get', 'tile_level'], 0, 8, 1, 6, 2, 4, 6], - 20, - ['match', ['get', 'tile_level'], 0, 10, 1, 8, 2, 6, 8], - 22, - ['match', ['get', 'tile_level'], 0, 12, 1, 10, 2, 8, 10], - ], - 'line-opacity': 0.8, - }, -}; - -export const VALHALLA_NODES_LAYER: LayerSpecification = { - id: VALHALLA_NODES_LAYER_ID, - type: 'circle', - source: VALHALLA_SOURCE_ID, - 'source-layer': 'nodes', - minzoom: 16, - maxzoom: 22, - paint: { - 'circle-radius': ['interpolate', ['linear'], ['zoom'], 16, 2, 18, 4, 20, 6], - 'circle-color': ['case', ['get', 'traffic_signal'], '#ff0000', '#0088ff'], - 'circle-stroke-color': '#ffffff', - 'circle-stroke-width': 1, - 'circle-opacity': 0.8, - }, -}; - -export const VALHALLA_LAYERS: LayerSpecification[] = [ - VALHALLA_EDGES_LAYER, - VALHALLA_SHORTCUTS_LAYER, - VALHALLA_NODES_LAYER, -]; +export async function getValhallaStyle() { + const res = await fetch( + 'https://raw.githubusercontent.com/valhalla/valhalla/refs/heads/master/docs/docs/api/tile/default_style.json' + ); + const style = await res.json(); + if (style.sources?.valhalla) { + style.sources.valhalla.tiles = [getValhallaTileUrl()]; + } + return style; +}