Skip to content

Commit bd163c3

Browse files
authored
Fix/mlwmsloader layer switch (#253)
1 parent dfd1c95 commit bd163c3

File tree

7 files changed

+163
-81
lines changed

7 files changed

+163
-81
lines changed

packages/react-maplibre/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"redux-thunk": "^3.1.0",
5353
"topojson-client": "^3.1.0",
5454
"uuid": "^11.1.0",
55-
"maplibre-gl": "^5.7.0",
55+
"maplibre-gl": "^5.16.0",
5656
"wms-capabilities": "^0.6.0"
5757
},
5858
"peerDependencies": {

packages/react-maplibre/src/components/MlTerrainLayer/MlTerrainLayer.cy.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('MlTerrainLayer Tests', () => {
1717
const { _map }: any = win;
1818
cy.wrap(_map).should((_map: any) => {
1919
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
20-
expect(_map?.style?.sourceCaches?.terrain).to.not.be.undefined;
20+
expect(_map?.style?.getSource('terrain')).to.not.be.undefined;
2121
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
2222
expect(_map?.style?._layers?.hills).to.not.be.undefined;
2323
});
@@ -33,7 +33,7 @@ describe('MlTerrainLayer Tests', () => {
3333
const { _map }: any = win;
3434
cy.wrap(_map).should((_map: any) => {
3535
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
36-
expect(_map?.style?.sourceCaches?.terrain).to.be.undefined;
36+
expect(_map?.style?.getSource('terrain')).to.be.undefined;
3737
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
3838
expect(_map?.style?._layers?.hills).to.be.undefined;
3939
});

packages/react-maplibre/src/components/MlWmsLayer/MlWmsLayer.tsx

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const defaultProps: MlWmsLayerProps = {
1313
srs: 'EPSG:3857',
1414
width: '256',
1515
height: '256',
16+
Transparent: 'true',
1617
styles: '',
1718
},
1819
};
@@ -122,29 +123,16 @@ const MlWmsLayer = (props: MlWmsLayerProps) => {
122123
}
123124
}, [mapHook.map, props, tileUrl]);
124125

125-
useEffect(() => {
126-
if (initializedRef.current) return;
127-
128-
createLayer();
129-
}, [createLayer]);
130-
131126
useEffect(() => {
132127
if (
133-
!mapHook.map ||
134-
!mapHook.map?.map?.style?.sourceCaches?.[layerId.current] ||
135-
!initializedRef.current
128+
initializedRef.current &&
129+
(mapHook?.map?.map?.getSource?.(layerId.current) as RasterSourceSpecification)?.tiles?.[0] ===
130+
tileUrl
136131
)
137132
return;
138133

139-
const source = mapHook.map.map.getSource(layerId.current) as RasterSourceSpecification;
140-
source.tiles = [tileUrl];
141-
142-
mapHook.map.map.style.sourceCaches[layerId.current].clearTiles();
143-
144-
mapHook.map.map.style.sourceCaches[layerId.current].update(mapHook.map.map.transform);
145-
146-
mapHook.map.map.triggerRepaint();
147-
}, [mapHook.map, tileUrl]);
134+
createLayer();
135+
}, [createLayer]);
148136

