Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds wms styling using geostyler #1771

Draft
wants to merge 2 commits into
base: next
Choose a base branch
from
Draft
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
109 changes: 109 additions & 0 deletions src/components/StylingDrawer/SldStylingPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, {
useEffect, useState, useCallback
} from 'react';

import {
CardStyle, CardStyleProps
} from 'geostyler/dist/Component/CardStyle/CardStyle';
import SLDParser from 'geostyler-sld-parser';
import { Style as GsStyle } from 'geostyler-style';
import Layer from 'ol/layer/Layer';
import {
ImageWMS, TileWMS
} from 'ol/source';

import { Logger } from '@terrestris/base-util';
import { MapUtil } from '@terrestris/ol-util';
import { useMap } from '@terrestris/react-util/dist/Hooks/useMap/useMap';

import useAppSelector from '../../../hooks/useAppSelector';
import useSHOGunAPIClient from '../../../hooks/useSHOGunAPIClient';
import {
fetchGeoserverStyle, fetchWorkspaceFromGetCapabilities, getLayerUrl
} from '../../../utils/geoserverUtils';

export type SldStylingPanelProps = CardStyleProps;

const SldStylingPanel: React.FC<SldStylingPanelProps> = (props): JSX.Element => {
const [style, setStyle] = useState<GsStyle | undefined>();
const layerUid = useAppSelector(state => state.stylingDrawerLayerUid);
const map = useMap();
const client = useSHOGunAPIClient();

const getStyle = useCallback(async () => {
if (!map || !layerUid) {
return;
}

const layer = MapUtil.getLayerByOlUid(map, layerUid) as Layer;
if (!layer) {
return;
}

const source = layer.getSource();
if (!(source instanceof ImageWMS || source instanceof TileWMS)) {
return;
}

const layerUrl = getLayerUrl(source);
if (!layerUrl) {
return;
}

try {
const workspaceInfo = await fetchWorkspaceFromGetCapabilities(layerUrl, client);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering why this is needed. Usually the workspace is already party of the layer name, e.g. MYWORKSPACE:MYLAYER. This can probably enhanced by checking if the name contains the workspace already and if not getting it from the capabilities.

if (!workspaceInfo) {return;}

const geoserverStyle = await fetchGeoserverStyle(workspaceInfo.workspace, workspaceInfo.layerName, layerUrl, client);
if (geoserverStyle) {
const parser = new SLDParser();
const { output: sldObject } = await parser.readStyle(geoserverStyle);
setStyle(sldObject);
}
} catch (error) {
Logger.error('Error: ', error);
}
}, [map, layerUid, client]);

useEffect(() => {
getStyle();
}, [getStyle]);

useEffect(() => {
if (!map || !style) {
return;
}

const layer = MapUtil.getLayerByOlUid(map, layerUid) as Layer;
const source = layer?.getSource() as ImageWMS;
if (!source) {
return;
}

style.name = source.getParams().LAYERS;

const parser = new SLDParser();
parser.writeStyle(style).then(({ output: sld }) => {
if (sld) {
source.updateParams({
SLD_BODY: sld,
STYLES: style.name
});
Comment on lines +88 to +91
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering, but what happens if:

  • a layer has been opened in the drawer,
  • got a new style via SLD_BODY,
  • the drawer has been closed and reopened with the same layer.

What style would be shown in the styler UI? The default one or the updated one?

Copy link
Contributor Author

@FritzHoing FritzHoing Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good hint! The default style will be used in that case. Maybe we want to check if a layer has a SLD_BODY already?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we also want to store the SLD_BODY?

}
});
}, [style, map, layerUid]);

if (!map || !style) {
return <></>;
}

return (
<CardStyle
style={style}
onStyleChange={setStyle}
{...props}
/>
);
};

export default SldStylingPanel;
43 changes: 40 additions & 3 deletions src/components/StylingDrawer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,73 @@
import React from 'react';
import React, {
useEffect, useState
} from 'react';

import {
Drawer,
DrawerProps
} from 'antd';

import Layer from 'ol/layer/Layer';
import {
ImageWMS, TileWMS
} from 'ol/source';
import {
useTranslation
} from 'react-i18next';

import './index.less';

import { MapUtil } from '@terrestris/ol-util';

