Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/react-maplibre/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"redux-thunk": "^3.1.0",
"topojson-client": "^3.1.0",
"uuid": "^11.1.0",
"maplibre-gl": "^5.7.0",
"maplibre-gl": "^5.16.0",
"wms-capabilities": "^0.6.0"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('MlTerrainLayer Tests', () => {
const { _map }: any = win;
cy.wrap(_map).should((_map: any) => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(_map?.style?.sourceCaches?.terrain).to.not.be.undefined;
expect(_map?.style?.getSource('terrain')).to.not.be.undefined;
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(_map?.style?._layers?.hills).to.not.be.undefined;
});
Expand All @@ -33,7 +33,7 @@ describe('MlTerrainLayer Tests', () => {
const { _map }: any = win;
cy.wrap(_map).should((_map: any) => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(_map?.style?.sourceCaches?.terrain).to.be.undefined;
expect(_map?.style?.getSource('terrain')).to.be.undefined;
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
expect(_map?.style?._layers?.hills).to.be.undefined;
});
Expand Down
24 changes: 6 additions & 18 deletions packages/react-maplibre/src/components/MlWmsLayer/MlWmsLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const defaultProps: MlWmsLayerProps = {
srs: 'EPSG:3857',
width: '256',
height: '256',
Transparent: 'true',
styles: '',
},
};
Expand Down Expand Up @@ -122,29 +123,16 @@ const MlWmsLayer = (props: MlWmsLayerProps) => {
}
}, [mapHook.map, props, tileUrl]);

useEffect(() => {
if (initializedRef.current) return;

createLayer();
}, [createLayer]);

useEffect(() => {
if (
!mapHook.map ||
!mapHook.map?.map?.style?.sourceCaches?.[layerId.current] ||
!initializedRef.current
initializedRef.current &&
(mapHook?.map?.map?.getSource?.(layerId.current) as RasterSourceSpecification)?.tiles?.[0] ===
tileUrl
)
return;

const source = mapHook.map.map.getSource(layerId.current) as RasterSourceSpecification;
source.tiles = [tileUrl];

mapHook.map.map.style.sourceCaches[layerId.current].clearTiles();

mapHook.map.map.style.sourceCaches[layerId.current].update(mapHook.map.map.transform);

mapHook.map.map.triggerRepaint();
}, [mapHook.map, tileUrl]);
createLayer();
}, [createLayer]);

