Skip to content
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.
13 changes: 13 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 @@ -65,6 +66,7 @@
export const GRID_QUERY_RESULT = 'FEATUREGRID:QUERY_RESULT';
export const SET_TIME_SYNC = "FEATUREGRID:SET_TIME_SYNC";
export const SET_PAGINATION = "FEATUREGRID:SET_PAGINATION";
export const TOGGLE_RESTRICTED_AREA = "FEATUREGRID:TOGGLE_RESTRICTED_AREA";

export function toggleShowAgain() {
return {
Expand Down Expand Up @@ -403,3 +405,14 @@
type: SET_VIEWPORT_FILTER,
value: viewportFilter
});

export const setRestrictedArea = (area) => ({
type: SET_RESTRICTED_AREA,
area: area
});
export function toggleRestrictedArea(activate) {
return {
type: TOGGLE_RESTRICTED_AREA,
activate
};
}

Check failure on line 418 in web/client/actions/featuregrid.js

View workflow job for this annotation

GitHub Actions / test-front-end

Newline required at end of file but not found
4 changes: 4 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,8 @@ class FeatureGrid extends React.PureComponent {
static propTypes = {
autocompleteEnabled: PropTypes.bool,
editingAllowedRoles: PropTypes.array,
restrictedArea: PropTypes.object,
restrictedAreaOperator: PropTypes.string,
gridOpts: PropTypes.object,
changes: PropTypes.object,
selectBy: PropTypes.object,
Expand All @@ -56,6 +58,8 @@ class FeatureGrid extends React.PureComponent {
};
static defaultProps = {
editingAllowedRoles: ["ADMIN"],
restrictedArea: {},
restrictedAreaOperator: "CONTAINS",
autocompleteEnabled: false,
gridComponent: AdaptiveGrid,
changes: {},
Expand Down
17 changes: 15 additions & 2 deletions web/client/components/data/featuregrid/toolbars/Toolbar.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import './toolbar.css';
import { sortBy } from 'lodash';
import { sortBy, isEmpty } from 'lodash';

Check failure on line 3 in web/client/components/data/featuregrid/toolbars/Toolbar.jsx

View workflow job for this annotation

GitHub Actions / test-front-end

'isEmpty' is defined but never used
import {ButtonGroup, Checkbox, Glyphicon, FormControl, FormGroup, Col} from 'react-bootstrap';

import Message from '../../../I18N/Message';
Expand Down Expand Up @@ -35,7 +35,19 @@
disabled={disabled}
visible={mode === "VIEW" && isEditingAllowed && areLayerFeaturesEditable(layer)}
onClick={events.switchEditMode}
glyph="pencil"/>),
glyph="pencil" />),
isRestrictedByArea: ({ restrictedAreaActivate}) => {
return (<TButton
id="fg-isRestrictedByArea-button"
keyProp="fg-restrictedarea-button"
className="square-button-md"
bsStyle="warning"
tooltipId="featuregrid.toolbar.restrictedByArea"
visible={restrictedAreaActivate}
glyph="1-point-dashed"
disabled
/>);
},
filter: ({isFilterActive = false, viewportFilter, disabled, isSearchAllowed, mode, showAdvancedFilterButton = true, events = {}}) => (<TButton
id="search"
keyProp="search"
Expand Down Expand Up @@ -273,6 +285,7 @@

// 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
133 changes: 115 additions & 18 deletions web/client/epics/featuregrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
import { LOCATION_CHANGE } from 'connected-react-router';
import axios from '../libs/ajax';
import bbox from '@turf/bbox';
import { createChangesTransaction, getDefaultFeatureProjection, getPagesToLoad, gridUpdateToQueryUpdate, updatePages } from '../utils/FeatureGridUtils';
import booleanIntersects from "@turf/boolean-intersects";

Check failure on line 14 in web/client/epics/featuregrid.js

View workflow job for this annotation

GitHub Actions / test-front-end

'booleanIntersects' is defined but never used
import { createChangesTransaction, getDefaultFeatureProjection, getPagesToLoad, gridUpdateToQueryUpdate, updatePages, rawAsGeoJson } from '../utils/FeatureGridUtils';

import assign from 'object-assign';
import {
Expand All @@ -24,6 +25,7 @@
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";

Check failure on line 28 in web/client/epics/featuregrid.js

View workflow job for this annotation

GitHub Actions / test-front-end

'LOGIN_SUCCESS' is defined but never used

import {
query,
Expand Down Expand Up @@ -109,7 +111,11 @@
setPagination,
launchUpdateFilterFunc,
LAUNCH_UPDATE_FILTER_FUNC, SET_LAYER,
SET_VIEWPORT_FILTER, setViewportFilter
SET_VIEWPORT_FILTER, setViewportFilter,
setRestrictedArea,
SET_RESTRICTED_AREA,
toggleRestrictedArea,
TOGGLE_RESTRICTED_AREA
} from '../actions/featuregrid';

import {
Expand Down Expand Up @@ -143,7 +149,13 @@
getAttributeFilters,
selectedLayerSelector,
multiSelect,
paginationSelector, isViewportFilterActive, viewportFilter
paginationSelector, isViewportFilterActive, viewportFilter,
restrictedAreaSrcSelector,
restrictedAreaSelector,
restrictedAreaLayerFilters,
restrictedAreaFilter,
isRestrictedAreaActivated,
featuregridFeaturesSelector
} from '../selectors/featuregrid';

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

const setupDrawSupport = (state, original) => {
const defaultFeatureProj = getDefaultFeatureProjection();
Expand Down Expand Up @@ -204,11 +217,14 @@
});

// 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 => {
let isValidFeature = ft.geometry !== null || ft.id !== 'empty_row';
return isValidFeature;
});