import { useMap } from '@terrestris/react-util';

import useAppDispatch from '../../hooks/useAppDispatch';
import useAppSelector from '../../hooks/useAppSelector';
import { clearStylingDrawerLayerUid } from '../../store/stylingDrawerLayerUid';
import { setStylingDrawerVisibility } from '../../store/stylingDrawerVisibility';
import StylingComponent from '../ToolMenu/Draw/StylingDrawerButton/StylingComponent';
import DrawLayerStylingPanel from '../ToolMenu/Draw/StylingDrawerButton/DrawLayerStylingPanel';

import SldStylingPanel from './SldStylingPanel';

export type StylingDrawerProps = DrawerProps;

export const StylingDrawer: React.FC<StylingDrawerProps> = ({
...passThroughProps
}): JSX.Element => {

const [isImageLayer, setIsImageLayer] = useState<boolean>();
const dispatch = useAppDispatch();
const isStylingDrawerVisible = useAppSelector(state => state.stylingDrawerVisibility);
const layerUid = useAppSelector(state => state.stylingDrawerLayerUid);
const map = useMap();

const {
t
} = useTranslation();

useEffect(() => {
if (!layerUid) {
setIsImageLayer(false);
}

if (!map) {
return;
}
const layer = MapUtil.getLayerByOlUid(map, layerUid) as Layer;
if (!layer) {
return;
}
const layerSource = layer.getSource();
if (layerSource instanceof TileWMS || layerSource instanceof ImageWMS){
setIsImageLayer(true);
} else {
setIsImageLayer(false);
}
}, [layerUid, map]);

const onClose = () => {
dispatch(setStylingDrawerVisibility(false));
dispatch(clearStylingDrawerLayerUid());
};

return (
Expand All @@ -44,7 +81,7 @@ export const StylingDrawer: React.FC<StylingDrawerProps> = ({
mask={false}
{...passThroughProps}
>
<StylingComponent />
{isImageLayer ? <SldStylingPanel /> : <DrawLayerStylingPanel />}
</Drawer>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ import {
useMap
} from '@terrestris/react-util/dist/Hooks/useMap/useMap';

export type StylingComponentProps = CardStyleProps;
export type DrawLayerStylingPanelProps = CardStyleProps;

export const StylingComponent: React.FC<StylingComponentProps> = ({
export const DrawLayerStylingPanel: React.FC<DrawLayerStylingPanelProps> = ({
...passThroughProps
}): JSX.Element => {

Expand Down Expand Up @@ -113,6 +113,10 @@ export const StylingComponent: React.FC<StylingComponentProps> = ({

const drawVectorLayer = MapUtil.getLayerByName(map, 'react-geo_digitize') as OlLayerVector<OlSourceVector>;

if (!drawVectorLayer) {
return;
}

const parseStyles = async () => {
let olStylePolygon: OlStyleLike;
let olStyleLineString: OlStyleLike;
Expand Down Expand Up @@ -187,4 +191,4 @@ export const StylingComponent: React.FC<StylingComponentProps> = ({
);
};

export default StylingComponent;
export default DrawLayerStylingPanel;
38 changes: 34 additions & 4 deletions src/components/ToolMenu/LayerTree/LayerTreeContextMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, {
useEffect,
useState
} from 'react';

import {
faEllipsisV
} from '@fortawesome/free-solid-svg-icons';

import {
FontAwesomeIcon
} from '@fortawesome/react-fontawesome';
Expand All @@ -15,10 +17,7 @@ import {
notification,
Spin
} from 'antd';
import {
ItemType
} from 'antd/lib/menu/interface';

import { ItemType } from 'antd/lib/menu/interface';
import {
getUid
} from 'ol';
Expand Down Expand Up @@ -76,6 +75,9 @@ import {
setLayer as setLayerDetailsLayer,
show as showLayerDetailsModal
} from '../../../../store/layerDetailsModal';
import { setStylingDrawerLayerUid } from '../../../../store/stylingDrawerLayerUid';
import { setStylingDrawerVisibility } from '../../../../store/stylingDrawerVisibility';
import { checkIfGeoserverLayer } from '../../../../utils/geoserverUtils';

export type LayerTreeContextMenuProps = {
layer: OlLayerTile<OlSourceTileWMS> | OlLayerImage<OlSourceImageWMS>;
Expand All @@ -92,6 +94,7 @@ export const LayerTreeContextMenu: React.FC<LayerTreeContextMenuProps> = ({

const [settingsVisible, setSettingsVisible] = useState<boolean>(false);
const [extentLoading, setExtentLoading] = useState<boolean>(false);
const [isGeoserverLayer, setIsGeoserverLayer] = useState<boolean>(false);

const dispatch = useAppDispatch();
const client = useSHOGunAPIClient();
Expand All @@ -106,12 +109,26 @@ export const LayerTreeContextMenu: React.FC<LayerTreeContextMenuProps> = ({
);
const metadataVisible = useAppSelector(state => state.layerTree.metadataVisible);

useEffect(() => {
if (layer) {
const layerSource = layer.getSource();
if (layerSource) {
setIsGeoserverLayer(checkIfGeoserverLayer(layerSource));
} else {
setIsGeoserverLayer(false);
}
}
}, [layer]);

const onContextMenuItemClick = (evt: MenuInfo): void => {
if (evt?.key.startsWith('downloadLayer')) {
const url = evt.key.split('|')[1];
downloadLayer(decodeURI(url));
}
switch (evt?.key) {
case 'geostyler':
configureStyles();
break;
case 'zoomToExtent':
zoomToLayerExtent();
break;
Expand Down Expand Up @@ -238,6 +255,13 @@ export const LayerTreeContextMenu: React.FC<LayerTreeContextMenuProps> = ({
a.click();
};

const configureStyles = () => {
if (layer) {
dispatch(setStylingDrawerVisibility(true));
dispatch(setStylingDrawerLayerUid(getUid(layer)));
}
};

const dropdownMenuItems: ItemType[] = [];

if (isWmsLayer(layer)) {
Expand Down Expand Up @@ -299,6 +323,12 @@ export const LayerTreeContextMenu: React.FC<LayerTreeContextMenuProps> = ({
key: 'layerDetails'
});
}
if (isGeoserverLayer) {
dropdownMenuItems.push({
label: t('LayerTreeContextMenu.styleLayer'),
key: 'geostyler'
});
}

return (
<div
Expand Down
6 changes: 4 additions & 2 deletions src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export default {
hideLegend: 'Legende ausblenden',
downloadLayer: 'Layer exportieren ({{formatName}})',
editLayer: 'Layer bearbeiten',
layerDetails: 'Eigenschaften'
layerDetails: 'Eigenschaften',
styleLayer: 'Layer Stil'
},
LayerDetailsModal: {
title: 'Eigenschaften des Layers {{layerName}}',
Expand Down Expand Up @@ -386,7 +387,8 @@ export default {
hideLegend: 'Hide legend',
downloadLayer: 'Export layer as {{formatName}}',
editLayer: 'Edit layer',
layerDetails: 'Properties'
layerDetails: 'Properties',
styleLayer: 'Layer style'
},
LayerDetailsModal: {
title: 'Properties of layer {{layerName}}',
Expand Down
2 changes: 2 additions & 0 deletions src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import logoPath from './logoPath';
import print from './print';
import searchEngines from './searchEngines';
import selectedFeatures from './selectedFeatures';
import stylingDrawerLayerUid from './stylingDrawerLayerUid';
import stylingDrawerVisibility from './stylingDrawerVisibility';
import title from './title';
import toolMenu from './toolMenu';
Expand Down Expand Up @@ -50,6 +51,7 @@ export const createReducer = (asyncReducers?: AsyncReducer) => {
searchEngines,
user,
stylingDrawerVisibility,
stylingDrawerLayerUid,
...asyncReducers
});
};
Expand Down
25 changes: 25 additions & 0 deletions src/store/stylingDrawerLayerUid/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
createSlice, PayloadAction
} from '@reduxjs/toolkit';

const initialState = '';

export const slice = createSlice({
name: 'stylingDrawerLayer',
initialState,
reducers: {
setStylingDrawerLayerUid: (state, action: PayloadAction<string>) => {
return action.payload;
},
clearStylingDrawerLayerUid: () => {
return '';
}
}
});

export const {
setStylingDrawerLayerUid,
clearStylingDrawerLayerUid
} = slice.actions;

export default slice.reducer;
Loading
Loading