useEffect(() => {
if (!mapHook.map || !initializedRef.current) return;
Expand Down
132 changes: 81 additions & 51 deletions packages/react-maplibre/src/components/MlWmsLoader/MlWmsLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ export interface MlWmsLoaderProps {
* A function to set the feature info active state
*/
setFeatureInfoActive?: (val: boolean | ((current: boolean) => boolean)) => void;
/**
* Callback function that is called after the featureInfoRequest has succeeded
*/
featureInfoSuccess?: (content: string, lngLat: { lng: number; lat: number }) => void;
/**
* If true, displays a marker at the feature info location
*/
featureInfoMarkerEnabled?: boolean;
/**
* The WMS configuration object
*/
Expand All @@ -121,6 +129,14 @@ export interface MlWmsLoaderProps {
*/
buttons?: React.JSX.Element;
sortable?: boolean;
/**
* Array of layer Names (IDs) that should be visible at start. If not provided, default visibility logic applies.
*/
visibleLayersAtStart?: string[];
/**
* If true, renders the layer list UI. If false, only the WMS layer is rendered without UI controls.
*/
showLayerList?: boolean;
}

export interface WmsLayer {
Expand Down Expand Up @@ -170,8 +186,10 @@ const defaultProps = {
TRANSPARENT: 'TRUE',
},
featureInfoEnabled: true,
featureInfoMarkerEnabled: true,
zoomToExtent: false,
showDeleteButton: false,
showLayerList: true,
};
/**
* Loads a WMS getCapabilities xml document and adds a MlWmsLayer component for each layer that is
Expand Down Expand Up @@ -289,6 +307,7 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
QUERY_LAYERS: layers
.map((layer: LayerType) => (layer.visible && layer.queryable ? layer.Name : undefined))
.filter((n) => n),
STYLES: '',
WIDTH: 100,
HEIGHT: 100,
srs: 'EPSG:3857',
Expand Down Expand Up @@ -327,6 +346,7 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
.then((text) => {
setFeatureInfoLngLat(ev.lngLat);
setFeatureInfoContent(text);
props.featureInfoSuccess?.(text, ev.lngLat);
})
.catch((error) => console.log(error));
},
Expand Down Expand Up @@ -383,8 +403,11 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
if (idx === 0) {
_LatLonBoundingBox = layer.EX_GeographicBoundingBox || layer?.LatLonBoundingBox || [];
}
const isVisible = props.visibleLayersAtStart
? props.visibleLayersAtStart.includes(layer.Name || '')
: true;
return {
visible: capabilities?.Capability?.Layer?.Layer?.length > 2 ? idx > 1 : true,
visible: isVisible,
Attribution: { Title: '' },
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...(({ CRS, ..._layer }) => _layer)(layer),
Expand All @@ -398,8 +421,12 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
if (idx === 0) {
_LatLonBoundingBox = layer.EX_GeographicBoundingBox || layer?.LatLonBoundingBox || [];
}
const isVisible =
props.visibleLayersAtStart && layer.Name
? props.visibleLayersAtStart.includes(layer.Name)
: false;
return {
visible: false,
visible: isVisible,
Attribution: { Title: '' },
// eslint-disable-next-line @typescript-eslint/no-unused-vars
...(({ CRS, ..._layer }) => _layer)(layer),
Expand Down Expand Up @@ -527,57 +554,60 @@ const MlWmsLoader = (props: MlWmsLoaderProps) => {
)}
{wmsUrl && (
<>
{props.layerId && props.sortable && (
<SortableContainer layerId={props.layerId}>{listContent}</SortableContainer>
{props.showLayerList && (
<>
{props.sortable && <SortableContainer>{listContent}</SortableContainer>}
{!props.sortable && listContent}
<Box sx={{ display: open ? 'block' : 'none' }}>
<List dense component="div" disablePadding sx={{ paddingLeft: '18px' }}>
{wmsUrl &&
layers?.map?.((layer, idx) => {
return layer?.Name ? (
<ListItem
key={layer.Name + idx}
secondaryAction={<>{layer?.queryable && <InfoIcon />}</>}
>
<ListItemIcon sx={{ minWidth: '30px' }}>
<Checkbox
checked={layer.visible}
sx={{ padding: 0 }}
onClick={() => {
const _layers: Array<LayerType> = [...layers];
_layers[idx].visible = !_layers[idx].visible;
setLayers([..._layers]);
}}
/>
</ListItemIcon>
<ListItemText primary={layer?.Title} variant="layerlist" />
</ListItem>
) : (
<></>
);
})}
</List>
</Box>
</>
)}
{wmsUrl && layers?.length && (
<MlWmsLayer
key={mapHook.componentId}
layerId={props.layerId || mapHook.componentId}
url={wmsUrl}
attribution={attribution}
visible={visible}
urlParameters={{
...props.wmsUrlParameters,
layers: layers
?.filter?.((layer) => layer.visible)
.map((el) => el.Name)
.reverse()
.join(','),
}}
insertBeforeLayer={props?.insertBeforeLayer}
/>
)}
{props.layerId && !props.sortable && listContent}
<Box sx={{ display: open ? 'block' : 'none' }}>
<List dense component="div" disablePadding sx={{ paddingLeft: '18px' }}>
{wmsUrl &&
layers?.map?.((layer, idx) => {
return layer?.Name ? (
<ListItem
key={layer.Name + idx}
secondaryAction={<>{layer?.queryable && <InfoIcon />}</>}
>
<ListItemIcon sx={{ minWidth: '30px' }}>
<Checkbox
checked={layer.visible}
sx={{ padding: 0 }}
onClick={() => {
const _layers: Array<LayerType> = [...layers];
_layers[idx].visible = !_layers[idx].visible;
setLayers([..._layers]);
}}
/>
</ListItemIcon>
<ListItemText primary={layer?.Title} variant="layerlist" />
</ListItem>
) : (
<></>
);
})}
</List>
{wmsUrl && layers?.length && (
<MlWmsLayer
key={mapHook.componentId}
url={wmsUrl}
attribution={attribution}
visible={visible}
urlParameters={{
...props.wmsUrlParameters,
layers: layers
?.filter?.((layer) => layer.visible)
.map((el) => el.Name)
.reverse()
.join(','),
}}
insertBeforeLayer={props?.insertBeforeLayer}
/>
)}
</Box>

{props.featureInfoEnabled && featureInfoLngLat && (
{props.featureInfoEnabled && props.featureInfoMarkerEnabled && featureInfoLngLat && (
<MlMarker {...featureInfoLngLat} content={featureInfoContent} />
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ function LayerListItem({
return (
<>
{props.sortable && props.layerId && !layerComponent?.props?.layers && (
<SortableContainer layerId={props.layerId}>{listContent}</SortableContainer>
<SortableContainer>{listContent}</SortableContainer>
)}
{!props.sortable && !layerComponent?.props?.layers && listContent}
{_layerComponent}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { ReactNode } from 'react';
import { ReactNode, useRef } from 'react';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { v4 as uuid } from 'uuid';

interface SortableContainerProps {
children: ReactNode;
layerId: string;
}

function SortableContainer({ children, layerId }: SortableContainerProps) {
const { attributes, listeners, setNodeRef, transform } = useSortable({
id: layerId,
});
function SortableContainer({ children }: SortableContainerProps) {
const idRef = useRef(uuid());
const { attributes, listeners, setNodeRef, transform } = useSortable({ id: idRef.current });
const style = {
transform: CSS.Transform.toString(transform),
};
Expand Down
Loading
Loading