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

#10514 - FeatureEditor filter by geometric area #10515

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions docs/user-guide/attributes-table.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,11 @@ With a click on the <img src="../img/button/export_at.jpg" class="ms-docbutton"/
* Deciding which columns to show and which to hide through the <img src="../img/button/hide_show_col.jpg" class="ms-docbutton"/> button:

<video class="ms-docimage" controls><source src="../img/attributes-table/show_hide_columns.mp4"/></video>

## Restriction by area

MapStore [allows to configure](https://mapstore.geosolutionsgroup.com/mapstore/docs/api/plugins#plugins.FeatureEditor) attribute table in order to limit features consultation by a geometric area.

Note that this restriction is never active for adminstrators. If active, the user see an icon to the left of the attribute table toolbar :

<img src="../img/attributes-table/restricted_area_icon.png" class="ms-docbutton"/>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions web/client/actions/featuregrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

export const SET_UP = 'FEATUREGRID:SET_UP';
export const SET_RESTRICTED_AREA = "FEATUREGRID:SET_RESTRICTED_AREA";
export const SELECT_FEATURES = 'FEATUREGRID:SELECT_FEATURES';
export const DESELECT_FEATURES = 'FEATUREGRID:DESELECT_FEATURES';
export const CLEAR_SELECTION = 'FEATUREGRID:CLEAR_SELECTION';
Expand Down Expand Up @@ -404,3 +405,8 @@ export const setViewportFilter = (viewportFilter) => ({
type: SET_VIEWPORT_FILTER,
value: viewportFilter
});

export const setRestrictedArea = (area) => ({
type: SET_RESTRICTED_AREA,
area: area
});
6 changes: 6 additions & 0 deletions web/client/components/data/featuregrid/FeatureGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class FeatureGrid extends React.PureComponent {
static propTypes = {
autocompleteEnabled: PropTypes.bool,
editingAllowedRoles: PropTypes.array,
restrictedArea: PropTypes.object,
restrictedAreaUrl: PropTypes.string,
restrictedAreaOperator: PropTypes.string,
gridOpts: PropTypes.object,
changes: PropTypes.object,
selectBy: PropTypes.object,
Expand All @@ -56,6 +59,9 @@ class FeatureGrid extends React.PureComponent {
};
static defaultProps = {
editingAllowedRoles: ["ADMIN"],
restrictedArea: {},
restrictedAreaUrl: "",
restrictedAreaOperator: "CONTAINS",
autocompleteEnabled: false,
gridComponent: AdaptiveGrid,
changes: {},
Expand Down
23 changes: 20 additions & 3 deletions web/client/components/data/featuregrid/toolbars/Toolbar.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import './toolbar.css';
import { sortBy } from 'lodash';
import {ButtonGroup, Checkbox, Glyphicon, FormControl, FormGroup, Col} from 'react-bootstrap';
import { sortBy, isEmpty } from 'lodash';
import {ButtonGroup, Checkbox, Glyphicon, FormControl, FormGroup, Col, Button} from 'react-bootstrap';

import Message from '../../../I18N/Message';
import withHint from '../enhancers/withHint';
Expand Down Expand Up @@ -35,7 +35,23 @@ const standardButtons = {
disabled={disabled}
visible={mode === "VIEW" && isEditingAllowed && areLayerFeaturesEditable(layer)}
onClick={events.switchEditMode}
glyph="pencil"/>),
glyph="pencil" />),
isRestrictedByArea: ({ restrictedArea }) => {
return (<Button
id="fg-isRestrictedByArea-button"
keyProp="fg-restrictedarea-button"
className="square-button-md"
bsStyle="warning"
tooltipId="featuregrid.toolbar.restrictedByArea"
disabled
style={isEmpty(restrictedArea) ? {
width: 0,
padding: 0,
borderWidth: 0
} : {}}>
<Glyphicon glyph="1-point-dashed" />
</Button>);
Comment on lines +39 to +53
Copy link
Member

Choose a reason for hiding this comment

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

Here the tooltipId but the tooltip is not present in translation file.
Moreover the property do not exist for normal bootstrap button. In order to use it you should use TButton (created for feature grid) or Button with tooltip enhancer.
I noticed that bsStyle property anyway is overridden by the TButton.
So in order to have a tooltip here you should:

  • Apply the following changes.
Suggested change
isRestrictedByArea: ({ restrictedArea }) => {
return (<Button
id="fg-isRestrictedByArea-button"
keyProp="fg-restrictedarea-button"
className="square-button-md"
bsStyle="warning"
tooltipId="featuregrid.toolbar.restrictedByArea"
disabled
style={isEmpty(restrictedArea) ? {
width: 0,
padding: 0,
borderWidth: 0
} : {}}>
<Glyphicon glyph="1-point-dashed" />
</Button>);
isRestrictedByArea: ({ restrictedArea }) => {
return (<TButton
id="fg-isRestrictedByArea-button"
keyProp="fg-restrictedarea-button"
className="square-button-md"
bsStyle="warning"
visible={!isEmpty(restrictedArea)}
tooltipId="featuregrid.toolbar.restrictedByArea"
glyph="1-point-dashed"
/>);
},
  • insert a localized string for each translation file (at least mandatory ones, as usual)
  • Fix TButton like this (putting bsStyle after the forced one).
+++ b/web/client/components/data/featuregrid/toolbars/TButton.jsx
@@ -18,7 +18,7 @@ const hideStyle = {
 const normalStyle = {};
 const getStyle = (visible) => visible ? normalStyle : hideStyle;
 export const SimpleTButton = forwardRef(({ disabled, id, visible, onClick, glyph, active, className = "square-button-md", ...props }, ref) => {
-    return (<Button ref={ref} {...props} bsStyle={active ? "success" : "primary"} disabled={disabled} id={`fg-${id}`}
+    return (<Button ref={ref} bsStyle={active ? "success" : "primary"} {...props} disabled={disabled} id={`fg-${id}`}
         style={getStyle(visible)}
         className={className}
         onClick={() => !disabled && onClick()}>

Schermata del 2024-10-21 14-42-54

Note: I removed "disabled" flag for a better aspect.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. This change will be made.

},
filter: ({isFilterActive = false, viewportFilter, disabled, isSearchAllowed, mode, showAdvancedFilterButton = true, events = {}}) => (<TButton
id="search"
keyProp="search"
Expand Down Expand Up @@ -273,6 +289,7 @@ const standardButtons = {

// standard buttons with position set to index in this array. shape {name, Component, position} is aligned with attributes expected from tools injected.
const buttons = [
{name: "isRestrictedByArea", Component: standardButtons.isRestrictedByArea}, // GRID - features load is restricted by area
{name: "editMode", Component: standardButtons.editMode}, // EDITOR
{name: "backToViewMode", Component: standardButtons.backToViewMode}, // EDITOR
{name: "addFeature", Component: standardButtons.addFeature}, // EDITOR
Expand Down
58 changes: 52 additions & 6 deletions web/client/epics/featuregrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {get, head, isEmpty, find, castArray, includes, reduce} from 'lodash';
import { LOCATION_CHANGE } from 'connected-react-router';
import axios from '../libs/ajax';
import bbox from '@turf/bbox';
import booleanIntersects from "@turf/boolean-intersects";
import { fidFilter } from '../utils/ogc/Filter/filter';
import { getDefaultFeatureProjection, getPagesToLoad, gridUpdateToQueryUpdate, updatePages } from '../utils/FeatureGridUtils';
import { getDefaultFeatureProjection, getPagesToLoad, gridUpdateToQueryUpdate, rawAsGeoJson, updatePages } from '../utils/FeatureGridUtils';

import assign from 'object-assign';
import {
Expand All @@ -25,6 +26,7 @@ import {
import requestBuilder from '../utils/ogc/WFST/RequestBuilder';
import { findGeometryProperty } from '../utils/ogc/WFS/base';
import { FEATURE_INFO_CLICK, HIDE_MAPINFO_MARKER, closeIdentify, hideMapinfoMarker } from '../actions/mapInfo';
import { LOGIN_SUCCESS, LOGOUT } from "../actions/security";

import {
query,
Expand Down Expand Up @@ -110,7 +112,8 @@ import {
setPagination,
launchUpdateFilterFunc,
LAUNCH_UPDATE_FILTER_FUNC, SET_LAYER,
SET_VIEWPORT_FILTER, setViewportFilter
SET_VIEWPORT_FILTER, setViewportFilter,
setRestrictedArea
} from '../actions/featuregrid';

import {
Expand Down Expand Up @@ -144,7 +147,10 @@ import {
getAttributeFilters,
selectedLayerSelector,
multiSelect,
paginationSelector, isViewportFilterActive, viewportFilter
paginationSelector, isViewportFilterActive, viewportFilter,
restrictedAreaSrcSelector,
restrictedAreaSelector,
additionnalGridFilters
} from '../selectors/featuregrid';

import { error, warning } from '../actions/notifications';
Expand All @@ -169,6 +175,7 @@ import {dockPanelsSelector} from "../selectors/maplayout";
import {shutdownToolOnAnotherToolDrawing} from "../utils/ControlUtils";
import {mapTypeSelector} from "../selectors/maptype";
import { MapLibraries } from '../utils/MapTypeUtils';
import { isAdminUserSelector, isLoggedIn } from '../selectors/security';

const setupDrawSupport = (state, original) => {
const defaultFeatureProj = getDefaultFeatureProjection();
Expand Down Expand Up @@ -205,7 +212,15 @@ const setupDrawSupport = (state, original) => {
});

// Remove features with geometry null or id "empty_row"
const cleanFeatures = features.filter(ft => ft.geometry !== null || ft.id !== 'empty_row');
const cleanFeatures = features.filter(ft => {
const restrictedArea = restrictedAreaSelector(state);
let isValidFeature = ft.geometry !== null || ft.id !== 'empty_row';
if (isValidFeature && !isEmpty(restrictedArea)) {
// allow only feature inside restricted area
isValidFeature = booleanIntersects(restrictedArea, ft.geometry);
}
return isValidFeature;
});

if (cleanFeatures.length > 0) {
return Rx.Observable.from([
Expand Down Expand Up @@ -262,7 +277,7 @@ const createLoadPageFlow = (store) => ({page, size, reason} = {}) => {
wfsURL(state),
addPagination({
...(wfsFilter(state)),
...viewportFilter(state)
...additionnalGridFilters(state)
},
getPagination(state, {page, size})
),
Expand Down Expand Up @@ -298,7 +313,7 @@ const updateFilterFunc = (store) => ({update = {}, append} = {}) => {
// If an advanced filter is present it's filterFields should be composed with the action'
const {id} = selectedLayerSelector(store.getState());
const filterObj = {...get(store.getState(), `featuregrid.advancedFilters["${id}"]`)};
if (filterObj) {
if (filterObj && !isEmpty(filterObj)) {
// TODO: make append with advanced filters work
const attributesFilter = getAttributeFilters(store.getState()) || {};
const columnsFilters = reduce(attributesFilter, (cFilters, value, attribute) => {
Expand Down Expand Up @@ -1283,3 +1298,34 @@ export const resetViewportFilter = (action$, store) =>
return viewportFilter(store.getState()) !== null ? Rx.Observable.of(setViewportFilter(null))
: Rx.Observable.empty();
});

export const requestRestrictedArea = (action$, store) =>
action$.ofType(OPEN_FEATURE_GRID, LOGIN_SUCCESS)
.filter(() => {
return !isAdminUserSelector(store.getState())
Copy link
Member

Choose a reason for hiding this comment

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

This makes the restriction not be applied for anonymous users. Only for logged, not-admin.
It is a little unconsistent as a restriction.

image

I think we should at least apply the same restrictedArea to the anonymous users?

Maybe a configuration inside the restrictedAreas that defines who has the restriction can be more generic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, we have think avout authenticated users (to works with geOrchestra restricted org area) first and forget anonymous users.

To apply this restriction, we needs to confirm if restricted area is apply in edit mode only or not as explain in global review comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should at least apply the same restrictedArea to the anonymous users?

After discussion, we will also apply the restriction to anonymous users to be compliant with MapStore2 behavior.

&& isLoggedIn(store.getState())
&& !isEmpty(restrictedAreaSrcSelector(store.getState()));
})
.switchMap(() => {
const src = restrictedAreaSrcSelector(store.getState());
if (src.url) {
return Rx.Observable.defer(() => fetch(src?.url).then(r => r?.json?.()))
.switchMap(result => {
return Rx.Observable.of(
setRestrictedArea(rawAsGeoJson(result)),
changePage(0)
);
});
}
return Rx.Observable.of(
setRestrictedArea(rawAsGeoJson(src.raw) || {}),
changePage(0)
);
});

export const resetRestrictedArea = (action$, store) =>
action$.ofType(LOGOUT, CLOSE_FEATURE_GRID)
.filter(() => !isEmpty(restrictedAreaSrcSelector(store.getState())))
.switchMap(() => Rx.Observable.of(
setRestrictedArea({})
));
12 changes: 11 additions & 1 deletion web/client/plugins/FeatureEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ import {isViewportFilterActive} from "../selectors/featuregrid";
* @prop {array} cfg.showFilterByViewportTool Show button to toggle filter by viewport in toolbar.
* @prop {object} cfg.dateFormats Allows to specify custom date formats ( in [ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) format) to use to display dates in the table. `date` `date-time` and `time` are the supported entries for the date format. Example:
* @prop {boolean} cfg.showPopoverSync default false. Hide the popup of map sync if false, shows the popup of map sync if true
* @prop {string} cfg.restrictedArea.url Geometry definition as WKT or GeoJSON loaded from URL or path.
* @prop {string} cfg.restrictedArea.raw Geometry definition as WKT or GeoJSON.
* @prop {string} cfg.restrictedArea.operator Spatial operation to performed between features and the given geometry.
Comment on lines +82 to +84
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* @prop {string} cfg.restrictedArea.url Geometry definition as WKT or GeoJSON loaded from URL or path.
* @prop {string} cfg.restrictedArea.raw Geometry definition as WKT or GeoJSON.
* @prop {string} cfg.restrictedArea.operator Spatial operation to performed between features and the given geometry.
* @prop {object} cfg.restrictedArea object containing settings for restricted area. If present, it restricts the editing area to the given geometry. It requires at least `url` or `raw` to be defined.
* @prop {string} cfg.restrictedArea.raw Geometry definition as WKT or GeoJSON. This attribute allows to define the geometry directly in the configuration.
* @prop {string} cfg.restrictedArea.url Geometry definition as WKT or GeoJSON loaded from URL or path. If present, this wins over the raw geometry configuration. By default, the filter will use `CONTAINS` if not defined.

* ```
* "dateFormats": {
* "date-time": "MM DD YYYY - HH:mm:ss",
Expand Down Expand Up @@ -114,6 +117,11 @@ import {isViewportFilterActive} from "../selectors/featuregrid";
* },
* "editingAllowedRoles": ["ADMIN"],
* "snapTool": true,
* "restrictedArea": {
* "url": "/wkt_or_geojson_geometry",
* "raw": "POLYGON ((-64.8 32.3, -65.5 18.3, -80.3 25.2, -64.8 32.3))",
* "operator": "WITHIN"
* },
* "snapConfig": {
* "vertex": true,
* "edge": true,
Expand Down Expand Up @@ -175,7 +183,9 @@ const EditorPlugin = connect(
virtualScroll: this.props.virtualScroll ?? true,
editingAllowedRoles: this.props.editingAllowedRoles,
editingAllowedGroups: this.props.editingAllowedGroups,
maxStoredPages: this.props.maxStoredPages
maxStoredPages: this.props.maxStoredPages,
restrictedAreaUrl: this.props.restrictedAreaUrl,
restrictedArea: this.props.restrictedArea
});
},
componentDidUpdate(prevProps) {
Expand Down
8 changes: 8 additions & 0 deletions web/client/plugins/featuregrid/FeatureEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ const Dock = connect(createSelector(
* @prop {array} cfg.snapConfig.additionalLayers Array of additional layers to include into snapping layers list. Provides a way to include layers from "state.additionallayers"
* @prop {object} cfg.dateFormats object containing custom formats for one of the date/time attribute types. Following keys are supported: "date-time", "date", "time"
* @prop {boolean} cfg.showPopoverSync default false. Hide the popup of map sync if false, shows the popup of map sync if true
* @prop {string} cfg.restrictedArea.url Geometry definition as WKT or GeoJSON loaded from URL or path.
* @prop {string} cfg.restrictedArea.raw Geometry definition as WKT or GeoJSON.
* @prop {string} cfg.restrictedArea.operator Spatial operation to performed between features and the given geometry.
*
* @classdesc
* `FeatureEditor` Plugin, also called *FeatureGrid*, provides functionalities to browse/edit data via WFS. The grid can be configured to use paging or
Expand Down Expand Up @@ -124,6 +127,11 @@ const Dock = connect(createSelector(
* },
* "editingAllowedRoles": ["ADMIN"],
* "snapTool": true,
* "restrictedArea": {
* "url": "/wkt_or_geojson_geometry",
* "raw": "POLYGON ((-64.8 32.3, -65.5 18.3, -80.3 25.2, -64.8 32.3))",
* "operator": "WITHIN"
* },
* "snapConfig": {
* "vertex": true,
* "edge": true,
Expand Down
4 changes: 3 additions & 1 deletion web/client/plugins/featuregrid/panels/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ import {
timeSyncActive,
isViewportFilterActive,
isFilterByViewportSupported,
selectedLayerSelector
selectedLayerSelector,
restrictedAreaSelector
} from '../../../selectors/featuregrid';
import { mapLayoutValuesSelector } from '../../../selectors/maplayout';
import {isCesium, mapTypeSelector} from '../../../selectors/maptype';
Expand Down Expand Up @@ -95,6 +96,7 @@ const Toolbar = connect(
disableZoomAll: (state) => state && state.featuregrid.virtualScroll || featureCollectionResultSelector(state).features.length === 0,
isSearchAllowed: (state) => !isCesium(state),
isEditingAllowed: isEditingAllowedSelector,
restrictedArea: restrictedAreaSelector,
hasSupportedGeometry,
isFilterActive,
showTimeSyncButton: showTimeSync,
Expand Down
10 changes: 8 additions & 2 deletions web/client/reducers/featuregrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ import {
SET_TIME_SYNC,
UPDATE_EDITORS_OPTIONS,
SET_PAGINATION,
SET_VIEWPORT_FILTER
SET_VIEWPORT_FILTER,
SET_RESTRICTED_AREA
} from '../actions/featuregrid';
import { MAP_CONFIG_LOADED } from '../actions/config';

Expand Down Expand Up @@ -156,7 +157,9 @@ function featuregrid(state = emptyResultsState, action) {
editingAllowedRoles: action.options.editingAllowedRoles || state.editingAllowedRoles || ["ADMIN"],
editingAllowedGroups: action.options.editingAllowedGroups || state.editingAllowedGroups || [],
virtualScroll: !!action.options.virtualScroll,
maxStoredPages: action.options.maxStoredPages || 5
maxStoredPages: action.options.maxStoredPages || 5,
restrictedAreaUrl: action.options.restrictedAreaUrl || "",
restrictedArea: action.options.restrictedArea || {}
});
}
case LOAD_MORE_FEATURES:
Expand Down Expand Up @@ -441,6 +444,9 @@ function featuregrid(state = emptyResultsState, action) {
case MAP_CONFIG_LOADED: {
return {...state, ...get(action, 'config.featureGrid', {})};
}
case SET_RESTRICTED_AREA: {
return { ...state, restrictedArea: { ...state.restrictedArea, geometry: action.area } };
}
default:
return state;
}
Expand Down
Loading
Loading