if (cleanFeatures.length > 0) {
return Rx.Observable.from([
changeDrawingStatus("drawOrEdit", geomType, "featureGrid", cleanFeatures, drawOptions)
changeDrawingStatus("drawOrEdit", geomType, "featureGrid", features, drawOptions)
]);
}

Expand Down Expand Up @@ -252,7 +268,8 @@
wfsURL(state),
addPagination({
...(wfsFilter(state)),
...viewportFilter(state)
...viewportFilter(state),
...restrictedAreaLayerFilters(state, "EPSG:4326")
},
getPagination(state, {page, size})
),
Expand Down Expand Up @@ -450,6 +467,21 @@
});
});

/**
* Performs the query when the restricted Area filter is activate.
* @memberof epics.featuregrid
*/
export const limitSelectionToRestrictedArea = (action$, store) =>
action$.ofType(FEATURE_LOADING)
.filter(() => isRestrictedAreaActivated(store.getState()))

Check failure on line 476 in web/client/epics/featuregrid.js

View workflow job for this annotation

GitHub Actions / test-front-end

Expected indentation of 8 spaces but found 4
.switchMap(() => {

Check failure on line 477 in web/client/epics/featuregrid.js

View workflow job for this annotation

GitHub Actions / test-front-end

Expected indentation of 8 spaces but found 4
const featureSelected = selectedFeaturesSelector(store.getState());

Check failure on line 478 in web/client/epics/featuregrid.js

View workflow job for this annotation

GitHub Actions / test-front-end

Expected indentation of 12 spaces but found 8
let loadedFeatures = featuregridFeaturesSelector(store.getState());

Check failure on line 479 in web/client/epics/featuregrid.js

View workflow job for this annotation

GitHub Actions / test-front-end

Expected indentation of 12 spaces but found 8
let loadedSelection = featureSelected.filter(f => loadedFeatures.map(x => x.id).includes(f.id));

Check failure on line 480 in web/client/epics/featuregrid.js

View workflow job for this annotation

GitHub Actions / test-front-end

Expected indentation of 12 spaces but found 8

Check failure on line 481 in web/client/epics/featuregrid.js

View workflow job for this annotation

GitHub Actions / test-front-end

Trailing spaces not allowed
return Rx.Observable.of(selectFeatures(loadedSelection));
});