149137
useEffect(() => {
150138
if (!mapHook.map || !initializedRef.current) return;

packages/react-maplibre/src/components/MlWmsLoader/MlWmsLoader.tsx

Lines changed: 81 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ export interface MlWmsLoaderProps {
100100
* A function to set the feature info active state
101101
*/
102102
setFeatureInfoActive?: (val: boolean | ((current: boolean) => boolean)) => void;
103+
/**
104+
* Callback function that is called after the featureInfoRequest has succeeded
105+
*/
106+
featureInfoSuccess?: (content: string, lngLat: { lng: number; lat: number }) => void;
107+
/**
108+
* If true, displays a marker at the feature info location
109+
*/
110+
featureInfoMarkerEnabled?: boolean;
103111
/**
104112
* The WMS configuration object
105113
*/
@@ -121,6 +129,14 @@ export interface MlWmsLoaderProps {
121129
*/
122130
buttons?: React.JSX.Element;
123131
sortable?: boolean;
132+
/**
133+
* Array of layer Names (IDs) that should be visible at start. If not provided, default visibility logic applies.
134+
*/
135+
visibleLayersAtStart?: string[];
136+
/**
137+
* If true, renders the layer list UI. If false, only the WMS layer is rendered without UI controls.
138+
*/
139+
showLayerList?: boolean;
124140
}
125141

126142
export interface WmsLayer {
@@ -170,8 +186,10 @@ const defaultProps = {
170186
TRANSPARENT: 'TRUE',
171187
},
172188
featureInfoEnabled: true,
189+
featureInfoMarkerEnabled: true,
173190
zoomToExtent: false,
174191
showDeleteButton: false,
192+
showLayerList: true,
175193
};
176194
/**
177195
* Loads a WMS getCapabilities xml document and adds a MlWmsLayer component for each layer that is
@@ -289,6 +307,7 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
289307
QUERY_LAYERS: layers
290308
.map((layer: LayerType) => (layer.visible && layer.queryable ? layer.Name : undefined))
291309
.filter((n) => n),
310+
STYLES: '',
292311
WIDTH: 100,
293312
HEIGHT: 100,
294313
srs: 'EPSG:3857',
@@ -327,6 +346,7 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
327346
.then((text) => {
328347
setFeatureInfoLngLat(ev.lngLat);
329348
setFeatureInfoContent(text);
349+
props.featureInfoSuccess?.(text, ev.lngLat);
330350
})
331351
.catch((error) => console.log(error));
332352
},
@@ -383,8 +403,11 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
383403
if (idx === 0) {
384404
_LatLonBoundingBox = layer.EX_GeographicBoundingBox || layer?.LatLonBoundingBox || [];
385405
}
406+
const isVisible = props.visibleLayersAtStart
407+
? props.visibleLayersAtStart.includes(layer.Name || '')
408+
: true;
386409
return {
387-
visible: capabilities?.Capability?.Layer?.Layer?.length > 2 ? idx > 1 : true,
410+
visible: isVisible,
388411
Attribution: { Title: '' },
389412
// eslint-disable-next-line @typescript-eslint/no-unused-vars
390413
...(({ CRS, ..._layer }) => _layer)(layer),
@@ -398,8 +421,12 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
398421
if (idx === 0) {
399422
_LatLonBoundingBox = layer.EX_GeographicBoundingBox || layer?.LatLonBoundingBox || [];
400423
}
424+
const isVisible =
425+
props.visibleLayersAtStart && layer.Name
426+
? props.visibleLayersAtStart.includes(layer.Name)
427+
: false;
401428
return {
402-
visible: false,
429+
visible: isVisible,
403430
Attribution: { Title: '' },
404431
// eslint-disable-next-line @typescript-eslint/no-unused-vars
405432
...(({ CRS, ..._layer }) => _layer)(layer),
@@ -527,57 +554,60 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
527554
)}
528555
{wmsUrl && (
529556
<>
530-
{props.layerId && props.sortable && (
531-
<SortableContainer layerId={props.layerId}>{listContent}</SortableContainer>
557+
{props.showLayerList && (
558+
<>
559+
{props.sortable && <SortableContainer>{listContent}</SortableContainer>}
560+
{!props.sortable && listContent}
561+
<Box sx={{ display: open ? 'block' : 'none' }}>
562+
<List dense component="div" disablePadding sx={{ paddingLeft: '18px' }}>
563+
{wmsUrl &&
564+
layers?.map?.((layer, idx) => {
565+
return layer?.Name ? (
566+
<ListItem
567+
key={layer.Name + idx}
568+
secondaryAction={<>{layer?.queryable && <InfoIcon />}</>}
569+
>
570+
<ListItemIcon sx={{ minWidth: '30px' }}>
571+
<Checkbox
572+
checked={layer.visible}
573+
sx={{ padding: 0 }}
574+
onClick={() => {
575+
const _layers: Array<LayerType> = [...layers];
576+
_layers[idx].visible = !_layers[idx].visible;
577+
setLayers([..._layers]);
578+
}}
579+
/>
580+
</ListItemIcon>
581+
<ListItemText primary={layer?.Title} variant="layerlist" />
582+
</ListItem>
583+
) : (
584+
<></>
585+
);
586+
})}
587+
</List>
588+
</Box>
589+
</>
590+
)}
591+
{wmsUrl && layers?.length && (
592+
<MlWmsLayer
593+
key={mapHook.componentId}
594+
layerId={props.layerId || mapHook.componentId}
595+
url={wmsUrl}
596+
attribution={attribution}
597+
visible={visible}
598+
urlParameters={{
599+
...props.wmsUrlParameters,
600+
layers: layers
601+
?.filter?.((layer) => layer.visible)
602+
.map((el) => el.Name)
603+
.reverse()
604+
.join(','),
605+
}}
606+
insertBeforeLayer={props?.insertBeforeLayer}
607+
/>
532608
)}
533-
{props.layerId && !props.sortable && listContent}
534-
<Box sx={{ display: open ? 'block' : 'none' }}>
535-
<List dense component="div" disablePadding sx={{ paddingLeft: '18px' }}>
536-
{wmsUrl &&
537-
layers?.map?.((layer, idx) => {
538-
return layer?.Name ? (
539-
<ListItem
540-
key={layer.Name + idx}
541-
secondaryAction={<>{layer?.queryable && <InfoIcon />}</>}
542-
>
543-
<ListItemIcon sx={{ minWidth: '30px' }}>
544-
<Checkbox
545-
checked={layer.visible}
546-
sx={{ padding: 0 }}
547-
onClick={() => {
548-
const _layers: Array<LayerType> = [...layers];
549-
_layers[idx].visible = !_layers[idx].visible;
550-
setLayers([..._layers]);
551-
}}
552-
/>
553-
</ListItemIcon>
554-
<ListItemText primary={layer?.Title} variant="layerlist" />
555-
</ListItem>
556-
) : (
557-
<></>
558-
);
559-
})}
560-
</List>
561-
{wmsUrl && layers?.length && (
562-
<MlWmsLayer
563-
key={mapHook.componentId}
564-
url={wmsUrl}
565-
attribution={attribution}
566-
visible={visible}
567-
urlParameters={{
568-
...props.wmsUrlParameters,
569-
layers: layers
570-
?.filter?.((layer) => layer.visible)
571-
.map((el) => el.Name)
572-
.reverse()
573-
.join(','),
574-
}}
575-
insertBeforeLayer={props?.insertBeforeLayer}
576-
/>
577-
)}
578-
</Box>
579609

580-
{props.featureInfoEnabled && featureInfoLngLat && (
610+
{props.featureInfoEnabled && props.featureInfoMarkerEnabled && featureInfoLngLat && (
581611
<MlMarker {...featureInfoLngLat} content={featureInfoContent} />
582612
)}
583613
</>

packages/react-maplibre/src/ui_components/LayerList/LayerListItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ function LayerListItem({
202202
return (
203203
<>
204204
{props.sortable && props.layerId && !layerComponent?.props?.layers && (
205-
<SortableContainer layerId={props.layerId}>{listContent}</SortableContainer>
205+
<SortableContainer>{listContent}</SortableContainer>
206206
)}
207207
{!props.sortable && !layerComponent?.props?.layers && listContent}
208208
{_layerComponent}

packages/react-maplibre/src/ui_components/LayerList/util/SortableContainer.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import { ReactNode } from 'react';
1+
import { ReactNode, useRef } from 'react';
22
import { useSortable } from '@dnd-kit/sortable';
33
import { CSS } from '@dnd-kit/utilities';
4+
import { v4 as uuid } from 'uuid';
45

56
interface SortableContainerProps {
67
children: ReactNode;
7-
layerId: string;
88
}
99

10-
function SortableContainer({ children, layerId }: SortableContainerProps) {
11-
const { attributes, listeners, setNodeRef, transform } = useSortable({
12-
id: layerId,
13-
});
10+
function SortableContainer({ children }: SortableContainerProps) {
11+
const idRef = useRef(uuid());
12+
const { attributes, listeners, setNodeRef, transform } = useSortable({ id: idRef.current });
1413
const style = {
1514
transform: CSS.Transform.toString(transform),
1615
};

0 commit comments

Comments
 (0)