diff --git a/API/Backend/Geodatasets/routes/geodatasets.js b/API/Backend/Geodatasets/routes/geodatasets.js index 94234add1..764580316 100644 --- a/API/Backend/Geodatasets/routes/geodatasets.js +++ b/API/Backend/Geodatasets/routes/geodatasets.js @@ -71,7 +71,7 @@ function get(reqtype, req, res, next) { const filterSplit = req.query.filters.split(","); filters = []; filterSplit.forEach((f) => { - if (f === "OR" || f === "AND" || f === "NOT") { + if (f === "OR" || f === "AND" || f === "NOT_AND" || f === "NOT_OR") { filters.push({ isGroup: true, op: f, @@ -257,9 +257,17 @@ function get(reqtype, req, res, next) { ) { filterSQL.push( `${ - currentGroupOp == "NOT" ? "NOT " : "" + currentGroupOp == "NOT_AND" || currentGroupOp == "NOT_OR" + ? "NOT " + : "" }(${currentGroup.join( - ` ${currentGroupOp == "NOT" ? "AND" : f.op} ` + ` ${ + currentGroupOp == "NOT_AND" + ? "AND" + : currentGroupOp == "NOT_OR" + ? "OR" + : currentGroupOp + } ` )})` ); currentGroup = []; @@ -286,6 +294,12 @@ function get(reqtype, req, res, next) { case "<": op = "<"; break; + case ">=": + op = ">="; + break; + case "<=": + op = "<="; + break; case "in": op = "IN"; break; @@ -294,6 +308,9 @@ function get(reqtype, req, res, next) { case "endswith": op = "LIKE"; break; + case "!=": + op = "!="; + break; case "=": default: break; @@ -342,9 +359,17 @@ function get(reqtype, req, res, next) { // Final group if (currentGroup.length > 0) { filterSQL.push( - `${currentGroupOp == "NOT" ? "NOT " : ""}(${currentGroup.join( + `${ + currentGroupOp == "NOT_AND" || currentGroupOp == "NOT_OR" + ? "NOT " + : "" + }(${currentGroup.join( ` ${ - currentGroupOp === "NOT" ? "AND" : currentGroupOp || "AND" + currentGroupOp === "NOT_AND" + ? "AND" + : currentGroupOp === "NOT_OR" + ? "OR" + : currentGroupOp || "AND" } ` )})` ); diff --git a/configure/src/metaconfigs/layer-vector-config.json b/configure/src/metaconfigs/layer-vector-config.json index b6555e478..96937c6ad 100644 --- a/configure/src/metaconfigs/layer-vector-config.json +++ b/configure/src/metaconfigs/layer-vector-config.json @@ -555,6 +555,82 @@ } ] }, + { + "name": "Filter", + "rows": [ + { + "name": "Filters", + "components": [ + { + "field": "variables.initialFilters", + "name": "Initial Filters", + "description": "Configures the layer's filters for displaying an initial custom subset of the data.", + "type": "objectarray", + "width": 12, + "object": [ + { + "field": "type", + "name": "Type of Property", + "description": "If not a Group, whether the property's value should be treated as a string or as a number.", + "type": "dropdown", + "width": 2, + "options": ["string", "number"] + }, + { + "field": "key", + "name": "Property", + "description": "If not a Group, a field name from the properties object of each feature of this layer to be to construct a filter. Supports dot.notation for nested properties.", + "type": "text", + "width": 4 + }, + { + "field": "op", + "name": "Operator", + "description": "If not a Group, the operator to use between the 'Property' and 'Value'.", + "type": "dropdown", + "width": 2, + "options": [ + "=", + "!=", + ",", + "<", + ">", + "<=", + ">=", + "contains", + "beginswith", + "endswith" + ] + }, + { + "field": "value", + "name": "Value", + "description": "If not a Group, a value for the equation to operate on.", + "type": "text", + "width": 4 + }, + { + "field": "isGroup", + "name": "Is A Group", + "description": "A group contains all property-value rows beneath this row and up until the next Group row or up until the end. Groups themselves are always ANDed together but enables member rows within them to abide by a different operator. If this entry 'Is A Group', then the type, property, operator and values fields are ignored.", + "type": "switch", + "width": 6, + "defaultChecked": false + }, + { + "field": "groupOp", + "name": "Group Operator", + "description": "If 'Is A Group', the operator to use for members within the group. 'AND' ands all the group members together. 'OR' ors all the group members together. 'NOT_AND' ands all the group members together and then negates the evaluation. 'NOT_OR' ors all the group members together and then negates the evaluation.", + "type": "dropdown", + "width": 6, + "options": ["AND", "OR", "NOT_AND", "NOT_OR"] + } + ] + } + ] + } + ] + }, { "name": "Interface", "rows": [ diff --git a/src/essence/Ancillary/LocalFilterer.js b/src/essence/Ancillary/LocalFilterer.js index d706a7a3c..9b1653d0c 100644 --- a/src/essence/Ancillary/LocalFilterer.js +++ b/src/essence/Ancillary/LocalFilterer.js @@ -318,8 +318,10 @@ const LocalFilterer = { let result if (group.op === 'OR') { result = group.matches.some(Boolean) - } else if (group.op === 'NOT') { + } else if (group.op === 'NOT_AND') { result = !group.matches.every(Boolean) + } else if (group.op === 'NOT_OR') { + result = !group.matches.some(Boolean) } else { // default to AND result = group.matches.every(Boolean) diff --git a/src/essence/Basics/Layers_/Filtering/Filtering.css b/src/essence/Basics/Layers_/Filtering/Filtering.css index 6d3dcc9f0..a3768b135 100644 --- a/src/essence/Basics/Layers_/Filtering/Filtering.css +++ b/src/essence/Basics/Layers_/Filtering/Filtering.css @@ -168,7 +168,7 @@ font-size: 13px; } .layersTool_filtering_group_operator { - width: 190px; + width: 231px; text-align: center; } @@ -257,7 +257,10 @@ .layersTool_filtering_group_operator_select.op_or { background: var(--color-c2); } -.layersTool_filtering_group_operator_select.op_not { +.layersTool_filtering_group_operator_select.op_not_and { + background: var(--color-orange2); +} +.layersTool_filtering_group_operator_select.op_not_or { background: var(--color-p4); } .layersTool_filtering_group_operator_select .dropy__title span { diff --git a/src/essence/Basics/Layers_/Filtering/Filtering.js b/src/essence/Basics/Layers_/Filtering/Filtering.js index aab9b59bb..4e2ed25d5 100644 --- a/src/essence/Basics/Layers_/Filtering/Filtering.js +++ b/src/essence/Basics/Layers_/Filtering/Filtering.js @@ -23,6 +23,46 @@ const Filtering = { filters: {}, current: {}, mapSpatialLayer: null, + initialize: function () { + Object.keys(L_.layers.data).forEach((layerName) => { + const layerObj = L_.layers.data[layerName] + + if (layerObj == null || layerObj.type != 'vector') return + + let shouldInitiallySubmit = false + + let initialFilterValues = [] + if ( + Filtering.filters[layerName] == null && + layerObj?.variables?.initialFilters && + layerObj.variables.initialFilters.length > 0 + ) { + initialFilterValues = layerObj.variables.initialFilters + initialFilterValues.forEach((f, idx) => { + f.id = idx + if (f.isGroup === true) { + if (f.groupOp != null) f.op = f.groupOp + if (f.key != null) delete f.key + if (f.value != null) delete f.value + if (f.type != null) delete f.type + } else { + f.op = f.op || '=' + } + }) + + Filtering.filters[layerName] = Filtering.filters[layerName] || { + spatial: { + center: null, + radius: 0, + }, + values: initialFilterValues || [], + geojson: null, + } + + Filtering.submit(layerName) + } + }) + }, make: async function (container, layerName) { const layerObj = L_.layers.data[layerName] @@ -34,7 +74,6 @@ const Filtering = { radius: 0, }, values: [], - groups: [], geojson: null, } Filtering.current = { @@ -387,43 +426,7 @@ const Filtering = { // Submit $(`#layersTool_filtering_submit`).on('click', async () => { - // Update the desired order of values - const valuesOrder = [] - $('#layerTool_filtering_filters_list > li').each(function () { - const idx = $(this).attr('idx') - if (idx !== undefined) { - valuesOrder.push(parseInt(idx)) - } - }) - Filtering.filters[layerName].valuesOrder = valuesOrder - - Filtering.setSubmitButtonState(true) - $(`#layersTool_filtering_submit_loading`).addClass('active') - if (Filtering.current.type === 'vector') { - if (Filtering.current.needsToQueryGeodataset) { - GeodatasetFilterer.filter( - layerName, - Filtering.filters[layerName] - ) - } else { - LocalFilterer.filter( - layerName, - Filtering.filters[layerName] - ) - } - } else if (Filtering.current.type === 'query') { - await ESFilterer.filter( - layerName, - Filtering.filters[layerName], - Filtering.getConfig() - ) - } - - $(`#layersTool_filtering_submit_loading`).removeClass('active') - Filtering.setSubmitButtonState(false) - - if (Filtering.mapSpatialLayer) - Filtering.mapSpatialLayer.bringToFront() + Filtering.submit(layerName, true) }) // Clear @@ -522,14 +525,15 @@ const Filtering = { layerName )}_${id}` - const ops = ['AND', 'OR', 'NOT'] + const ops = ['AND', 'OR', 'NOT_AND', 'NOT_OR'] const opId = Math.max(ops.indexOf(options.op), 0) $(elmId).html( Dropy.construct( [ - `