/**
* @memberof epics.featuregrid
* this epic has been created because there was a non correct sequence of actions dispatched by featureGridUpdateGeometryFilter when CLOSE_FEATURE_GRID was triggered
Expand Down Expand Up @@ -478,11 +510,38 @@
.filter(() => modeSelector(store.getState()) === MODES.EDIT)
.switchMap(() => {
const currentFilter = find(getAttributeFilters(store.getState()), f => f.type === 'geometry') || {};
return currentFilter.value ? Rx.Observable.empty() : Rx.Observable.of(updateFilter({
attribute: findGeometryProperty(describeSelector(store.getState())).name,
enabled: true,
type: "geometry"
}));
return currentFilter.value ? Rx.Observable.empty() : Rx.Observable.of(
updateFilter({
attribute: findGeometryProperty(describeSelector(store.getState())).name,
enabled: true,
type: "geometry"
})
);
});

/**
* Reload featuregrid features according to restricted area filter.
* @memberof epics.featuregrid
*/
export const changeRestrictedAreaOnModeChange = (action$, store) =>
action$.ofType(TOGGLE_MODE)
.filter(() =>{
return [MODES.VIEW, MODES.EDIT].includes(modeSelector(store.getState()))
})
.switchMap((action) => {
let newStatus = false;
if(action.mode === MODES.VIEW) {
newStatus = false;
}
if(action.mode === MODES.EDIT && !isEmpty(restrictedAreaSelector(store.getState()))) {
newStatus = true;
}

return Rx.Observable.from([
toggleRestrictedArea(newStatus),
updateFilterFunc(store),
changePage(0)
])
});
/**
* @memberof epics.featuregird
Expand Down Expand Up @@ -650,8 +709,9 @@
let features = get(ra, "result.features", []);
const multipleSelect = multiSelect(store.getState());
const geometryFilter = find(getAttributeFilters(store.getState()), f => f.type === 'geometry');
if (multipleSelect && geometryFilter?.enabled) {
features = selectedFeaturesSelector(store.getState());
const selectedFeatures = selectedFeaturesSelector(store.getState());
if (multipleSelect && geometryFilter?.enabled && !isEmpty(selectedFeatures) && !isRestrictedAreaActivated(store.getState())) {
features = selectedFeatures;
}
// TODO: Handle pagination when multi-select due to control
return featureGridQueryResult(features, [get(ra, "filterObj.pagination.startIndex")]);
Expand Down Expand Up @@ -1115,20 +1175,25 @@
*
*/
export const syncMapWmsFilter = (action$, store) =>
action$.ofType(QUERY_CREATE, UPDATE_QUERY).
action$.ofType(QUERY_CREATE, UPDATE_QUERY, SET_RESTRICTED_AREA).
filter((a) => {
const {disableQuickFilterSync} = (store.getState()).featuregrid;
return a.type === QUERY_CREATE || !disableQuickFilterSync;
})
.switchMap(() => {
const {query: q, featuregrid: f} = store.getState();
const layerId = (f || {}).selectedLayer;
const filter = (q || {}).filterObj;
const queryFilters = (store.getState()?.query || {}).filterObj;
const layerId = selectedLayerIdSelector(store.getState());
return Rx.Observable.merge(
Rx.Observable.of(isSyncWmsActive(store.getState())).filter(a => a),
action$.ofType(START_SYNC_WMS))
.mergeMap(() => {
return Rx.Observable.of(addFilterToWMSLayer(layerId, filter));
let restrictedArea = isRestrictedAreaActivated(store.getState());
let filters = [...(queryFilters?.filters || [])].filter(f => !f.restrictedArea);
if(restrictedArea) {
filters = [...filters, restrictedAreaFilter(store.getState())];
}
queryFilters.filters = filters;
return Rx.Observable.of(addFilterToWMSLayer(layerId, queryFilters));
});
});
export const virtualScrollLoadFeatures = (action$, {getState}) =>
Expand Down Expand Up @@ -1273,3 +1338,35 @@
return viewportFilter(store.getState()) !== null ? Rx.Observable.of(setViewportFilter(null))
: Rx.Observable.empty();
});


export const requestRestrictedArea = (action$, store) =>
action$.ofType(SET_RESTRICTED_AREA, TOGGLE_RESTRICTED_AREA)
.filter(() => {
return !isAdminUserSelector(store.getState())
Comment thread
Gaetanbrl marked this conversation as resolved.
&& !isEmpty(restrictedAreaSrcSelector(store.getState()))
&& isEmpty(restrictedAreaSelector(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)
.filter(() => !isEmpty(restrictedAreaSrcSelector(store.getState())))
.switchMap(() => Rx.Observable.of(
setRestrictedArea({})
));
11 changes: 10 additions & 1 deletion web/client/plugins/FeatureEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ import {isViewportFilterActive} from "../selectors/featuregrid";
* @prop {boolean} cfg.useUTCOffset avoid using UTC dates in attribute table and datetime editor, should be kept consistent with dateFormats
* @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 {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 @@ -115,6 +118,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 @@ -176,7 +184,8 @@ const EditorPlugin = connect(
virtualScroll: this.props.virtualScroll ?? true,
editingAllowedRoles: this.props.editingAllowedRoles,
editingAllowedGroups: this.props.editingAllowedGroups,
maxStoredPages: this.props.maxStoredPages
maxStoredPages: this.props.maxStoredPages,
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 @@ -97,6 +97,9 @@ const Dock = connect(createSelector(
* @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.useUTCOffset avoid using UTC dates in attribute table and datetime editor, should be kept consistent with dateFormats, default is true
* @prop {boolean} cfg.showPopoverSync default false. Hide the popup of map sync if false, shows the popup of map sync if true
* @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.
*
* @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 @@ -125,6 +128,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,
isRestrictedAreaActivated
} 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,
restrictedAreaActivate: isRestrictedAreaActivated,
hasSupportedGeometry,
isFilterActive,
showTimeSyncButton: showTimeSync,
Expand Down
Loading