Skip to content

Commit 535f314

Browse files
committed
Add custom layer adapters
1 parent 0503503 commit 535f314

8 files changed

Lines changed: 349 additions & 5 deletions

File tree

examples/cog/index.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ <h3>GPU COG Layer Demo</h3>
130130
<select id="cog-select">
131131
<option value="">-- Select a COG --</option>
132132
<option value="https://s3.us-east-1.amazonaws.com/ds-deck.gl-raster-public/cog/Annual_NLCD_LndCov_2024_CU_C1V1.tif">NLCD 2024 Land Cover</option>
133-
<option value="https://s3.us-east-1.amazonaws.com/ds-deck.gl-raster-public/cog/NOAA_GOES_West_201907291630.tif">NOAA GOES West Imagery</option>
134133
</select>
135134
<button id="add-cog" disabled>Add COG Layer</button>
136135
</div>

examples/cog/main.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import maplibregl from 'maplibre-gl';
44
import '../../src/index'; // Import to extend Map.prototype
5+
import { COGLayerAdapter } from '../../src/lib/layers';
56
import 'maplibre-gl/dist/maplibre-gl.css';
67
import { LayerControl } from 'maplibre-gl-layer-control';
78
import 'maplibre-gl-layer-control/style.css';
89

10+
// COG Layer Adapter for layer control integration
11+
let cogAdapter: COGLayerAdapter;
12+
913
// Create map
1014
const map = new maplibregl.Map({
1115
container: 'map',
@@ -81,6 +85,12 @@ function updateLayerList(): void {
8185
layerList.querySelectorAll('.remove').forEach((btn) => {
8286
btn.addEventListener('click', (e) => {
8387
const layerId = (e.target as HTMLButtonElement).dataset.layer!;
88+
89+
// Notify adapter before removal
90+
if (cogAdapter) {
91+
cogAdapter.notifyLayerRemoved(layerId);
92+
}
93+
8494
map.removeLayerById(layerId);
8595
if (layerId === currentCogLayerId) {
8696
currentCogLayerId = null;
@@ -124,6 +134,12 @@ addCogBtn.addEventListener('click', async () => {
124134

125135
currentCogLayerId = layerId;
126136
opacitySlider.disabled = false;
137+
138+
// Notify the COG adapter that a layer was added
139+
if (cogAdapter) {
140+
cogAdapter.notifyLayerAdded(layerId);
141+
}
142+
127143
updateLayerList();
128144
setStatus(`COG layer loaded: ${layerId}`, 'success');
129145
console.log('GPU COG layer added:', layerId);
@@ -140,12 +156,16 @@ map.on('load', () => {
140156
// Set initial basemap
141157
map.setBasemap('CartoDB.Positron');
142158

143-
// Add layer control
159+
// Create COG layer adapter for layer control integration
160+
cogAdapter = new COGLayerAdapter(map);
161+
162+
// Add layer control with COG adapter
144163
const layerControl = new LayerControl({
145164
collapsed: true,
146165
panelWidth: 360,
166+
customLayerAdapters: [cogAdapter],
147167
});
148-
map.addControl(layerControl as unknown as maplibregl.IControl, 'top-left');
168+
map.addControl(layerControl as unknown as maplibregl.IControl, 'top-right');
149169

150170
updateLayerList();
151171
setStatus('Ready to load COG layer');

examples/zarr/main.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@
22

33
import maplibregl from 'maplibre-gl';
44
import '../../src/index'; // Import to extend Map.prototype
5+
import { ZarrLayerAdapter, getZarrLayersMap } from '../../src/lib/layers';
56
import 'maplibre-gl/dist/maplibre-gl.css';
67
import { LayerControl } from 'maplibre-gl-layer-control';
78
import 'maplibre-gl-layer-control/style.css';
9+
import type { ZarrLayer } from '@carbonplan/zarr-layer';
10+
11+
// Zarr Layer Adapter for layer control integration
12+
let zarrAdapter: ZarrLayerAdapter;
13+
// Local map to track Zarr layers for the adapter
14+
const zarrLayersMap = new Map<string, ZarrLayer>();
815

916
// Colormap definitions
1017
const colormaps: Record<string, string[]> = {
@@ -109,6 +116,13 @@ function updateLayerList(): void {
109116
layerList.querySelectorAll('.remove').forEach((btn) => {
110117
btn.addEventListener('click', (e) => {
111118
const layerId = (e.target as HTMLButtonElement).dataset.layer!;
119+
120+
// Notify adapter before removal
121+
if (zarrAdapter) {
122+
zarrAdapter.notifyLayerRemoved(layerId);
123+
}
124+
zarrLayersMap.delete(layerId);
125+
112126
map.removeZarrLayer(layerId);
113127
if (layerId === currentZarrLayerId) {
114128
currentZarrLayerId = null;
@@ -205,6 +219,17 @@ addZarrBtn.addEventListener('click', async () => {
205219
currentZarrLayerId = layerId;
206220
enableControls();
207221
updateColormapPreview(colormaps[colormapSelect.value]);
222+
223+
// Sync local map and notify adapter
224+
const layersMapFromLib = getZarrLayersMap(map);
225+
const layerInstance = layersMapFromLib.get(layerId);
226+
if (layerInstance) {
227+
zarrLayersMap.set(layerId, layerInstance);
228+
if (zarrAdapter) {
229+
zarrAdapter.notifyLayerAdded(layerId);
230+
}
231+
}
232+
208233
updateLayerList();
209234
setStatus(`Zarr layer loaded: ${layerId}`, 'success');
210235
console.log('Zarr layer added:', layerId);
@@ -221,12 +246,16 @@ map.on('load', () => {
221246
// Set initial basemap
222247
map.setBasemap('CartoDB.DarkMatter');
223248

224-
// Add layer control
249+
// Create Zarr layer adapter for layer control integration
250+
zarrAdapter = new ZarrLayerAdapter(map, zarrLayersMap);
251+
252+
// Add layer control with Zarr adapter
225253
const layerControl = new LayerControl({
226254
collapsed: true,
227255
panelWidth: 360,
256+
customLayerAdapters: [zarrAdapter],
228257
});
229-
map.addControl(layerControl as unknown as maplibregl.IControl, 'top-left');
258+
map.addControl(layerControl as unknown as maplibregl.IControl, 'top-right');
230259

231260
// Initialize colormap preview
232261
updateColormapPreview(colormaps.rdbu);

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,6 @@ export type {
206206
GetMapStateOptions,
207207
SetMapStateOptions,
208208
} from './lib/state';
209+
210+
// Export layer control adapters
211+
export { COGLayerAdapter, ZarrLayerAdapter } from './lib/layers';
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* COG Layer Adapter for integrating deck.gl COG layers with maplibre-gl-layer-control.
3+
*/
4+
5+
import type { CustomLayerAdapter, LayerState } from 'maplibre-gl-layer-control';
6+
import type { Map as MapLibreMap } from 'maplibre-gl';
7+
import type { Layer } from '@deck.gl/core';
8+
import { getDeckLayers, setDeckLayerVisibility, setDeckLayerOpacity } from './deck-overlay';
9+
10+
interface DeckLayerEntry {
11+
id: string;
12+
layer: Layer;
13+
visible: boolean;
14+
opacity: number;
15+
}
16+
17+
/**
18+
* Adapter for COG (Cloud Optimized GeoTIFF) layers.
19+
* Allows the layer control to manage deck.gl COG layers.
20+
*/
21+
export class COGLayerAdapter implements CustomLayerAdapter {
22+
readonly type = 'cog';
23+
24+
private map: MapLibreMap;
25+
private changeCallbacks: Array<(event: 'add' | 'remove', layerId: string) => void> = [];
26+
27+
constructor(map: MapLibreMap) {
28+
this.map = map;
29+
}
30+
31+
/**
32+
* Get all COG layer IDs.
33+
*/
34+
getLayerIds(): string[] {
35+
const layers = getDeckLayers(this.map);
36+
return Object.keys(layers);
37+
}
38+
39+
/**
40+
* Get the state of a COG layer.
41+
*/
42+
getLayerState(layerId: string): LayerState | null {
43+
const layers = getDeckLayers(this.map);
44+
const entry = layers[layerId] as DeckLayerEntry | undefined;
45+
if (!entry) return null;
46+
47+
return {
48+
visible: entry.visible,
49+
opacity: entry.opacity,
50+
name: this.getName(layerId),
51+
};
52+
}
53+
54+
/**
55+
* Set visibility of a COG layer.
56+
*/
57+
setVisibility(layerId: string, visible: boolean): void {
58+
setDeckLayerVisibility(this.map, layerId, visible);
59+
}
60+
61+
/**
62+
* Set opacity of a COG layer.
63+
*/
64+
setOpacity(layerId: string, opacity: number): void {
65+
setDeckLayerOpacity(this.map, layerId, opacity);
66+
}
67+
68+
/**
69+
* Get the display name for a COG layer.
70+
*/
71+
getName(layerId: string): string {
72+
// Convert layer ID to a friendly name
73+
return layerId
74+
.replace(/^(mgl-extend-)?gpu-cog[-_]?/i, '')
75+
.replace(/[-_]/g, ' ')
76+
.replace(/\b\w/g, c => c.toUpperCase()) || 'COG Layer';
77+
}
78+
79+
/**
80+
* Get the symbol type for COG layers.
81+
*/
82+
getSymbolType(): string {
83+
return 'raster';
84+
}
85+
86+
/**
87+
* Notify that a layer was added.
88+
* Call this when a COG layer is added.
89+
*/
90+
notifyLayerAdded(layerId: string): void {
91+
this.changeCallbacks.forEach(cb => cb('add', layerId));
92+
}
93+
94+
/**
95+
* Notify that a layer was removed.
96+
* Call this when a COG layer is removed.
97+
*/
98+
notifyLayerRemoved(layerId: string): void {
99+
this.changeCallbacks.forEach(cb => cb('remove', layerId));
100+
}
101+
102+
/**
103+
* Subscribe to layer changes.
104+
*/
105+
onLayerChange(callback: (event: 'add' | 'remove', layerId: string) => void): () => void {
106+
this.changeCallbacks.push(callback);
107+
return () => {
108+
const idx = this.changeCallbacks.indexOf(callback);
109+
if (idx >= 0) {
110+
this.changeCallbacks.splice(idx, 1);
111+
}
112+
};
113+
}
114+
}
115+
116+
export default COGLayerAdapter;

src/lib/layers/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,15 @@ export {
4040
setZarrLayerOpacity,
4141
removeZarrLayer,
4242
isZarrLayer,
43+
getZarrLayersMap,
4344
} from './zarr';
4445

4546
// Deck.gl overlay exports
4647
export {
4748
isDeckLayer,
4849
getDeckLayerEntry,
4950
} from './deck-overlay';
51+
52+
// Layer control adapters
53+
export { COGLayerAdapter } from './cog-layer-adapter';
54+
export { ZarrLayerAdapter } from './zarr-layer-adapter';

0 commit comments

Comments
 (0)