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

#10684: Legend filtering for GeoServer WMS layers #10718

Merged
merged 7 commits into from
Dec 19, 2024
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
20 changes: 15 additions & 5 deletions web/client/api/WMS.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import urlUtil from 'url';
import { isArray, castArray, get } from 'lodash';
import xml2js from 'xml2js';
import axios from '../libs/ajax';
import { getConfigProp } from '../utils/ConfigUtils';
import ConfigUtils, { getConfigProp } from '../utils/ConfigUtils';
import { getWMSBoundingBox } from '../utils/CoordinatesUtils';
import { isValidGetMapFormat, isValidGetFeatureInfoFormat } from '../utils/WMSUtils';
const capabilitiesCache = {};
Expand Down Expand Up @@ -323,15 +323,25 @@ export const getSupportedFormat = (url, includeGFIFormats = false) => {

let layerLegendJsonData = {};
export const getJsonWMSLegend = (url) => {
const request = layerLegendJsonData[url]
? () => Promise.resolve(layerLegendJsonData[url])
: () => axios.get(url).then((response) => {
let request;

// enables caching of the JSON legend for a specified duration,
// while providing the possibility of re-fetching the legend data in case of external modifications
const cached = layerLegendJsonData[url];
if (cached && new Date().getTime() < cached.timestamp + (ConfigUtils.getConfigProp('cacheExpire') || 60) * 1000) {
request = () => Promise.resolve(cached.data);
} else {
request = () => axios.get(url).then((response) => {
if (typeof response?.data === 'string' && response.data.includes("Exception")) {
throw new Error("Faild to get json legend");
}
layerLegendJsonData[url] = response?.data?.Legend;
layerLegendJsonData[url] = {
timestamp: new Date().getTime(),
data: response?.data?.Legend
};
return response?.data?.Legend || [];
});
}
return request().then((data) => data).catch(err => {
throw err;
});
Expand Down
16 changes: 14 additions & 2 deletions web/client/components/TOC/fragments/settings/Display.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
* LICENSE file in the root directory of this source tree.
*/

import { clamp, isNil, isNumber } from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import clamp from 'lodash/clamp';
import isNil from 'lodash/isNil';
import isNumber from 'lodash/isNumber';
import pick from 'lodash/pick';
import PropTypes from 'prop-types';
import {Checkbox, Col, ControlLabel, FormGroup, Glyphicon, Grid, Row, Button as ButtonRB } from 'react-bootstrap';

import tooltip from '../../../misc/enhancers/buttonTooltip';
const Button = tooltip(ButtonRB);
import IntlNumberFormControl from '../../../I18N/IntlNumberFormControl';
Expand All @@ -26,6 +30,7 @@ import ThreeDTilesSettings from './ThreeDTilesSettings';
import ModelTransformation from './ModelTransformation';
import StyleBasedWMSJsonLegend from '../../../../plugins/TOC/components/StyleBasedWMSJsonLegend';
import { getMiscSetting } from '../../../../utils/ConfigUtils';

export default class extends React.Component {
static propTypes = {
opacityText: PropTypes.node,
Expand All @@ -38,6 +43,8 @@ export default class extends React.Component {
isLocalizedLayerStylesEnabled: PropTypes.bool,
isCesiumActive: PropTypes.bool,
projection: PropTypes.string,
mapSize: PropTypes.object,
mapBbox: PropTypes.object,
resolutions: PropTypes.array,
zoom: PropTypes.number,
hideInteractiveLegendOption: PropTypes.bool
Expand Down Expand Up @@ -122,6 +129,9 @@ export default class extends React.Component {
}
return null;
};
getLegendProps = () => {
return pick(this.props, ['projection', 'mapSize', 'mapBbox']);
}
render() {
const formatValue = this.props.element && this.props.element.format || "image/png";
const experimentalInteractiveLegend = getMiscSetting('experimentalInteractiveLegend', false);
Expand Down Expand Up @@ -324,6 +334,7 @@ export default class extends React.Component {
this.useLegendOptions() && this.state.legendOptions.legendWidth || undefined}
language={
this.props.isLocalizedLayerStylesEnabled ? this.props.currentLocaleLanguage : undefined}
{...this.getLegendProps()}
/> :
<Legend
style={this.setOverFlow() && {} || undefined}
Expand All @@ -334,6 +345,7 @@ export default class extends React.Component {
this.useLegendOptions() && this.state.legendOptions.legendWidth || undefined}
language={
this.props.isLocalizedLayerStylesEnabled ? this.props.currentLocaleLanguage : undefined}
{...this.getLegendProps()}
/>}
</div>
</Col>
Expand Down
3 changes: 3 additions & 0 deletions web/client/components/widgets/builder/wizard/map/TOC.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ function WidgetTOC({
visualizationMode: map?.visualizationMode,
layerOptions: {
legendOptions: {
projection: map?.projection,
mapSize: map?.size,
mapBbox: map?.bbox,
WMSLegendOptions: 'forceLabels:on',
scaleDependent: true,
legendWidth: 12,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export default compose(
: 1
}
},
projection: map.projection,
mapSize: map.size,
mapBbox: map.bbox,
groups: get(splitMapAndLayers(map), 'layers.groups')
})),
// adapter for handlers
Expand Down
8 changes: 6 additions & 2 deletions web/client/components/widgets/enhancers/legendWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { editableWidget, defaultIcons, withHeaderTools } from './tools';
import { getScales } from '../../../utils/MapUtils';
import { WIDGETS_MAPS_REGEX } from "../../../actions/widgets";
import { getInactiveNode, DEFAULT_GROUP_ID } from '../../../utils/LayersUtils';
import { updateLayerWithLegendFilters } from '../../../utils/LegendUtils';

/**
* map dependencies to layers, scales and current zoom level to show legend items for current zoom.
Expand All @@ -22,19 +23,22 @@ export default compose(
withProps(({ dependencies = {}, dependenciesMap = {} }) => {
const allLayers = dependencies[dependenciesMap.layers] || dependencies.layers || [];
const groups = castArray(dependencies[dependenciesMap.groups] || dependencies.groups || []);
const layers = allLayers
let layers = allLayers
// filter backgrounds and inactive layer
// the inactive layers are the one with a not visible parent group
.filter((layer = {}) =>
layer.group !== 'background' && !getInactiveNode(layer?.group || DEFAULT_GROUP_ID, groups)
)
.map(({ group, ...layer }) => layer);
layers = updateLayerWithLegendFilters(layers, dependencies);
return {
allLayers,
map: {
layers,
// use empty so it creates the default group that will be hidden in the layers tree
groups: []
groups: [],
projection: dependencies.projection,
bbox: dependencies.viewport
},
dependencyMapPath: dependenciesMap.layers || '',
scales: getScales(
Expand Down
7 changes: 6 additions & 1 deletion web/client/components/widgets/widget/LegendView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ export default ({
scales,
zoom: currentZoomLvl,
layerOptions: {
legendOptions: legendProps,
legendOptions: {
...legendProps,
projection: map?.projection,
mapSize: map?.size,
mapBbox: map?.bbox
},
hideFilter: true
}
}}
Expand Down
3 changes: 2 additions & 1 deletion web/client/plugins/Print.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,8 @@ export default {
this.props.onBeforePrint();
this.props.printingService.print({
layers: this.getMapConfiguration()?.layers,
scales: this.props.useFixedScales ? getPrintScales(this.props.capabilities) : undefined
scales: this.props.useFixedScales ? getPrintScales(this.props.capabilities) : undefined,
bbox: this.props.map?.bbox
})
.then((spec) =>
this.props.onPrint(this.props.capabilities.createURL, { ...spec, ...this.props.overrideOptions })
Expand Down
49 changes: 24 additions & 25 deletions web/client/plugins/TOC/components/Legend.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
* LICENSE file in the root directory of this source tree.
*/

import urlUtil from 'url';

import { isArray, isNil } from 'lodash';
import assign from 'object-assign';
import PropTypes from 'prop-types';
import React from 'react';
import urlUtil from 'url';
import isArray from 'lodash/isArray';
import isNil from 'lodash/isNil';
import pick from 'lodash/pick';

import {
addAuthenticationToSLD,
Expand All @@ -21,6 +21,8 @@ import Message from '../../../components/I18N/Message';
import SecureImage from '../../../components/misc/SecureImage';

import { randomInt } from '../../../utils/RandomUtils';
import { normalizeSRS } from '../../../utils/CoordinatesUtils';
import { getWMSLegendConfig, LEGEND_FORMAT } from '../../../utils/LegendUtils';

/**
* Legend renders the wms legend image
Expand All @@ -44,7 +46,10 @@ class Legend extends React.Component {
currentZoomLvl: PropTypes.number,
scales: PropTypes.array,
scaleDependent: PropTypes.bool,
language: PropTypes.string
language: PropTypes.string,
projection: PropTypes.string,
mapSize: PropTypes.object,
bbox: PropTypes.object
};

static defaultProps = {
Expand Down Expand Up @@ -86,26 +91,20 @@ class Legend extends React.Component {

const cleanParams = clearNilValuesForParams(layer.params);
const scale = this.getScale(props);
let query = assign(
{},
{
service: "WMS",
request: "GetLegendGraphic",
format: "image/png",
height: props.legendHeight,
width: props.legendWidth,
layer: layer.name,
style: layer.style || null,
version: layer.version || "1.3.0",
SLD_VERSION: "1.1.0",
LEGEND_OPTIONS: props.legendOptions
},
layer.legendParams || {},
props.language && layer.localizedLayerStyles ? {LANGUAGE: props.language} : {},
addAuthenticationToSLD(cleanParams || {}, props.layer),
cleanParams && cleanParams.SLD_BODY ? {SLD_BODY: cleanParams.SLD_BODY} : {},
scale !== null ? { SCALE: scale } : {}
);
const projection = normalizeSRS(this.props.projection || 'EPSG:3857', layer.allowedSRS);
const query = {
...getWMSLegendConfig({
layer,
format: LEGEND_FORMAT.IMAGE,
...pick(props, ['legendHeight', 'legendWidth', 'mapSize', 'legendOptions', 'mapBbox']),
projection
}),
...layer.legendParams,
...(props.language && layer.localizedLayerStyles ? { LANGUAGE: props.language } : {}),
...addAuthenticationToSLD(cleanParams || {}, props.layer),
...(cleanParams && cleanParams.SLD_BODY ? { SLD_BODY: cleanParams.SLD_BODY } : {}),
...(scale !== null ? { SCALE: scale } : {})
};

return urlUtil.format({
host: urlObj.host,
Expand Down
Loading
Loading