diff --git a/docs/user-guide/attributes-table.md b/docs/user-guide/attributes-table.md
index de20b5df32..1c60c826f1 100644
--- a/docs/user-guide/attributes-table.md
+++ b/docs/user-guide/attributes-table.md
@@ -228,3 +228,11 @@ With a click on the button:
+
+## 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 :
+
+
diff --git a/docs/user-guide/img/attributes-table/restricted_area_icon.png b/docs/user-guide/img/attributes-table/restricted_area_icon.png
new file mode 100644
index 0000000000..eff0e2207d
Binary files /dev/null and b/docs/user-guide/img/attributes-table/restricted_area_icon.png differ
diff --git a/web/client/components/data/featuregrid/toolbars/Toolbar.jsx b/web/client/components/data/featuregrid/toolbars/Toolbar.jsx
index dda9c9416c..bae1b34b51 100644
--- a/web/client/components/data/featuregrid/toolbars/Toolbar.jsx
+++ b/web/client/components/data/featuregrid/toolbars/Toolbar.jsx
@@ -37,7 +37,7 @@ const standardButtons = {
onClick={events.switchEditMode}
glyph="pencil" />),
isRestrictedByArea: ({ restrictedArea }) => {
- return
- },
+ width: 0,
+ padding: 0,
+ borderWidth: 0
+ } : {}}>
+
+ );
+ },
filter: ({isFilterActive = false, viewportFilter, disabled, isSearchAllowed, mode, showAdvancedFilterButton = true, events = {}}) => ( {
// Remove features with geometry null or id "empty_row"
const cleanFeatures = features.filter(ft => {
- console.log("clean features");
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
+ return isValidFeature;
});
if (cleanFeatures.length > 0) {
@@ -503,7 +502,6 @@ export const enableGeometryFilterOnEditMode = (action$, store) =>
action$.ofType(TOGGLE_MODE)
.filter(() => modeSelector(store.getState()) === MODES.EDIT)
.switchMap(() => {
- console.log("enableGeometryFilterOnEditMode")
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,
@@ -1301,35 +1299,33 @@ export const resetViewportFilter = (action$, store) =>
: Rx.Observable.empty();
});
- export const requestRestrictedArea = (action$, store) =>
+export const requestRestrictedArea = (action$, store) =>
action$.ofType(OPEN_FEATURE_GRID, LOGIN_SUCCESS)
- .filter(() =>
- {
+ .filter(() => {
return !isAdminUserSelector(store.getState())
- && isLoggedIn(store.getState())
- && !isEmpty(restrictedAreaSrcSelector(store.getState()))}
- )
- .switchMap((action) => {
+ && 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(result),
+ setRestrictedArea(rawAsGeoJson(result)),
changePage(0)
- )
- })
- } else {
- return Rx.Observable.of(
- setRestrictedArea(src?.raw || {}),
- changePage(0)
- )
+ );
+ });
}
- })
+ return Rx.Observable.of(
+ setRestrictedArea(rawAsGeoJson(src.raw) || {}),
+ changePage(0)
+ );
+ });
export const resetRestrictedArea = (action$, store) =>
action$.ofType(LOGOUT, CLOSE_FEATURE_GRID)
- .filter((a) => !isEmpty(restrictedAreaSrcSelector(store.getState())))
+ .filter(() => !isEmpty(restrictedAreaSrcSelector(store.getState())))
.switchMap(() => Rx.Observable.of(
setRestrictedArea({})
- ))
+ ));
diff --git a/web/client/plugins/FeatureEditor.jsx b/web/client/plugins/FeatureEditor.jsx
index 78ce960d7a..4231efb330 100644
--- a/web/client/plugins/FeatureEditor.jsx
+++ b/web/client/plugins/FeatureEditor.jsx
@@ -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.
* ```
* "dateFormats": {
* "date-time": "MM DD YYYY - HH:mm:ss",
@@ -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,
diff --git a/web/client/plugins/featuregrid/FeatureEditor.jsx b/web/client/plugins/featuregrid/FeatureEditor.jsx
index 0873b815e6..2486f3aa0d 100644
--- a/web/client/plugins/featuregrid/FeatureEditor.jsx
+++ b/web/client/plugins/featuregrid/FeatureEditor.jsx
@@ -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
@@ -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,
diff --git a/web/client/reducers/featuregrid.js b/web/client/reducers/featuregrid.js
index 5d27311f5f..fd3e671d91 100644
--- a/web/client/reducers/featuregrid.js
+++ b/web/client/reducers/featuregrid.js
@@ -48,7 +48,7 @@ import {
UPDATE_EDITORS_OPTIONS,
SET_PAGINATION,
SET_VIEWPORT_FILTER,
- SET_RESTRICTED_AREA,
+ SET_RESTRICTED_AREA
} from '../actions/featuregrid';
import { MAP_CONFIG_LOADED } from '../actions/config';
@@ -443,7 +443,7 @@ 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 } };
}
diff --git a/web/client/selectors/featuregrid.js b/web/client/selectors/featuregrid.js
index 1596f11a67..05c80bc6f7 100644
--- a/web/client/selectors/featuregrid.js
+++ b/web/client/selectors/featuregrid.js
@@ -252,12 +252,12 @@ export const restrictedAreaFilter = createShallowSelectorCreator(isEqual)(
projectionSelector,
describeSelector,
state => restrictedAreaOperatorSelector(state),
- (restrictedArea, spatialField = [], viewportFilter, projection, describeLayer, operator) => {
+ (restrictedArea, spatialField = [], viewPortFilter, projection, describeLayer, operator) => {
const attribute = findGeometryProperty(describeLayer)?.name;
let existingFilter = [];
// if activate, viewportFilter already get existing filter
- if(isEmpty(viewportFilter) && !isEmpty(spatialField)) {
- existingFilter = spatialField?.operation ? [spatialField] : spatialField
+ if (isEmpty(viewPortFilter) && !isEmpty(spatialField)) {
+ existingFilter = spatialField?.operation ? [spatialField] : spatialField;
}
return !isEmpty(restrictedArea) ? {
spatialField: [
@@ -275,7 +275,7 @@ export const restrictedAreaFilter = createShallowSelectorCreator(isEqual)(
]
} : {};
}
-)
+);
/**
* Create spatialField filters array.
@@ -284,5 +284,5 @@ export const restrictedAreaFilter = createShallowSelectorCreator(isEqual)(
export const additionnalGridFilters = (state) => {
const restrictedArea = restrictedAreaFilter(state)?.spatialField || [];
const viewport = viewportFilter(state)?.spatialField || [];
- return {spatialField: [...restrictedArea, ...viewport]}
-}
+ return {spatialField: [...restrictedArea, ...viewport]};
+};
diff --git a/web/client/utils/FeatureGridUtils.js b/web/client/utils/FeatureGridUtils.js
index b5406acd93..badabf7da6 100644
--- a/web/client/utils/FeatureGridUtils.js
+++ b/web/client/utils/FeatureGridUtils.js
@@ -17,6 +17,8 @@ import {
isValidValueForPropertyName as isValidValueForPropertyNameBase
} from './ogc/WFS/base';
+import { WKT } from 'ol/format';
+
import { applyDefaultToLocalizedString } from '../components/I18N/LocalizedString';
const getGeometryName = (describe) => get(findGeometryProperty(describe), "name");
@@ -392,3 +394,38 @@ export const supportsFeatureEditing = (layer) => includes(supportedEditLayerType
* @returns {boolean} flag
*/
export const areLayerFeaturesEditable = (layer) => !layer?.disableFeaturesEditing && supportsFeatureEditing(layer);
+
+export const isWKT = (wktString) => {
+ let isWKTGeom = false;
+ try {
+ const reader = new WKT();
+ const feature = reader.readFeature(wktString);
+ if (feature) {
+ isWKTGeom = true;
+ }
+ } catch (e) {
+ isWKTGeom = false;
+ }
+ return isWKTGeom;
+};
+
+export const wktToGeoJson = (wktString) => {
+ const reader = new WKT();
+ const feature = reader.readFeature(wktString);
+ return {
+ type: feature.getGeometry().getType(),
+ coordinates: feature.getGeometry().getCoordinates()
+ };
+};
+
+/**
+ * Return GeoJSON geometry. Transform WKT to GeoJSON if necessary.
+ * @param {string} raw - geometry
+ * @returns geometry object
+ */
+export const rawAsGeoJson = (raw) => {
+ if (isWKT(raw)) {
+ return wktToGeoJson(raw);
+ }
+ return raw;
+};