Skip to content

Commit

Permalink
#10684: Legend filtering for GeoServer WMS layers (#10718)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsuren1 authored Dec 19, 2024
1 parent d70bae0 commit d1ebec1
Show file tree
Hide file tree
Showing 25 changed files with 824 additions and 169 deletions.
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

0 comments on commit d1ebec1

Please sign in to comment.