From 11d1b5cd9189ff0983517b5b0a43843f19ba1152 Mon Sep 17 00:00:00 2001 From: Galen Date: Tue, 23 Apr 2024 19:19:02 -0700 Subject: [PATCH 01/68] includes geometries on feature props in getPopupData, re #10816 --- arches/app/media/js/viewmodels/map.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arches/app/media/js/viewmodels/map.js b/arches/app/media/js/viewmodels/map.js index a85415fe274..29382858672 100644 --- a/arches/app/media/js/viewmodels/map.js +++ b/arches/app/media/js/viewmodels/map.js @@ -344,7 +344,7 @@ define([ var data = feature.properties; var id = data.resourceinstanceid; data.showEditButton = self.canEdit; - const descriptionProperties = ['displayname', 'graph_name', 'map_popup']; + const descriptionProperties = ['displayname', 'graph_name', 'map_popup', 'geometries']; if (id) { if (!self.resourceLookup[id]){ data = _.defaults(data, { @@ -352,6 +352,7 @@ define([ 'displayname': '', 'graph_name': '', 'map_popup': '', + 'geometries': [], 'feature': feature, }); if (data.permissions) { From bc4bf3aca193aace97b960711f54227cef67d08a Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 00:02:45 -0700 Subject: [PATCH 02/68] adds translation for map popup map-filter search, re #10816 --- arches/app/templates/javascript.htm | 1 + 1 file changed, 1 insertion(+) diff --git a/arches/app/templates/javascript.htm b/arches/app/templates/javascript.htm index fedbb43da7d..071e9d2eecd 100644 --- a/arches/app/templates/javascript.htm +++ b/arches/app/templates/javascript.htm @@ -654,6 +654,7 @@ map-add-line='{% trans "Add line" as mapAddLine %} "{{ mapAddLine|escapejs }}"' map-add-polygon='{% trans "Add polygon" as mapAddPolygon %} "{{ mapAddPolygon|escapejs }}"' map-select-drawing='{% trans "Select drawing" as mapSelectDrawing %} "{{ mapSelectDrawing|escapejs }}"' + search-near-here='{% trans "Search Near Here" as searchNearHere %} "{{ searchNearHere|escapejs }}"' related-instance-map-sources='{% trans "Related instance map sources" as relatedInstanceMapSources %} "{{ relatedInstanceMapSources|escapejs }}"' related-instance-map-source-layers='{% trans "Related instance map source layers (optional)" as relatedInstanceMapSourceLayers %} "{{ relatedInstanceMapSourceLayers|escapejs }}"' intersection-layer-configuration='{% trans "Intersection layer configuration" as intersectionLayerConfiguration %} "{{ intersectionLayerConfiguration|escapejs }}"' From a99651f98978b90181bc607ca1b824ebb6256123 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 00:10:35 -0700 Subject: [PATCH 03/68] creates fn in map-filter to take in a feature, interrogate type, set searchGeoms, re #10816 --- .../js/views/components/search/map-filter.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 3295ab7131a..98a2294c301 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -274,6 +274,33 @@ define([ } }, this); + this.filterByFeatureGeom = function(geoms) { + let feature; + if (geoms.length) { + for (let i = 0; i < geoms.length; i++) { + if (geoms[i].geom.features[0].geometry.type == 'Polygon') { + feature = geoms[i].geom.features[0]; + } else if (geoms[i].geom.features[0].geometry.type == 'Point') { + feature = geoms[i].geom.features[0]; + self.buffer(100); + } + } + } + if (!feature) { return; } + let currentSearchGeoms = self.searchGeometries(); + this.draw.set({ + "type": "FeatureCollection", + "features": [feature] + }); + if (currentSearchGeoms != null) { + currentSearchGeoms.push(feature); + } else { + currentSearchGeoms = [feature]; + } + self.searchGeometries(currentSearchGeoms); + self.updateFilter(); + }; + var updateSearchResultPointLayer = function() { var pointSource = self.map().getSource('search-results-points'); var agg = ko.unwrap(self.searchAggregations); From 1eebc611e6a643de2bc80571adcbe62222da21af Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 00:12:14 -0700 Subject: [PATCH 04/68] adds link in popup footer to search near this feature, re #10816 --- arches/app/templates/views/components/map-popup.htm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index 3ff25881bd7..7655fee6f48 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -58,6 +58,12 @@ + + + +
From 074c511574f2f45221030c04aa3dc23a27743a92 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 12:40:55 -0700 Subject: [PATCH 05/68] passes in resourceid as arg to filterByFeatureGeom, re #10816 --- arches/app/templates/views/components/map-popup.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index 7655fee6f48..d15a5e4dcdd 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -59,7 +59,7 @@ From 1d01240d0a72c74d2b089912f1065ecbfd6b11cf Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 12:41:24 -0700 Subject: [PATCH 06/68] takes in resourceid as arg in filterByFeatureGeom, re #10816 --- arches/app/media/js/views/components/search/map-filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 98a2294c301..14412fe7123 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -274,7 +274,7 @@ define([ } }, this); - this.filterByFeatureGeom = function(geoms) { + this.filterByFeatureGeom = function(resourceid, geoms) { let feature; if (geoms.length) { for (let i = 0; i < geoms.length; i++) { From 31ef33369370d3739744fa824320c8e96e4e61a0 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 12:42:09 -0700 Subject: [PATCH 07/68] rm for loop in geom.features, assumes 1 feature, re #10816 --- .../media/js/views/components/search/map-filter.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 14412fe7123..f594d25accc 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -277,13 +277,11 @@ define([ this.filterByFeatureGeom = function(resourceid, geoms) { let feature; if (geoms.length) { - for (let i = 0; i < geoms.length; i++) { - if (geoms[i].geom.features[0].geometry.type == 'Polygon') { - feature = geoms[i].geom.features[0]; - } else if (geoms[i].geom.features[0].geometry.type == 'Point') { - feature = geoms[i].geom.features[0]; - self.buffer(100); - } + if (geoms[0].geom.features[0].geometry.type == 'Polygon') { + feature = geoms[0].geom.features[0]; + } else if (geoms[0].geom.features[0].geometry.type == 'Point') { + feature = geoms[0].geom.features[0]; + self.buffer(100); } } if (!feature) { return; } From cf05766e18ed5ff04cb6031ded4c9c445f8e70d6 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 12:43:43 -0700 Subject: [PATCH 08/68] simplify buffer assignment in filterByFeatureGeom, re #10816 --- arches/app/media/js/views/components/search/map-filter.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index f594d25accc..bf1d0598a3b 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -277,12 +277,8 @@ define([ this.filterByFeatureGeom = function(resourceid, geoms) { let feature; if (geoms.length) { - if (geoms[0].geom.features[0].geometry.type == 'Polygon') { - feature = geoms[0].geom.features[0]; - } else if (geoms[0].geom.features[0].geometry.type == 'Point') { - feature = geoms[0].geom.features[0]; - self.buffer(100); - } + feature = geoms[0].geom.features[0]; + if (feature.geometry.type == 'Point') { self.buffer(100); } } if (!feature) { return; } let currentSearchGeoms = self.searchGeometries(); From 54281bea63fdcbf0c8ff396a410a8363ca4ad7b5 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 12:44:18 -0700 Subject: [PATCH 09/68] makes filter feature geom on map static, un-editable, re #10816 --- arches/app/media/js/views/components/search/map-filter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index bf1d0598a3b..cecae843b13 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -293,6 +293,7 @@ define([ } self.searchGeometries(currentSearchGeoms); self.updateFilter(); + self.draw.changeMode('static'); }; var updateSearchResultPointLayer = function() { From 0a2f425f4d469f43e21c5839e1ade3b7d04d1487 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 16:41:21 -0700 Subject: [PATCH 10/68] alternates map-filter payload with featureid and resourceid to avoid sending large poly coord sets in GET, re #10816 --- .../js/views/components/search/map-filter.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index cecae843b13..7b73a43e14e 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -113,6 +113,7 @@ define([ }; this.searchGeometries = ko.observableArray(null); + this.searchGeometryFeature = ko.observable(null); this.searchAggregations = ko.observable(); this.selectedTool = ko.observable(); this.geoJSONString = ko.observable(undefined); @@ -281,6 +282,15 @@ define([ if (feature.geometry.type == 'Point') { self.buffer(100); } } if (!feature) { return; } + self.searchGeometryFeature({ + "featureid":feature.id, + "resourceid":resourceid, + "buffer": { + "width": this.buffer(), + "unit": this.bufferUnit() + }, + "inverted": false + }); let currentSearchGeoms = self.searchGeometries(); this.draw.set({ "type": "FeatureCollection", @@ -540,7 +550,11 @@ define([ this.getFilter('term-filter').addTag('Map Filter Enabled', this.name, this.filter.inverted); } this.filter.feature_collection().features[0].properties['inverted'] = this.filter.inverted(); - queryObj[componentName] = ko.toJSON(this.filter.feature_collection()); + if (!!this.searchGeometryFeature()) { + queryObj[componentName] = ko.toJSON(this.searchGeometryFeature()); + } else { + queryObj[componentName] = ko.toJSON(this.filter.feature_collection()); + } } else { delete queryObj[componentName]; } @@ -595,6 +609,7 @@ define([ }, clear: function(reset_features) { + this.searchGeometryFeature(null); this.filter.feature_collection({ "type": "FeatureCollection", "features": [] @@ -606,6 +621,7 @@ define([ this.getFilter('term-filter').removeTag('Map Filter Enabled'); this.draw.deleteAll(); this.searchGeometries([]); + this.setupDraw(); }, zoomToAllFeaturesHandler: function(){ From 5ab58d62629b3de2c88989173438003eaf8f7064 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 16:42:14 -0700 Subject: [PATCH 11/68] moves geoshape_query build logic into own method in map_filter, re #10816 --- arches/app/search/components/map_filter.py | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 97b9234ce49..7fb2fddbffc 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -98,3 +98,26 @@ def _buffer(geojson, width=0, unit="ft"): res = cursor.fetchone() geom = GEOSGeometry(res[0], srid=4326) return geom + +def create_geoshape_query(feature_geom, feature_properties={}): + + buffer = {"width": 0, "unit": "ft"} + if "buffer" in feature_properties: + buffer = feature_properties["buffer"] + # feature_geom = spatial_filter["features"][0]["geometry"] + search_buffer = _buffer(feature_geom, int(buffer["width"]), buffer["unit"]) + feature_geom = JSONDeserializer().deserialize(search_buffer.geojson) + geoshape = GeoShape( + field="geometries.geom.features.geometry", type=feature_geom["type"], coordinates=feature_geom["coordinates"] + ) + invert_spatial_search = False + if "inverted" in feature_properties: + invert_spatial_search = feature_properties["inverted"] + + spatial_query = Bool() + if invert_spatial_search is True: + spatial_query.must_not(geoshape) + else: + spatial_query.filter(geoshape) + + return spatial_query From 1b1a573ceddac9e2e233a691049b98c188293dce Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 16:43:40 -0700 Subject: [PATCH 12/68] imports other dsl builder methods into map_filter, re #10816 --- arches/app/search/components/map_filter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 7fb2fddbffc..205b6b58ad5 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -4,8 +4,10 @@ from django.utils.translation import gettext as _ from arches.app.models.system_settings import settings from arches.app.utils.betterJSONSerializer import JSONSerializer, JSONDeserializer -from arches.app.search.elasticsearch_dsl_builder import Bool, Nested, Terms, GeoShape +from arches.app.search.elasticsearch_dsl_builder import Bool, Match, Query, Nested, Term, Terms, GeoShape from arches.app.search.components.base import BaseSearchFilter +from arches.app.search.search_engine_factory import SearchEngineFactory +from arches.app.search.mappings import RESOURCES_INDEX logger = logging.getLogger(__name__) From 2922ce5742fdb516c411c5fdfdf74b1b591532cc Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 16:44:29 -0700 Subject: [PATCH 13/68] creates 2nd control flow for payload of featureid, resourceid into map_filter to lookup geom, re #10816 --- arches/app/search/components/map_filter.py | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 205b6b58ad5..d46d0550031 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -65,6 +65,41 @@ def append_dsl(self, search_results_object, permitted_nodegroups, include_provis spatial_query.filter(Terms(field="geometries.provisional", terms=["true"])) search_query.filter(Nested(path="geometries", query=spatial_query)) + + elif "featureid" in spatial_filter and "resourceid" in spatial_filter: + se = SearchEngineFactory().create() + main_query = Query(se) + nested_query = Nested(path="geometries") + match_feature = Match(field="geometries.geom.features.id", query=spatial_filter["featureid"]) + + # Create a Bool query for conditions inside the nested path + bool_nested_query = Bool() + bool_nested_query.must(match_feature.dsl) + nested_query.add_query(bool_nested_query.dsl) + + bool_query = Bool() + match_resource = Term(field="resourceinstanceid", term=spatial_filter["resourceid"]) + bool_query.must(match_resource.dsl) # Match resource instance ID at the document level + bool_query.must(nested_query.dsl) # Add the nested query + + # Set the entire bool query to the main query object + main_query.add_query(bool_query.dsl) + + response = main_query.search(index=RESOURCES_INDEX) + geometries = [] + for hit in response['hits']['hits']: + geometries.extend(hit['_source']['geometries'][0]['geom']['features']) + + feature_geom = geometries[0]["geometry"] + spatial_query = create_geoshape_query(feature_geom, spatial_filter) + spatial_query.filter(Terms(field="geometries.nodegroup_id", terms=permitted_nodegroups)) + + if include_provisional is False: + spatial_query.filter(Terms(field="geometries.provisional", terms=["false"])) + elif include_provisional == "only provisional": + spatial_query.filter(Terms(field="geometries.provisional", terms=["true"])) + + search_query.filter(Nested(path="geometries", query=spatial_query)) search_results_object["query"].add_query(search_query) From ea387b576f8ea11aa869f2b663d1406c1819412e Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 16:45:03 -0700 Subject: [PATCH 14/68] refactors standard spatial_query logic to use helper method create_geoshape_query, re #10816 --- arches/app/search/components/map_filter.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index d46d0550031..91d7712d6d6 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -36,24 +36,8 @@ def append_dsl(self, search_results_object, permitted_nodegroups, include_provis feature_properties = {} if "properties" in spatial_filter["features"][0]: feature_properties = spatial_filter["features"][0]["properties"] - buffer = {"width": 0, "unit": "ft"} - if "buffer" in feature_properties: - buffer = feature_properties["buffer"] - search_buffer = _buffer(feature_geom, buffer["width"], buffer["unit"]) - feature_geom = JSONDeserializer().deserialize(search_buffer.geojson) - geoshape = GeoShape( - field="geometries.geom.features.geometry", type=feature_geom["type"], coordinates=feature_geom["coordinates"] - ) - - invert_spatial_search = False - if "inverted" in feature_properties: - invert_spatial_search = feature_properties["inverted"] - - spatial_query = Bool() - if invert_spatial_search is True: - spatial_query.must_not(geoshape) - else: - spatial_query.filter(geoshape) + + spatial_query = create_geoshape_query(feature_geom, feature_properties) # get the nodegroup_ids that the user has permission to search spatial_query.filter(Terms(field="geometries.nodegroup_id", terms=permitted_nodegroups)) From f0759987f8b14a62ca9ac22dfe5f5946bc297f50 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 24 Apr 2024 19:05:23 -0700 Subject: [PATCH 15/68] creates alternate flow if no resourceid supplied to filterByGeom, simplifies geom, re #10816 --- .../js/views/components/search/map-filter.js | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 7b73a43e14e..b131d36cce7 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -282,15 +282,21 @@ define([ if (feature.geometry.type == 'Point') { self.buffer(100); } } if (!feature) { return; } - self.searchGeometryFeature({ - "featureid":feature.id, - "resourceid":resourceid, - "buffer": { - "width": this.buffer(), - "unit": this.bufferUnit() - }, - "inverted": false - }); + if (resourceid) { + self.searchGeometryFeature({ + "featureid":feature.id, + "resourceid":resourceid, + "buffer": { + "width": this.buffer(), + "unit": this.bufferUnit() + }, + "inverted": this.filter.inverted() + }); + } else { + const tolerance = 0.1; // Degree of Simplification: Lower numbers are less simplified, preserving more detail + const highQuality = true; // Set to true for a slower but higher quality simplification + turf.simplify(feature.geometry, {tolerance: tolerance, highQuality: highQuality, mutate: true}); + } let currentSearchGeoms = self.searchGeometries(); this.draw.set({ "type": "FeatureCollection", From 1c4d836df5b08dc8f5942da04dd9ba62ba656c37 Mon Sep 17 00:00:00 2001 From: Galen Date: Thu, 25 Apr 2024 12:08:14 -0700 Subject: [PATCH 16/68] moves duplicative code into helper method, alters search_query in place instead of ret, re #10816 --- arches/app/search/components/map_filter.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 91d7712d6d6..6eec8267f60 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -141,4 +141,13 @@ def create_geoshape_query(feature_geom, feature_properties={}): else: spatial_query.filter(geoshape) - return spatial_query + # get the nodegroup_ids that the user has permission to search + spatial_query.filter(Terms(field="geometries.nodegroup_id", terms=permitted_nodegroups)) + + if include_provisional is False: + spatial_query.filter(Terms(field="geometries.provisional", terms=["false"])) + + elif include_provisional == "only provisional": + spatial_query.filter(Terms(field="geometries.provisional", terms=["true"])) + + search_query.filter(Nested(path="geometries", query=spatial_query)) From f5181b12c5868362b29de80c130f802bf092f21c Mon Sep 17 00:00:00 2001 From: Galen Date: Thu, 25 Apr 2024 12:08:54 -0700 Subject: [PATCH 17/68] refactors method signature to reflect inplace alteration, re #10816 --- arches/app/search/components/map_filter.py | 25 +++------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 6eec8267f60..2832225d6fe 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -37,19 +37,8 @@ def append_dsl(self, search_results_object, permitted_nodegroups, include_provis if "properties" in spatial_filter["features"][0]: feature_properties = spatial_filter["features"][0]["properties"] - spatial_query = create_geoshape_query(feature_geom, feature_properties) + add_geoshape_query_to_search_query(feature_geom, feature_properties, permitted_nodegroups, include_provisional, search_query) - # get the nodegroup_ids that the user has permission to search - spatial_query.filter(Terms(field="geometries.nodegroup_id", terms=permitted_nodegroups)) - - if include_provisional is False: - spatial_query.filter(Terms(field="geometries.provisional", terms=["false"])) - - elif include_provisional == "only provisional": - spatial_query.filter(Terms(field="geometries.provisional", terms=["true"])) - - search_query.filter(Nested(path="geometries", query=spatial_query)) - elif "featureid" in spatial_filter and "resourceid" in spatial_filter: se = SearchEngineFactory().create() main_query = Query(se) @@ -75,15 +64,7 @@ def append_dsl(self, search_results_object, permitted_nodegroups, include_provis geometries.extend(hit['_source']['geometries'][0]['geom']['features']) feature_geom = geometries[0]["geometry"] - spatial_query = create_geoshape_query(feature_geom, spatial_filter) - spatial_query.filter(Terms(field="geometries.nodegroup_id", terms=permitted_nodegroups)) - - if include_provisional is False: - spatial_query.filter(Terms(field="geometries.provisional", terms=["false"])) - elif include_provisional == "only provisional": - spatial_query.filter(Terms(field="geometries.provisional", terms=["true"])) - - search_query.filter(Nested(path="geometries", query=spatial_query)) + add_geoshape_query_to_search_query(feature_geom, spatial_filter, permitted_nodegroups, include_provisional, search_query) search_results_object["query"].add_query(search_query) @@ -120,7 +101,7 @@ def _buffer(geojson, width=0, unit="ft"): geom = GEOSGeometry(res[0], srid=4326) return geom -def create_geoshape_query(feature_geom, feature_properties={}): +def add_geoshape_query_to_search_query(feature_geom, feature_properties, permitted_nodegroups, include_provisional, search_query): buffer = {"width": 0, "unit": "ft"} if "buffer" in feature_properties: From 2a9266d7143d5cc6bd3794f6cca072fa11d62a8f Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 26 Apr 2024 17:35:53 -0700 Subject: [PATCH 18/68] writes test for spatial filter by featureid, resourceid, re #10816 --- tests/views/search_tests.py | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/views/search_tests.py b/tests/views/search_tests.py index 9def33b4fd1..ea21ab40f29 100644 --- a/tests/views/search_tests.py +++ b/tests/views/search_tests.py @@ -146,6 +146,48 @@ def setUpClass(cls): cls.name_resource.tiles.append(tile) cls.name_resource.save() + # spatial filter test data + cls.spatial_filter_geom_resourceid = 'cbb1e9df-5110-4f22-933c-9ccbeb57431b' + cls.spatial_filter_geom_resource = Resource(graph_id=cls.search_model_graphid, resourceinstanceid=cls.spatial_filter_geom_resourceid) + cls.polygon_feature_id = "2190cb9e-7c57-485c-bf1a-7b6f0389f8b1" + + geom_poly = { + "type": "FeatureCollection", + "features": [ + { + "geometry": {"type": "Polygon", "coordinates": [ + [-118.22687435396205, 34.04498354472949], + [-118.22673462509519,34.045024944460636], + [-118.22661984555208, 34.044757071199754], + [-118.22675979254618, 34.044715607647184], + [-118.22687435396205, 34.04498354472949] + ] + }, + "type": "Feature", + "id": cls.polygon_feature_id, + "properties": {} + } + ] + } + tile = Tile(data={cls.search_model_geom_nodeid: geom_poly}, nodegroup_id=cls.search_model_geom_nodeid) + cls.spatial_filter_geom_resource.tiles.append(tile) + cls.point_feature_id = "d41e81ac-4a53-4049-b266-c459b7641bc1" + geom_point = { + "type": "FeatureCollection", + "features": [ + { + "geometry": {"type": "Point", "coordinates": [-118.22687435396206, 34.04498354472948] + }, + "type": "Feature", + "id": cls.point_feature_id, + "properties": {} + } + ] + } + tile = Tile(data={cls.search_model_geom_nodeid: geom_point}, nodegroup_id=cls.search_model_geom_nodeid) + cls.spatial_filter_geom_resource.tiles.append(tile) + cls.spatial_filter_geom_resource.save() + # add delay to allow for indexes to be updated time.sleep(1) @@ -462,6 +504,35 @@ def test_spatial_search_2(self): self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) # self.assertCountEqual(extract_pks(response_json), [str(self.name_resource.pk)]) + def test_spatial_search_by_featureid_and_resourceid(self): + """ + Test spatial search functionality using featureid and resourceid to retrieve geometries. + """ + # Simulate spatial filter with featureid and resourceid, not feature collection + spatial_filter_poly_feature = { + "featureid": self.polygon_feature_id, + "resourceid": self.spatial_filter_geom_resourceid, + "buffer": { + "width": 10, + "unit": 'ft' + }, + "inverted": False + } + response_json = get_response_json(self.client, spatial_filter=spatial_filter_poly_feature) + self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) + + spatial_filter_point_feature = { + "featureid": self.point_feature_id, + "resourceid": self.spatial_filter_geom_resourceid, + "buffer": { + "width": 10, + "unit": 'ft' + }, + "inverted": False + } + response_json = get_response_json(self.client, spatial_filter=spatial_filter_point_feature) + self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) + # # -- ADD TESTS THAT INCLUDE PERMISSIONS REQUIREMENTS -- # # From 40959570cb3f87a330ecc461405809f64ab1b3b2 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 26 Apr 2024 17:43:18 -0700 Subject: [PATCH 19/68] ensure pre-assigned featureids not overwritten in datatype transform from json, re #10816 --- arches/app/datatypes/datatypes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arches/app/datatypes/datatypes.py b/arches/app/datatypes/datatypes.py index 66bfc990dda..3369dc183ff 100644 --- a/arches/app/datatypes/datatypes.py +++ b/arches/app/datatypes/datatypes.py @@ -998,7 +998,9 @@ def transform_value_for_tile(self, value, **kwargs): geojson = json.loads(value) if geojson["type"] == "FeatureCollection": for feature in geojson["features"]: - feature["id"] = str(uuid.uuid4()) + feature_id = feature.get("id", None) + if not feature_id: + feature["id"] = str(uuid.uuid4()) arches_geojson = geojson else: raise TypeError From 3c9878fa6a55c76bf97ec258f9bd318b79e1abc1 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 26 Apr 2024 17:44:04 -0700 Subject: [PATCH 20/68] checks for geometries.length in map_popup before passing args, re #10816 --- arches/app/templates/views/components/map-popup.htm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index d15a5e4dcdd..4ff85273caf 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -58,6 +58,7 @@ + @@ -65,6 +66,7 @@
+
From 805693ea73f1e6a9975543b41a9b69492785dd0f Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 26 Apr 2024 17:54:33 -0700 Subject: [PATCH 21/68] minor cleanup of logic in filterByFeatureGeom, re #10816 --- .../media/js/views/components/search/map-filter.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index b131d36cce7..0ca56780ca2 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -276,16 +276,12 @@ define([ }, this); this.filterByFeatureGeom = function(resourceid, geoms) { - let feature; - if (geoms.length) { - feature = geoms[0].geom.features[0]; - if (feature.geometry.type == 'Point') { self.buffer(100); } - } - if (!feature) { return; } - if (resourceid) { + let feature = geoms[0].geom.features[0]; + if (feature.geometry.type == 'Point') { self.buffer(100); } + if (feature.id) { self.searchGeometryFeature({ - "featureid":feature.id, - "resourceid":resourceid, + "featureid": feature.id, + "resourceid": resourceid, "buffer": { "width": this.buffer(), "unit": this.bufferUnit() From dfb65e34f5dbf4c6f154ed6821964035bb564b63 Mon Sep 17 00:00:00 2001 From: Galen Date: Sun, 28 Apr 2024 17:53:54 +0900 Subject: [PATCH 22/68] fixes polygon format in search_test, re #10816 --- tests/views/search_tests.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/views/search_tests.py b/tests/views/search_tests.py index ea21ab40f29..ff59abae615 100644 --- a/tests/views/search_tests.py +++ b/tests/views/search_tests.py @@ -155,13 +155,13 @@ def setUpClass(cls): "type": "FeatureCollection", "features": [ { - "geometry": {"type": "Polygon", "coordinates": [ + "geometry": {"type": "Polygon", "coordinates": [[ [-118.22687435396205, 34.04498354472949], - [-118.22673462509519,34.045024944460636], + [-118.22673462509519, 34.045024944460636], [-118.22661984555208, 34.044757071199754], [-118.22675979254618, 34.044715607647184], [-118.22687435396205, 34.04498354472949] - ] + ]] }, "type": "Feature", "id": cls.polygon_feature_id, @@ -176,8 +176,7 @@ def setUpClass(cls): "type": "FeatureCollection", "features": [ { - "geometry": {"type": "Point", "coordinates": [-118.22687435396206, 34.04498354472948] - }, + "geometry": {"type": "Point", "coordinates": [-118.22687435396206, 34.04498354472948]}, "type": "Feature", "id": cls.point_feature_id, "properties": {} From cb3140d4b13f9db8588fa3565c51be2e4b762ec8 Mon Sep 17 00:00:00 2001 From: Galen Date: Mon, 29 Apr 2024 20:15:13 +0900 Subject: [PATCH 23/68] minor tweak to search tests, re #10816 --- tests/views/search_tests.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/views/search_tests.py b/tests/views/search_tests.py index ff59abae615..c2b2c07acd3 100644 --- a/tests/views/search_tests.py +++ b/tests/views/search_tests.py @@ -149,6 +149,7 @@ def setUpClass(cls): # spatial filter test data cls.spatial_filter_geom_resourceid = 'cbb1e9df-5110-4f22-933c-9ccbeb57431b' cls.spatial_filter_geom_resource = Resource(graph_id=cls.search_model_graphid, resourceinstanceid=cls.spatial_filter_geom_resourceid) + cls.spatial_filter_geom_resource.save() cls.polygon_feature_id = "2190cb9e-7c57-485c-bf1a-7b6f0389f8b1" geom_poly = { @@ -169,8 +170,9 @@ def setUpClass(cls): } ] } - tile = Tile(data={cls.search_model_geom_nodeid: geom_poly}, nodegroup_id=cls.search_model_geom_nodeid) - cls.spatial_filter_geom_resource.tiles.append(tile) + tile = Tile.get_blank_tile(cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid) + tile.data[cls.search_model_geom_nodeid] = geom_poly + tile.save() cls.point_feature_id = "d41e81ac-4a53-4049-b266-c459b7641bc1" geom_point = { "type": "FeatureCollection", @@ -183,9 +185,9 @@ def setUpClass(cls): } ] } - tile = Tile(data={cls.search_model_geom_nodeid: geom_point}, nodegroup_id=cls.search_model_geom_nodeid) - cls.spatial_filter_geom_resource.tiles.append(tile) - cls.spatial_filter_geom_resource.save() + tile = Tile.get_blank_tile(cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid) + tile.data[cls.search_model_geom_nodeid] = geom_point + tile.save() # add delay to allow for indexes to be updated time.sleep(1) From f9fac10906b63d1441bee9276520001a7235cb2f Mon Sep 17 00:00:00 2001 From: Galen Date: Tue, 30 Apr 2024 11:34:03 +0900 Subject: [PATCH 24/68] moves popup feature handling into map popup provider utils for easier customization, calls this method which then calls mapFilter; rm feature mutation and simp logic from mapFilter method, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 13 +++++++++++++ arches/app/media/js/viewmodels/map.js | 1 + arches/app/templates/views/components/map-popup.htm | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 8a660438831..7db6999e489 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -40,6 +40,19 @@ define(['arches', return features; }, + /** + * This method enables custom logic for how the feature in the popup should be handled and/or mutated en route to the mapFilter. + * @param popupFeatureObject - the javascript object of the feature and its associated contexts (e.g. mapCard). + * This method should send at a minimum: a geojson feature object + * optionally: a resourceinstanceid + */ + sendFeaturetoMapFilter: function(popupFeatureObject) + { + console.log(popupFeatureObject); + const feature = popupFeatureObject.geometries()[0].geom.features[0]; + popupFeatureObject.mapCard.filterByFeatureGeom(feature, popupFeatureObject.resourceinstanceid); + }, + }; return provider; }); diff --git a/arches/app/media/js/viewmodels/map.js b/arches/app/media/js/viewmodels/map.js index 29382858672..0bfd506c9a0 100644 --- a/arches/app/media/js/viewmodels/map.js +++ b/arches/app/media/js/viewmodels/map.js @@ -344,6 +344,7 @@ define([ var data = feature.properties; var id = data.resourceinstanceid; data.showEditButton = self.canEdit; + data.sendFeaturetoMapFilter = mapPopupProvider.sendFeaturetoMapFilter; const descriptionProperties = ['displayname', 'graph_name', 'map_popup', 'geometries']; if (id) { if (!self.resourceLookup[id]){ diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index 4ff85273caf..4bd55cbdba3 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -60,7 +60,7 @@ From 37a5d45025c7a29d948549fbf578a9ba41e53f8f Mon Sep 17 00:00:00 2001 From: Galen Date: Tue, 30 Apr 2024 11:35:01 +0900 Subject: [PATCH 25/68] refactors mapFilter filterByFeatureGeom, expecting map popup provider caller, re #10816 --- .../app/media/js/views/components/search/map-filter.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 0ca56780ca2..6d700a10f20 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -275,10 +275,9 @@ define([ } }, this); - this.filterByFeatureGeom = function(resourceid, geoms) { - let feature = geoms[0].geom.features[0]; + this.filterByFeatureGeom = function(feature, resourceid=null) { if (feature.geometry.type == 'Point') { self.buffer(100); } - if (feature.id) { + if (feature.id && resourceid) { self.searchGeometryFeature({ "featureid": feature.id, "resourceid": resourceid, @@ -288,10 +287,6 @@ define([ }, "inverted": this.filter.inverted() }); - } else { - const tolerance = 0.1; // Degree of Simplification: Lower numbers are less simplified, preserving more detail - const highQuality = true; // Set to true for a slower but higher quality simplification - turf.simplify(feature.geometry, {tolerance: tolerance, highQuality: highQuality, mutate: true}); } let currentSearchGeoms = self.searchGeometries(); this.draw.set({ From 4dc361ace407771a39df2ddc18983cc867de2c37 Mon Sep 17 00:00:00 2001 From: Galen Date: Tue, 30 Apr 2024 11:46:11 +0900 Subject: [PATCH 26/68] indent child comment ko blocks to improve readability, re #10816 --- .../templates/views/components/map-popup.htm | 128 +++++++++--------- 1 file changed, 63 insertions(+), 65 deletions(-) diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index 4bd55cbdba3..96921191152 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -9,73 +9,71 @@ - - - -
- {% block title %} -
-
-
-
-
- {% endblock title %} -
- -
- {% block body %} -
- -
From 6afe8ca7d014b16f58447d124fc89e560477f609 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 14:01:54 -0700 Subject: [PATCH 30/68] migrates visibility logic of filter by feature to map popup provider, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 4 ++++ arches/app/templates/views/components/map-popup.htm | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index b2e57fd741b..5ad41ad1a04 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -53,6 +53,10 @@ define(['arches', popupFeatureObject.mapCard.filterByFeatureGeom(feature, popupFeatureObject.resourceinstanceid); }, + showFilterByFeature: function(feature) { + return (ko.unwrap(feature.geometries) || []).length > 0; + }, + }; return provider; }); diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index 98d04b03ea6..080b5739bcf 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -58,12 +58,10 @@ - - + -
From f8e4f48de36e777487f418ce1f44861e1f94fab6 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 14:03:14 -0700 Subject: [PATCH 31/68] include showFilterByFeature on feature props in map vm, re #10816 --- arches/app/media/js/viewmodels/map.js | 1 + 1 file changed, 1 insertion(+) diff --git a/arches/app/media/js/viewmodels/map.js b/arches/app/media/js/viewmodels/map.js index 0bfd506c9a0..7294eff2cd5 100644 --- a/arches/app/media/js/viewmodels/map.js +++ b/arches/app/media/js/viewmodels/map.js @@ -345,6 +345,7 @@ define([ var id = data.resourceinstanceid; data.showEditButton = self.canEdit; data.sendFeaturetoMapFilter = mapPopupProvider.sendFeaturetoMapFilter; + data.showFilterByFeature = mapPopupProvider.showFilterByFeature; const descriptionProperties = ['displayname', 'graph_name', 'map_popup', 'geometries']; if (id) { if (!self.resourceLookup[id]){ From eab6138b66b0a3ee433825838d2c4e6bb0004aa8 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 14:03:42 -0700 Subject: [PATCH 32/68] rm unneeded draw.setup in mapfilter.clear, re #10816 --- arches/app/media/js/views/components/search/map-filter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 6d700a10f20..0b81d627a06 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -618,7 +618,6 @@ define([ this.getFilter('term-filter').removeTag('Map Filter Enabled'); this.draw.deleteAll(); this.searchGeometries([]); - this.setupDraw(); }, zoomToAllFeaturesHandler: function(){ From 9dda45444293b3c52838b84affc03b2fc27e3c86 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 14:08:24 -0700 Subject: [PATCH 33/68] camelcase correction in method name sendFeatureToMapFilter, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 2 +- arches/app/media/js/viewmodels/map.js | 2 +- arches/app/templates/views/components/map-popup.htm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 5ad41ad1a04..0d797ab1713 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -47,7 +47,7 @@ define(['arches', * @required send argument: @param feature - a geojson feature object * @optional send argument: @param resourceid */ - sendFeaturetoMapFilter: function(popupFeatureObject) + sendFeatureToMapFilter: function(popupFeatureObject) { const feature = popupFeatureObject.geometries()[0].geom.features[0]; popupFeatureObject.mapCard.filterByFeatureGeom(feature, popupFeatureObject.resourceinstanceid); diff --git a/arches/app/media/js/viewmodels/map.js b/arches/app/media/js/viewmodels/map.js index 7294eff2cd5..c3494cca32b 100644 --- a/arches/app/media/js/viewmodels/map.js +++ b/arches/app/media/js/viewmodels/map.js @@ -344,7 +344,7 @@ define([ var data = feature.properties; var id = data.resourceinstanceid; data.showEditButton = self.canEdit; - data.sendFeaturetoMapFilter = mapPopupProvider.sendFeaturetoMapFilter; + data.sendFeatureToMapFilter = mapPopupProvider.sendFeatureToMapFilter; data.showFilterByFeature = mapPopupProvider.showFilterByFeature; const descriptionProperties = ['displayname', 'graph_name', 'map_popup', 'geometries']; if (id) { diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index 080b5739bcf..afad6f98c6d 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -58,7 +58,7 @@ - + From a6de407a2eea7c4d3dee8b6cf47187c3d70e476e Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 14:10:21 -0700 Subject: [PATCH 34/68] enforce consistent param name, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 0d797ab1713..e6619c00df0 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -53,8 +53,8 @@ define(['arches', popupFeatureObject.mapCard.filterByFeatureGeom(feature, popupFeatureObject.resourceinstanceid); }, - showFilterByFeature: function(feature) { - return (ko.unwrap(feature.geometries) || []).length > 0; + showFilterByFeature: function(popupFeatureObject) { + return (ko.unwrap(popupFeatureObject.geometries) || []).length > 0; }, }; From 24a7299aad5f3f6ea4f0e8ad77daba85af7131a5 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 15:56:35 -0700 Subject: [PATCH 35/68] accomodates single feature map query on restoreState, re #10816 --- .../js/views/components/search/map-filter.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 0b81d627a06..49cf8c87dcf 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -566,7 +566,21 @@ define([ var hasSpatialFilter = false; if (componentName in query) { var mapQuery = JSON.parse(query[componentName]); - if (mapQuery.features.length > 0) { + if (mapQuery.featureid && mapQuery.resourceid) { + buffer = mapQuery.buffer; + bufferUnit = mapQuery.bufferUnit; + inverted = mapQuery.inverted; + this.searchGeometryFeature({ + "featureid": mapQuery.id, + "resourceid": mapQuery.resourceid, + "buffer": { + "width": buffer, + "unit": bufferUnit + }, + "inverted": inverted + }); + hasSpatialFilter = true; + } else if (mapQuery.features.length > 0) { hasSpatialFilter = true; var properties = mapQuery.features[0].properties; inverted = properties.inverted; From 167461766b44b864e2d8358b438efb140cca903b Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 16:06:07 -0700 Subject: [PATCH 36/68] enables feature geom editing if map popup feature not from resource-layer, re #10816 --- arches/app/media/js/views/components/search/map-filter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 49cf8c87dcf..10096e4d95a 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -300,7 +300,8 @@ define([ } self.searchGeometries(currentSearchGeoms); self.updateFilter(); - self.draw.changeMode('static'); + // if feature is not from a resource-layer, enable feature editing in map + if (!!self.searchGeometryFeature()) { self.draw.changeMode('static'); } }; var updateSearchResultPointLayer = function() { From 820a911289b9036af4ee960a564dc1730316b55f Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 17:42:55 -0700 Subject: [PATCH 37/68] grabs the correct feature based on id match in feature-based spatial filter, re #10816 --- arches/app/search/components/map_filter.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 2832225d6fe..5e3536e2907 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -61,7 +61,16 @@ def append_dsl(self, search_results_object, permitted_nodegroups, include_provis response = main_query.search(index=RESOURCES_INDEX) geometries = [] for hit in response['hits']['hits']: - geometries.extend(hit['_source']['geometries'][0]['geom']['features']) + if len(geometries) > 0: + break + for geom in hit['_source']['geometries']: + if len(geometries) > 0: + break + for feature in geom['geom']['features']: + if len(geometries) > 0: + break + if feature['id'] == spatial_filter["featureid"]: + geometries.append(feature) feature_geom = geometries[0]["geometry"] add_geoshape_query_to_search_query(feature_geom, spatial_filter, permitted_nodegroups, include_provisional, search_query) From 387752c627de05b3637aec506c85eb225e40cb3b Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 17:53:52 -0700 Subject: [PATCH 38/68] move spatial feature tests into own module, re #10816 --- tests/search/spatial_search_tests.py | 173 +++++++++++++++++++++++++++ tests/views/search_tests.py | 72 ----------- 2 files changed, 173 insertions(+), 72 deletions(-) create mode 100644 tests/search/spatial_search_tests.py diff --git a/tests/search/spatial_search_tests.py b/tests/search/spatial_search_tests.py new file mode 100644 index 00000000000..35e2e61739a --- /dev/null +++ b/tests/search/spatial_search_tests.py @@ -0,0 +1,173 @@ +""" +ARCHES - a program developed to inventory and manage immovable cultural heritage. +Copyright (C) 2013 J. Paul Getty Trust and World Monuments Fund + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +""" + +import os +import json +import time +from tests.base_test import ArchesTestCase +from django.urls import reverse +from django.contrib.auth.models import User, Group +from django.test.client import Client +from arches.app.models import models +from arches.app.models.resource import Resource +from arches.app.models.tile import Tile +from arches.app.utils.i18n import LanguageSynchronizer +from arches.app.utils.data_management.resource_graphs.importer import import_graph as ResourceGraphImporter +from arches.app.utils.betterJSONSerializer import JSONSerializer, JSONDeserializer +from guardian.shortcuts import assign_perm +from arches.app.search.search_engine_factory import SearchEngineFactory +from arches.app.search.elasticsearch_dsl_builder import Query, Term +from arches.app.search.mappings import TERMS_INDEX, CONCEPTS_INDEX, RESOURCES_INDEX + +# these tests can be run from the command line via +# python manage.py test tests.search.spatial_search_tests --settings="tests.test_settings" + +class SpatialSearchTests(ArchesTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + se = SearchEngineFactory().create() + q = Query(se=se) + for indexname in [TERMS_INDEX, CONCEPTS_INDEX, RESOURCES_INDEX]: + q.delete(index=indexname, refresh=True) + + cls.client = Client() + cls.client.login(username="admin", password="admin") + + LanguageSynchronizer.synchronize_settings_with_db() + models.ResourceInstance.objects.all().delete() + with open(os.path.join("tests/fixtures/resource_graphs/Search Test Model.json"), "r") as f: + archesfile = JSONDeserializer().deserialize(f) + ResourceGraphImporter(archesfile["graph"]) + + cls.search_model_graphid = "d291a445-fa5f-11e6-afa8-14109fd34195" + cls.search_model_cultural_period_nodeid = "7a182580-fa60-11e6-96d1-14109fd34195" + cls.search_model_creation_date_nodeid = "1c1d05f5-fa60-11e6-887f-14109fd34195" + cls.search_model_destruction_date_nodeid = "e771b8a1-65fe-11e7-9163-14109fd34195" + cls.search_model_name_nodeid = "2fe14de3-fa61-11e6-897b-14109fd34195" + cls.search_model_sensitive_info_nodeid = "57446fae-65ff-11e7-b63a-14109fd34195" + cls.search_model_geom_nodeid = "3ebc6785-fa61-11e6-8c85-14109fd34195" + + cls.user = User.objects.create_user("unpriviliged_user", "unpriviliged_user@archesproject.org", "test") + cls.user.groups.add(Group.objects.get(name="Guest")) + + cls.spatial_filter_geom_resourceid = 'cbb1e9df-5110-4f22-933c-9ccbeb57431b' + cls.spatial_filter_geom_resource = Resource(graph_id=cls.search_model_graphid, resourceinstanceid=cls.spatial_filter_geom_resourceid) + cls.spatial_filter_geom_resource.save() + cls.polygon_feature_id = "2190cb9e-7c57-485c-bf1a-7b6f0389f8b1" + + geom_poly = { + "type": "FeatureCollection", + "features": [ + { + "geometry": {"type": "Polygon", "coordinates": [[ + [-118.22687435396205, 34.04498354472949], + [-118.22673462509519, 34.045024944460636], + [-118.22661984555208, 34.044757071199754], + [-118.22675979254618, 34.044715607647184], + [-118.22687435396205, 34.04498354472949] + ]] + }, + "type": "Feature", + "id": cls.polygon_feature_id, + "properties": {} + } + ] + } + poly_tile = Tile.get_blank_tile(cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid) + poly_tile.data[cls.search_model_geom_nodeid] = geom_poly + poly_tile.save() + cls.point_feature_id = "d41e81ac-4a53-4049-b266-c459b7641bc1" + geom_point = { + "type": "FeatureCollection", + "features": [ + { + "geometry": {"type": "Point", "coordinates": [-118.22687435396205, 34.04498354472949]}, + "type": "Feature", + "id": cls.point_feature_id, + "properties": {} + } + ] + } + point_tile = Tile.get_blank_tile(cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid) + point_tile.data[cls.search_model_geom_nodeid] = geom_point + point_tile.save() + time.sleep(2) + + @classmethod + def tearDownClass(cls): + cls.user.delete() + Resource.objects.filter(graph_id=cls.search_model_graphid).delete() + models.GraphModel.objects.filter(pk=cls.search_model_graphid).delete() + super().tearDownClass() + + def test_spatial_search_by_featureid_and_resourceid_1(self): + """ + Test spatial search functionality using featureid and resourceid to retrieve geometries. + """ + # Simulate spatial filter with featureid and resourceid, not feature collection + # spatial filter test data + + spatial_filter_poly_feature = { + "featureid": self.polygon_feature_id, + "resourceid": self.spatial_filter_geom_resourceid, + "buffer": { + "width": 25, + "unit": 'm' + }, + "inverted": False + } + response_json = get_response_json(self.client, spatial_filter=spatial_filter_poly_feature) + self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) + + def test_spatial_search_by_featureid_and_resourceid_2(self): + """ + Test spatial search functionality using featureid and resourceid to retrieve geometries. + """ + # Simulate spatial filter with featureid and resourceid, not feature collection + # spatial filter test data + + spatial_filter_point_feature = { + "featureid": self.point_feature_id, + "resourceid": self.spatial_filter_geom_resourceid, + "buffer": { + "width": 75, + "unit": 'm' + }, + "inverted": False + } + response_json = get_response_json(self.client, spatial_filter=spatial_filter_point_feature) + self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) + + +def get_response_json(client, temporal_filter=None, term_filter=None, spatial_filter=None): + query = {} + if temporal_filter is not None: + query["time-filter"] = JSONSerializer().serialize(temporal_filter) + if term_filter is not None: + query["term-filter"] = JSONSerializer().serialize(term_filter) + if spatial_filter is not None: + query["map-filter"] = JSONSerializer().serialize(spatial_filter) + resource_reviewer_group = Group.objects.get(name="Resource Reviewer") + test_user = User.objects.get(username="unpriviliged_user") + test_user.groups.add(resource_reviewer_group) + client.login(username="unpriviliged_user", password="test") + response = client.get("/search/resources", query) + response_json = json.loads(response.content) + return response_json \ No newline at end of file diff --git a/tests/views/search_tests.py b/tests/views/search_tests.py index c2b2c07acd3..9def33b4fd1 100644 --- a/tests/views/search_tests.py +++ b/tests/views/search_tests.py @@ -146,49 +146,6 @@ def setUpClass(cls): cls.name_resource.tiles.append(tile) cls.name_resource.save() - # spatial filter test data - cls.spatial_filter_geom_resourceid = 'cbb1e9df-5110-4f22-933c-9ccbeb57431b' - cls.spatial_filter_geom_resource = Resource(graph_id=cls.search_model_graphid, resourceinstanceid=cls.spatial_filter_geom_resourceid) - cls.spatial_filter_geom_resource.save() - cls.polygon_feature_id = "2190cb9e-7c57-485c-bf1a-7b6f0389f8b1" - - geom_poly = { - "type": "FeatureCollection", - "features": [ - { - "geometry": {"type": "Polygon", "coordinates": [[ - [-118.22687435396205, 34.04498354472949], - [-118.22673462509519, 34.045024944460636], - [-118.22661984555208, 34.044757071199754], - [-118.22675979254618, 34.044715607647184], - [-118.22687435396205, 34.04498354472949] - ]] - }, - "type": "Feature", - "id": cls.polygon_feature_id, - "properties": {} - } - ] - } - tile = Tile.get_blank_tile(cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid) - tile.data[cls.search_model_geom_nodeid] = geom_poly - tile.save() - cls.point_feature_id = "d41e81ac-4a53-4049-b266-c459b7641bc1" - geom_point = { - "type": "FeatureCollection", - "features": [ - { - "geometry": {"type": "Point", "coordinates": [-118.22687435396206, 34.04498354472948]}, - "type": "Feature", - "id": cls.point_feature_id, - "properties": {} - } - ] - } - tile = Tile.get_blank_tile(cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid) - tile.data[cls.search_model_geom_nodeid] = geom_point - tile.save() - # add delay to allow for indexes to be updated time.sleep(1) @@ -505,35 +462,6 @@ def test_spatial_search_2(self): self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) # self.assertCountEqual(extract_pks(response_json), [str(self.name_resource.pk)]) - def test_spatial_search_by_featureid_and_resourceid(self): - """ - Test spatial search functionality using featureid and resourceid to retrieve geometries. - """ - # Simulate spatial filter with featureid and resourceid, not feature collection - spatial_filter_poly_feature = { - "featureid": self.polygon_feature_id, - "resourceid": self.spatial_filter_geom_resourceid, - "buffer": { - "width": 10, - "unit": 'ft' - }, - "inverted": False - } - response_json = get_response_json(self.client, spatial_filter=spatial_filter_poly_feature) - self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) - - spatial_filter_point_feature = { - "featureid": self.point_feature_id, - "resourceid": self.spatial_filter_geom_resourceid, - "buffer": { - "width": 10, - "unit": 'ft' - }, - "inverted": False - } - response_json = get_response_json(self.client, spatial_filter=spatial_filter_point_feature) - self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) - # # -- ADD TESTS THAT INCLUDE PERMISSIONS REQUIREMENTS -- # # From 89e64d16a65da100db57eb78ba144150c88e8fc5 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 21:15:37 -0700 Subject: [PATCH 39/68] moves up mutate of search_results_obj earlier, adds buffered geom if only featureid, resourceid passed, re #10816 --- arches/app/search/components/map_filter.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 5e3536e2907..88d92b07461 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -30,6 +30,9 @@ def append_dsl(self, search_results_object, permitted_nodegroups, include_provis search_query = Bool() querysting_params = self.request.GET.get(details["componentname"], "") spatial_filter = JSONDeserializer().deserialize(querysting_params) + if details["componentname"] not in search_results_object: + search_results_object[details["componentname"]] = {} + if "features" in spatial_filter: if len(spatial_filter["features"]) > 0: feature_geom = spatial_filter["features"][0]["geometry"] @@ -73,13 +76,11 @@ def append_dsl(self, search_results_object, permitted_nodegroups, include_provis geometries.append(feature) feature_geom = geometries[0]["geometry"] - add_geoshape_query_to_search_query(feature_geom, spatial_filter, permitted_nodegroups, include_provisional, search_query) + buffered_feature_geom = add_geoshape_query_to_search_query(feature_geom, spatial_filter, permitted_nodegroups, include_provisional, search_query) + search_results_object[details["componentname"]] = buffered_feature_geom search_results_object["query"].add_query(search_query) - if details["componentname"] not in search_results_object: - search_results_object[details["componentname"]] = {} - try: search_results_object[details["componentname"]]["search_buffer"] = feature_geom except NameError: @@ -141,3 +142,5 @@ def add_geoshape_query_to_search_query(feature_geom, feature_properties, permitt spatial_query.filter(Terms(field="geometries.provisional", terms=["true"])) search_query.filter(Nested(path="geometries", query=spatial_query)) + + return feature_geom From ae5b22072af7a3c9feaaca6fffcc47ea543e3f02 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 21:26:07 -0700 Subject: [PATCH 40/68] includes commented implementation of turfjs polygon simplification, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index e6619c00df0..64661e3091c 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -1,7 +1,9 @@ -define(['arches', +define([ + 'arches', 'knockout', + 'turf', 'templates/views/components/map-popup.htm' -], function(arches, ko, popupTemplate) { +], function(arches, ko, turf, popupTemplate) { var provider = { @@ -49,7 +51,12 @@ define(['arches', */ sendFeatureToMapFilter: function(popupFeatureObject) { - const feature = popupFeatureObject.geometries()[0].geom.features[0]; + let feature = popupFeatureObject.geometries()[0].geom.features[0]; + // Note that polygons with very high vertex-counts can benefit from simplification. + // To use turf.js library, uncomment the next 3 lines: + // const tolerance = 0.1; // Degree of Simplification: Lower numbers are less simplified, preserving more detail + // const highQuality = true; // Set to true for a slower but higher quality simplification + // turf.simplify(feature.geometry, {tolerance: tolerance, highQuality: highQuality, mutate: true}); popupFeatureObject.mapCard.filterByFeatureGeom(feature, popupFeatureObject.resourceinstanceid); }, From 2fb80c916f5ddde7daf4ada0dfeb0e9b3ed13d0c Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 10 May 2024 21:34:13 -0700 Subject: [PATCH 41/68] includes documentation for popup provider method, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 64661e3091c..f86cd10bbdd 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -60,6 +60,12 @@ define([ popupFeatureObject.mapCard.filterByFeatureGeom(feature, popupFeatureObject.resourceinstanceid); }, + /** + * Return the template that should be used for the + * @param popupFeatureObject - the javascript object of the feature and its associated contexts (e.g. mapCard). + * @returns {boolean} - whether to show "Filter by Feature" on map popup + * typically dependent on at least 1 feature with a geometry and/or a featureid/resourceid combo + */ showFilterByFeature: function(popupFeatureObject) { return (ko.unwrap(popupFeatureObject.geometries) || []).length > 0; }, From bcab5d04ed48ee06a58b2ada065d28f090f99583 Mon Sep 17 00:00:00 2001 From: Galen Date: Mon, 13 May 2024 13:34:58 -0700 Subject: [PATCH 42/68] pass in idx of corresponding geom.feature to sendFeatureToMapFilter, re #10816 --- arches/app/templates/views/components/map-popup.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index afad6f98c6d..c45233d6a13 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -58,7 +58,7 @@ - + From 91b1e9871fd13d48e107952c2d10aacd7dfa9699 Mon Sep 17 00:00:00 2001 From: Galen Date: Mon, 13 May 2024 13:35:51 -0700 Subject: [PATCH 43/68] rm unused commented code, move to docs, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index f86cd10bbdd..f85bf527bf8 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -1,9 +1,8 @@ define([ 'arches', 'knockout', - 'turf', 'templates/views/components/map-popup.htm' -], function(arches, ko, turf, popupTemplate) { +], function(arches, ko, popupTemplate) { var provider = { From 8868cbdba6e3d96184314e64944f1070ab16e011 Mon Sep 17 00:00:00 2001 From: Galen Date: Mon, 13 May 2024 13:36:47 -0700 Subject: [PATCH 44/68] selects geometries.feature corresponding to the feature clicked in popup via passed index, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index f85bf527bf8..0d6a7a28541 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -48,24 +48,19 @@ define([ * @required send argument: @param feature - a geojson feature object * @optional send argument: @param resourceid */ - sendFeatureToMapFilter: function(popupFeatureObject) + sendFeatureToMapFilter: function(popupFeatureObject, featureGeomIndex) { - let feature = popupFeatureObject.geometries()[0].geom.features[0]; - // Note that polygons with very high vertex-counts can benefit from simplification. - // To use turf.js library, uncomment the next 3 lines: - // const tolerance = 0.1; // Degree of Simplification: Lower numbers are less simplified, preserving more detail - // const highQuality = true; // Set to true for a slower but higher quality simplification - // turf.simplify(feature.geometry, {tolerance: tolerance, highQuality: highQuality, mutate: true}); + let feature = popupFeatureObject.geometries()[featureGeomIndex].geom.features[0]; popupFeatureObject.mapCard.filterByFeatureGeom(feature, popupFeatureObject.resourceinstanceid); }, /** - * Return the template that should be used for the + * Determines whether to show the button for Filter By Feature * @param popupFeatureObject - the javascript object of the feature and its associated contexts (e.g. mapCard). * @returns {boolean} - whether to show "Filter by Feature" on map popup * typically dependent on at least 1 feature with a geometry and/or a featureid/resourceid combo */ - showFilterByFeature: function(popupFeatureObject) { + showFilterByFeature: function(popupFeatureObject, featureGeomIndex) { return (ko.unwrap(popupFeatureObject.geometries) || []).length > 0; }, From fcbfcd88b724e247503566d71013a4354fb2410f Mon Sep 17 00:00:00 2001 From: Galen Date: Mon, 13 May 2024 13:44:13 -0700 Subject: [PATCH 45/68] change default buffer behavior in filterByFeatureGeom, re #10816 --- arches/app/media/js/views/components/search/map-filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 10096e4d95a..102767b14cc 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -276,7 +276,7 @@ define([ }, this); this.filterByFeatureGeom = function(feature, resourceid=null) { - if (feature.geometry.type == 'Point') { self.buffer(100); } + if (feature.geometry.type == 'Point' && this.buffer() == 0) { this.buffer(25); } if (feature.id && resourceid) { self.searchGeometryFeature({ "featureid": feature.id, From cecdc2ca08053ad20e3a0a64979a69fba3ae7393 Mon Sep 17 00:00:00 2001 From: Galen Date: Mon, 13 May 2024 13:55:23 -0700 Subject: [PATCH 46/68] adds check for feature-filter geom match before access, query mod, re #10816 --- arches/app/search/components/map_filter.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 88d92b07461..a5d45a98713 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -41,6 +41,7 @@ def append_dsl(self, search_results_object, permitted_nodegroups, include_provis feature_properties = spatial_filter["features"][0]["properties"] add_geoshape_query_to_search_query(feature_geom, feature_properties, permitted_nodegroups, include_provisional, search_query) + search_results_object["query"].add_query(search_query) elif "featureid" in spatial_filter and "resourceid" in spatial_filter: se = SearchEngineFactory().create() @@ -75,11 +76,11 @@ def append_dsl(self, search_results_object, permitted_nodegroups, include_provis if feature['id'] == spatial_filter["featureid"]: geometries.append(feature) - feature_geom = geometries[0]["geometry"] - buffered_feature_geom = add_geoshape_query_to_search_query(feature_geom, spatial_filter, permitted_nodegroups, include_provisional, search_query) - search_results_object[details["componentname"]] = buffered_feature_geom - - search_results_object["query"].add_query(search_query) + if len(geometries) > 0: + feature_geom = geometries[0]["geometry"] + buffered_feature_geom = add_geoshape_query_to_search_query(feature_geom, spatial_filter, permitted_nodegroups, include_provisional, search_query) + search_results_object[details["componentname"]] = buffered_feature_geom + search_results_object["query"].add_query(search_query) try: search_results_object[details["componentname"]]["search_buffer"] = feature_geom From ddc814c223c392d743635298513355528a557010 Mon Sep 17 00:00:00 2001 From: Galen Date: Mon, 13 May 2024 14:04:53 -0700 Subject: [PATCH 47/68] clarifies method docs in map-popup-provider, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 0d6a7a28541..fe7c873677e 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -44,9 +44,11 @@ define([ /** * This method enables custom logic for how the feature in the popup should be handled and/or mutated en route to the mapFilter. * @param popupFeatureObject - the javascript object of the feature and its associated contexts (e.g. mapCard). + * @param featureGeomIndex - int index corresponding to the feature that was clicked, used for lookup against an arry of geoms * @required @method mapCard.filterByFeatureGeom() - * @required send argument: @param feature - a geojson feature object - * @optional send argument: @param resourceid + * @required @send argument: @param feature - a geojson feature object + * with an @optional @property: feature.id corresponding to a uuid for that tile_geometrycollection_feature + * @optional @send argument: @param resourceid */ sendFeatureToMapFilter: function(popupFeatureObject, featureGeomIndex) { @@ -57,6 +59,7 @@ define([ /** * Determines whether to show the button for Filter By Feature * @param popupFeatureObject - the javascript object of the feature and its associated contexts (e.g. mapCard). + * @param featureGeomIndex - int index corresponding to the feature that was clicked, used for lookup against an arry of geoms * @returns {boolean} - whether to show "Filter by Feature" on map popup * typically dependent on at least 1 feature with a geometry and/or a featureid/resourceid combo */ From 6b67f991bbf757e6b3db43e9eb2bfbb484745371 Mon Sep 17 00:00:00 2001 From: Galen Date: Thu, 6 Jun 2024 23:47:48 -0700 Subject: [PATCH 48/68] rm refs to featureid, resourceid in filter by feature, re #10816 --- .../app/media/js/utils/map-popup-provider.js | 4 +- .../js/views/components/search/map-filter.js | 39 ++----------------- 2 files changed, 4 insertions(+), 39 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index fe7c873677e..3f06e7bc478 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -47,13 +47,11 @@ define([ * @param featureGeomIndex - int index corresponding to the feature that was clicked, used for lookup against an arry of geoms * @required @method mapCard.filterByFeatureGeom() * @required @send argument: @param feature - a geojson feature object - * with an @optional @property: feature.id corresponding to a uuid for that tile_geometrycollection_feature - * @optional @send argument: @param resourceid */ sendFeatureToMapFilter: function(popupFeatureObject, featureGeomIndex) { let feature = popupFeatureObject.geometries()[featureGeomIndex].geom.features[0]; - popupFeatureObject.mapCard.filterByFeatureGeom(feature, popupFeatureObject.resourceinstanceid); + popupFeatureObject.mapCard.filterByFeatureGeom(feature); }, /** diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index 102767b14cc..8ba7d5f14ea 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -113,7 +113,6 @@ define([ }; this.searchGeometries = ko.observableArray(null); - this.searchGeometryFeature = ko.observable(null); this.searchAggregations = ko.observable(); this.selectedTool = ko.observable(); this.geoJSONString = ko.observable(undefined); @@ -275,19 +274,8 @@ define([ } }, this); - this.filterByFeatureGeom = function(feature, resourceid=null) { + this.filterByFeatureGeom = function(feature) { if (feature.geometry.type == 'Point' && this.buffer() == 0) { this.buffer(25); } - if (feature.id && resourceid) { - self.searchGeometryFeature({ - "featureid": feature.id, - "resourceid": resourceid, - "buffer": { - "width": this.buffer(), - "unit": this.bufferUnit() - }, - "inverted": this.filter.inverted() - }); - } let currentSearchGeoms = self.searchGeometries(); this.draw.set({ "type": "FeatureCollection", @@ -300,8 +288,6 @@ define([ } self.searchGeometries(currentSearchGeoms); self.updateFilter(); - // if feature is not from a resource-layer, enable feature editing in map - if (!!self.searchGeometryFeature()) { self.draw.changeMode('static'); } }; var updateSearchResultPointLayer = function() { @@ -548,11 +534,7 @@ define([ this.getFilter('term-filter').addTag('Map Filter Enabled', this.name, this.filter.inverted); } this.filter.feature_collection().features[0].properties['inverted'] = this.filter.inverted(); - if (!!this.searchGeometryFeature()) { - queryObj[componentName] = ko.toJSON(this.searchGeometryFeature()); - } else { - queryObj[componentName] = ko.toJSON(this.filter.feature_collection()); - } + queryObj[componentName] = ko.toJSON(this.filter.feature_collection()); } else { delete queryObj[componentName]; } @@ -567,21 +549,7 @@ define([ var hasSpatialFilter = false; if (componentName in query) { var mapQuery = JSON.parse(query[componentName]); - if (mapQuery.featureid && mapQuery.resourceid) { - buffer = mapQuery.buffer; - bufferUnit = mapQuery.bufferUnit; - inverted = mapQuery.inverted; - this.searchGeometryFeature({ - "featureid": mapQuery.id, - "resourceid": mapQuery.resourceid, - "buffer": { - "width": buffer, - "unit": bufferUnit - }, - "inverted": inverted - }); - hasSpatialFilter = true; - } else if (mapQuery.features.length > 0) { + if (mapQuery.features.length > 0) { hasSpatialFilter = true; var properties = mapQuery.features[0].properties; inverted = properties.inverted; @@ -621,7 +589,6 @@ define([ }, clear: function(reset_features) { - this.searchGeometryFeature(null); this.filter.feature_collection({ "type": "FeatureCollection", "features": [] From 53918a199df6578d1eb4f399d9093416bd40265a Mon Sep 17 00:00:00 2001 From: Galen Date: Thu, 6 Jun 2024 23:49:34 -0700 Subject: [PATCH 49/68] alter trans text for filter by feature, re #10816 --- arches/app/templates/javascript.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arches/app/templates/javascript.htm b/arches/app/templates/javascript.htm index effd320df57..4ac5a017818 100644 --- a/arches/app/templates/javascript.htm +++ b/arches/app/templates/javascript.htm @@ -654,7 +654,7 @@ map-add-line='{% trans "Add line" as mapAddLine %} "{{ mapAddLine|escapejs }}"' map-add-polygon='{% trans "Add polygon" as mapAddPolygon %} "{{ mapAddPolygon|escapejs }}"' map-select-drawing='{% trans "Select drawing" as mapSelectDrawing %} "{{ mapSelectDrawing|escapejs }}"' - filter-by-feature='{% trans "Filter by Feature" as filterByFeature %} "{{ filterByFeature|escapejs }}"' + filter-by-feature='{% trans "Add Feature to Map Filter" as filterByFeature %} "{{ filterByFeature|escapejs }}"' related-instance-map-sources='{% trans "Related instance map sources" as relatedInstanceMapSources %} "{{ relatedInstanceMapSources|escapejs }}"' related-instance-map-source-layers='{% trans "Related instance map source layers (optional)" as relatedInstanceMapSourceLayers %} "{{ relatedInstanceMapSourceLayers|escapejs }}"' intersection-layer-configuration='{% trans "Intersection layer configuration" as intersectionLayerConfiguration %} "{{ intersectionLayerConfiguration|escapejs }}"' From e1ea2f3d7383cfb53be53977afcf04b413df23de Mon Sep 17 00:00:00 2001 From: Galen Date: Tue, 18 Jun 2024 13:39:10 -0700 Subject: [PATCH 50/68] rm refs to popupFeatureObject index, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 8 +++----- arches/app/templates/views/components/map-popup.htm | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 3f06e7bc478..9729277a057 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -44,24 +44,22 @@ define([ /** * This method enables custom logic for how the feature in the popup should be handled and/or mutated en route to the mapFilter. * @param popupFeatureObject - the javascript object of the feature and its associated contexts (e.g. mapCard). - * @param featureGeomIndex - int index corresponding to the feature that was clicked, used for lookup against an arry of geoms * @required @method mapCard.filterByFeatureGeom() * @required @send argument: @param feature - a geojson feature object */ - sendFeatureToMapFilter: function(popupFeatureObject, featureGeomIndex) + sendFeatureToMapFilter: function(popupFeatureObject) { - let feature = popupFeatureObject.geometries()[featureGeomIndex].geom.features[0]; + let feature = popupFeatureObject.geometries()[0].geom.features[0]; popupFeatureObject.mapCard.filterByFeatureGeom(feature); }, /** * Determines whether to show the button for Filter By Feature * @param popupFeatureObject - the javascript object of the feature and its associated contexts (e.g. mapCard). - * @param featureGeomIndex - int index corresponding to the feature that was clicked, used for lookup against an arry of geoms * @returns {boolean} - whether to show "Filter by Feature" on map popup * typically dependent on at least 1 feature with a geometry and/or a featureid/resourceid combo */ - showFilterByFeature: function(popupFeatureObject, featureGeomIndex) { + showFilterByFeature: function(popupFeatureObject) { return (ko.unwrap(popupFeatureObject.geometries) || []).length > 0; }, diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index c45233d6a13..92506dbcfd8 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -58,7 +58,7 @@ - + From f18555cb39515af73ce1d47d6423130a81673085 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 14 Aug 2024 17:14:55 -0700 Subject: [PATCH 51/68] find specific feature in multi-feature geom tiles, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 9729277a057..2440416ebe0 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -49,8 +49,15 @@ define([ */ sendFeatureToMapFilter: function(popupFeatureObject) { - let feature = popupFeatureObject.geometries()[0].geom.features[0]; - popupFeatureObject.mapCard.filterByFeatureGeom(feature); + let foundFeature = null; + for (let geometry of popupFeatureObject.geometries()) { + if (geometry.geom && Array.isArray(geometry.geom.features)) { + foundFeature = geometry.geom.features.find(feature => feature.id === popupFeatureObject.featureid); + if (foundFeature) + break; + } + } + popupFeatureObject.mapCard.filterByFeatureGeom(foundFeature); }, /** From 52aad8486595ac91f2098b1e1fc21f5b0a47ad81 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 14 Aug 2024 17:25:28 -0700 Subject: [PATCH 52/68] run black against map_filter.py --- arches/app/search/components/map_filter.py | 69 +++++++++++----------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index d081a00a6c0..0883caabcd7 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -4,7 +4,15 @@ from django.utils.translation import gettext as _ from arches.app.models.system_settings import settings from arches.app.utils.betterJSONSerializer import JSONSerializer, JSONDeserializer -from arches.app.search.elasticsearch_dsl_builder import Bool, Match, Query, Nested, Term, Terms, GeoShape +from arches.app.search.elasticsearch_dsl_builder import ( + Bool, + Match, + Query, + Nested, + Term, + Terms, + GeoShape, +) from arches.app.search.components.base import BaseSearchFilter from arches.app.search.search_engine_factory import SearchEngineFactory from arches.app.search.mappings import RESOURCES_INDEX @@ -34,7 +42,7 @@ def append_dsl( spatial_filter = JSONDeserializer().deserialize(querysting_params) if details["componentname"] not in search_results_object: search_results_object[details["componentname"]] = {} - + if "features" in spatial_filter: if len(spatial_filter["features"]) > 0: feature_geom = spatial_filter["features"][0]["geometry"] @@ -47,7 +55,7 @@ def append_dsl( feature_properties, permitted_nodegroups, include_provisional, - search_query + search_query, ) search_results_object["query"].add_query(search_query) @@ -56,47 +64,46 @@ def append_dsl( main_query = Query(se) nested_query = Nested(path="geometries") match_feature = Match( - field="geometries.geom.features.id", - query=spatial_filter["featureid"] + field="geometries.geom.features.id", query=spatial_filter["featureid"] ) - - # Create a Bool query for conditions inside the nested path bool_nested_query = Bool() bool_nested_query.must(match_feature.dsl) nested_query.add_query(bool_nested_query.dsl) bool_query = Bool() match_resource = Term( - field="resourceinstanceid", - term=spatial_filter["resourceid"] + field="resourceinstanceid", term=spatial_filter["resourceid"] ) - bool_query.must(match_resource.dsl) # Match resource instance ID at the document level + bool_query.must( + match_resource.dsl + ) # Match resource instance ID at the document level bool_query.must(nested_query.dsl) # Add the nested query - + # Set the entire bool query to the main query object main_query.add_query(bool_query.dsl) response = main_query.search(index=RESOURCES_INDEX) geometries = [] - for hit in response['hits']['hits']: + for hit in response["hits"]["hits"]: if len(geometries) > 0: break - for geom in hit['_source']['geometries']: + for geom in hit["_source"]["geometries"]: if len(geometries) > 0: break - for feature in geom['geom']['features']: + for feature in geom["geom"]["features"]: if len(geometries) > 0: break - if feature['id'] == spatial_filter["featureid"]: + if feature["id"] == spatial_filter["featureid"]: geometries.append(feature) if len(geometries) > 0: feature_geom = geometries[0]["geometry"] buffered_feature_geom = add_geoshape_query_to_search_query( - feature_geom, spatial_filter, + feature_geom, + spatial_filter, permitted_nodegroups, include_provisional, - search_query + search_query, ) search_results_object[details["componentname"]] = buffered_feature_geom search_results_object["query"].add_query(search_query) @@ -137,13 +144,14 @@ def _buffer(geojson, width=0, unit="ft"): geom = GEOSGeometry(res[0], srid=4326) return geom + def add_geoshape_query_to_search_query( - feature_geom, - feature_properties, - permitted_nodegroups, - include_provisional, - search_query - ): + feature_geom, + feature_properties, + permitted_nodegroups, + include_provisional, + search_query, +): buffer = {"width": 0, "unit": "ft"} if "buffer" in feature_properties: @@ -154,7 +162,7 @@ def add_geoshape_query_to_search_query( geoshape = GeoShape( field="geometries.geom.features.geometry", type=feature_geom["type"], - coordinates=feature_geom["coordinates"] + coordinates=feature_geom["coordinates"], ) invert_spatial_search = False if "inverted" in feature_properties: @@ -168,21 +176,14 @@ def add_geoshape_query_to_search_query( # get the nodegroup_ids that the user has permission to search spatial_query.filter( - Terms( - field="geometries.nodegroup_id", - terms=permitted_nodegroups - ) + Terms(field="geometries.nodegroup_id", terms=permitted_nodegroups) ) if include_provisional is False: - spatial_query.filter( - Terms(field="geometries.provisional", terms=["false"]) - ) + spatial_query.filter(Terms(field="geometries.provisional", terms=["false"])) elif include_provisional == "only provisional": - spatial_query.filter( - Terms(field="geometries.provisional", terms=["true"]) - ) + spatial_query.filter(Terms(field="geometries.provisional", terms=["true"])) search_query.filter(Nested(path="geometries", query=spatial_query)) From 9782f7f8b8cfd71c3c9d9ade772b741b4e841929 Mon Sep 17 00:00:00 2001 From: Galen Date: Wed, 14 Aug 2024 17:27:42 -0700 Subject: [PATCH 53/68] run black on spatial_search_tests.py --- tests/search/spatial_search_tests.py | 100 ++++++++++++++++----------- 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/tests/search/spatial_search_tests.py b/tests/search/spatial_search_tests.py index 35e2e61739a..522f8a49abc 100644 --- a/tests/search/spatial_search_tests.py +++ b/tests/search/spatial_search_tests.py @@ -27,7 +27,9 @@ from arches.app.models.resource import Resource from arches.app.models.tile import Tile from arches.app.utils.i18n import LanguageSynchronizer -from arches.app.utils.data_management.resource_graphs.importer import import_graph as ResourceGraphImporter +from arches.app.utils.data_management.resource_graphs.importer import ( + import_graph as ResourceGraphImporter, +) from arches.app.utils.betterJSONSerializer import JSONSerializer, JSONDeserializer from guardian.shortcuts import assign_perm from arches.app.search.search_engine_factory import SearchEngineFactory @@ -37,6 +39,7 @@ # these tests can be run from the command line via # python manage.py test tests.search.spatial_search_tests --settings="tests.test_settings" + class SpatialSearchTests(ArchesTestCase): @classmethod def setUpClass(cls): @@ -52,45 +55,60 @@ def setUpClass(cls): LanguageSynchronizer.synchronize_settings_with_db() models.ResourceInstance.objects.all().delete() - with open(os.path.join("tests/fixtures/resource_graphs/Search Test Model.json"), "r") as f: + with open( + os.path.join("tests/fixtures/resource_graphs/Search Test Model.json"), "r" + ) as f: archesfile = JSONDeserializer().deserialize(f) ResourceGraphImporter(archesfile["graph"]) cls.search_model_graphid = "d291a445-fa5f-11e6-afa8-14109fd34195" cls.search_model_cultural_period_nodeid = "7a182580-fa60-11e6-96d1-14109fd34195" cls.search_model_creation_date_nodeid = "1c1d05f5-fa60-11e6-887f-14109fd34195" - cls.search_model_destruction_date_nodeid = "e771b8a1-65fe-11e7-9163-14109fd34195" + cls.search_model_destruction_date_nodeid = ( + "e771b8a1-65fe-11e7-9163-14109fd34195" + ) cls.search_model_name_nodeid = "2fe14de3-fa61-11e6-897b-14109fd34195" cls.search_model_sensitive_info_nodeid = "57446fae-65ff-11e7-b63a-14109fd34195" cls.search_model_geom_nodeid = "3ebc6785-fa61-11e6-8c85-14109fd34195" - cls.user = User.objects.create_user("unpriviliged_user", "unpriviliged_user@archesproject.org", "test") + cls.user = User.objects.create_user( + "unpriviliged_user", "unpriviliged_user@archesproject.org", "test" + ) cls.user.groups.add(Group.objects.get(name="Guest")) - cls.spatial_filter_geom_resourceid = 'cbb1e9df-5110-4f22-933c-9ccbeb57431b' - cls.spatial_filter_geom_resource = Resource(graph_id=cls.search_model_graphid, resourceinstanceid=cls.spatial_filter_geom_resourceid) + cls.spatial_filter_geom_resourceid = "cbb1e9df-5110-4f22-933c-9ccbeb57431b" + cls.spatial_filter_geom_resource = Resource( + graph_id=cls.search_model_graphid, + resourceinstanceid=cls.spatial_filter_geom_resourceid, + ) cls.spatial_filter_geom_resource.save() cls.polygon_feature_id = "2190cb9e-7c57-485c-bf1a-7b6f0389f8b1" - + geom_poly = { "type": "FeatureCollection", "features": [ { - "geometry": {"type": "Polygon", "coordinates": [[ - [-118.22687435396205, 34.04498354472949], - [-118.22673462509519, 34.045024944460636], - [-118.22661984555208, 34.044757071199754], - [-118.22675979254618, 34.044715607647184], - [-118.22687435396205, 34.04498354472949] - ]] + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-118.22687435396205, 34.04498354472949], + [-118.22673462509519, 34.045024944460636], + [-118.22661984555208, 34.044757071199754], + [-118.22675979254618, 34.044715607647184], + [-118.22687435396205, 34.04498354472949], + ] + ], }, - "type": "Feature", + "type": "Feature", "id": cls.polygon_feature_id, - "properties": {} + "properties": {}, } - ] + ], } - poly_tile = Tile.get_blank_tile(cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid) + poly_tile = Tile.get_blank_tile( + cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid + ) poly_tile.data[cls.search_model_geom_nodeid] = geom_poly poly_tile.save() cls.point_feature_id = "d41e81ac-4a53-4049-b266-c459b7641bc1" @@ -98,14 +116,19 @@ def setUpClass(cls): "type": "FeatureCollection", "features": [ { - "geometry": {"type": "Point", "coordinates": [-118.22687435396205, 34.04498354472949]}, - "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-118.22687435396205, 34.04498354472949], + }, + "type": "Feature", "id": cls.point_feature_id, - "properties": {} + "properties": {}, } - ] + ], } - point_tile = Tile.get_blank_tile(cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid) + point_tile = Tile.get_blank_tile( + cls.search_model_geom_nodeid, resourceid=cls.spatial_filter_geom_resourceid + ) point_tile.data[cls.search_model_geom_nodeid] = geom_point point_tile.save() time.sleep(2) @@ -123,17 +146,16 @@ def test_spatial_search_by_featureid_and_resourceid_1(self): """ # Simulate spatial filter with featureid and resourceid, not feature collection # spatial filter test data - + spatial_filter_poly_feature = { "featureid": self.polygon_feature_id, "resourceid": self.spatial_filter_geom_resourceid, - "buffer": { - "width": 25, - "unit": 'm' - }, - "inverted": False + "buffer": {"width": 25, "unit": "m"}, + "inverted": False, } - response_json = get_response_json(self.client, spatial_filter=spatial_filter_poly_feature) + response_json = get_response_json( + self.client, spatial_filter=spatial_filter_poly_feature + ) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) def test_spatial_search_by_featureid_and_resourceid_2(self): @@ -141,22 +163,22 @@ def test_spatial_search_by_featureid_and_resourceid_2(self): Test spatial search functionality using featureid and resourceid to retrieve geometries. """ # Simulate spatial filter with featureid and resourceid, not feature collection - # spatial filter test data spatial_filter_point_feature = { "featureid": self.point_feature_id, "resourceid": self.spatial_filter_geom_resourceid, - "buffer": { - "width": 75, - "unit": 'm' - }, - "inverted": False + "buffer": {"width": 75, "unit": "m"}, + "inverted": False, } - response_json = get_response_json(self.client, spatial_filter=spatial_filter_point_feature) + response_json = get_response_json( + self.client, spatial_filter=spatial_filter_point_feature + ) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) -def get_response_json(client, temporal_filter=None, term_filter=None, spatial_filter=None): +def get_response_json( + client, temporal_filter=None, term_filter=None, spatial_filter=None +): query = {} if temporal_filter is not None: query["time-filter"] = JSONSerializer().serialize(temporal_filter) @@ -170,4 +192,4 @@ def get_response_json(client, temporal_filter=None, term_filter=None, spatial_fi client.login(username="unpriviliged_user", password="test") response = client.get("/search/resources", query) response_json = json.loads(response.content) - return response_json \ No newline at end of file + return response_json From ede08ddbe2a278de1b90d3ed27b6e955496557f1 Mon Sep 17 00:00:00 2001 From: Galen Date: Thu, 22 Aug 2024 14:47:20 -0700 Subject: [PATCH 54/68] rm unneeded test for feature-less map-filter req, refactor get_response_json into utils, re #10816 --- arches/app/search/components/map_filter.py | 2 + tests/search/spatial_search_tests.py | 79 ++++++---------------- tests/utils/search_test_utils.py | 19 ++++++ 3 files changed, 42 insertions(+), 58 deletions(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 71ef64f5809..56e24e9b505 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -39,6 +39,8 @@ def append_dsl(self, search_query_object, **kwargs): search_query = Bool() querysting_params = self.request.GET.get(self.componentname, "{}") spatial_filter = JSONDeserializer().deserialize(querysting_params) + if not search_query_object.get(self.componentname, None): + search_query_object[self.componentname] = {} if "features" in spatial_filter: if len(spatial_filter["features"]) > 0: diff --git a/tests/search/spatial_search_tests.py b/tests/search/spatial_search_tests.py index 522f8a49abc..6cb7459e8ea 100644 --- a/tests/search/spatial_search_tests.py +++ b/tests/search/spatial_search_tests.py @@ -17,10 +17,8 @@ """ import os -import json -import time from tests.base_test import ArchesTestCase -from django.urls import reverse +from tests.utils.search_test_utils import sync_es, get_response_json from django.contrib.auth.models import User, Group from django.test.client import Client from arches.app.models import models @@ -30,10 +28,9 @@ from arches.app.utils.data_management.resource_graphs.importer import ( import_graph as ResourceGraphImporter, ) -from arches.app.utils.betterJSONSerializer import JSONSerializer, JSONDeserializer -from guardian.shortcuts import assign_perm +from arches.app.utils.betterJSONSerializer import JSONDeserializer from arches.app.search.search_engine_factory import SearchEngineFactory -from arches.app.search.elasticsearch_dsl_builder import Query, Term +from arches.app.search.elasticsearch_dsl_builder import Query from arches.app.search.mappings import TERMS_INDEX, CONCEPTS_INDEX, RESOURCES_INDEX # these tests can be run from the command line via @@ -131,7 +128,7 @@ def setUpClass(cls): ) point_tile.data[cls.search_model_geom_nodeid] = geom_point point_tile.save() - time.sleep(2) + sync_es(se) @classmethod def tearDownClass(cls): @@ -140,56 +137,22 @@ def tearDownClass(cls): models.GraphModel.objects.filter(pk=cls.search_model_graphid).delete() super().tearDownClass() - def test_spatial_search_by_featureid_and_resourceid_1(self): - """ - Test spatial search functionality using featureid and resourceid to retrieve geometries. - """ - # Simulate spatial filter with featureid and resourceid, not feature collection - # spatial filter test data - - spatial_filter_poly_feature = { - "featureid": self.polygon_feature_id, - "resourceid": self.spatial_filter_geom_resourceid, - "buffer": {"width": 25, "unit": "m"}, - "inverted": False, - } - response_json = get_response_json( - self.client, spatial_filter=spatial_filter_poly_feature - ) - self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) - - def test_spatial_search_by_featureid_and_resourceid_2(self): - """ - Test spatial search functionality using featureid and resourceid to retrieve geometries. - """ - # Simulate spatial filter with featureid and resourceid, not feature collection - - spatial_filter_point_feature = { - "featureid": self.point_feature_id, - "resourceid": self.spatial_filter_geom_resourceid, - "buffer": {"width": 75, "unit": "m"}, - "inverted": False, + def test_spatial_search_by_point_buffered(self): + spatial_filter = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "inverted": False, + "buffer": {"width": "100", "unit": "ft"}, + }, + "geometry": { + "coordinates": [-118.22687435396205, 34.04498354472949], + "type": "Point", + }, + } + ], } - response_json = get_response_json( - self.client, spatial_filter=spatial_filter_point_feature - ) + response_json = get_response_json(self.client, map_filter=spatial_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) - - -def get_response_json( - client, temporal_filter=None, term_filter=None, spatial_filter=None -): - query = {} - if temporal_filter is not None: - query["time-filter"] = JSONSerializer().serialize(temporal_filter) - if term_filter is not None: - query["term-filter"] = JSONSerializer().serialize(term_filter) - if spatial_filter is not None: - query["map-filter"] = JSONSerializer().serialize(spatial_filter) - resource_reviewer_group = Group.objects.get(name="Resource Reviewer") - test_user = User.objects.get(username="unpriviliged_user") - test_user.groups.add(resource_reviewer_group) - client.login(username="unpriviliged_user", password="test") - response = client.get("/search/resources", query) - response_json = json.loads(response.content) - return response_json diff --git a/tests/utils/search_test_utils.py b/tests/utils/search_test_utils.py index 8e220a2f6ac..f797b02759e 100644 --- a/tests/utils/search_test_utils.py +++ b/tests/utils/search_test_utils.py @@ -1,2 +1,21 @@ +import json +from arches.app.utils.betterJSONSerializer import JSONSerializer +from django.contrib.auth.models import User, Group + + def sync_es(search_engine, index="test_resources"): search_engine.es.indices.refresh(index=index) + + +def get_response_json(client, **kwargs): + query = kwargs.pop("query", {}) + for filter_name, filter_value in kwargs.items(): + query[filter_name.replace("_", "-")] = JSONSerializer().serialize(filter_value) + + resource_reviewer_group = Group.objects.get(name="Resource Reviewer") + test_user = User.objects.get(username="unpriviliged_user") + test_user.groups.add(resource_reviewer_group) + client.login(username="unpriviliged_user", password="test") + response = client.get("/search/resources", query) + response_json = json.loads(response.content) + return response_json From ae1adfb1f34a4d6903c769a412b912e77b8eff6a Mon Sep 17 00:00:00 2001 From: Galen Date: Thu, 22 Aug 2024 14:48:17 -0700 Subject: [PATCH 55/68] refactor get_response_json into utils, change kwarg names in search_tests, re #10816 --- tests/views/search_tests.py | 56 ++++++++++++------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/tests/views/search_tests.py b/tests/views/search_tests.py index 7c3afac6088..1da10d7c650 100644 --- a/tests/views/search_tests.py +++ b/tests/views/search_tests.py @@ -20,7 +20,7 @@ import json import time from tests.base_test import ArchesTestCase -from tests.utils.search_test_utils import sync_es +from tests.utils.search_test_utils import sync_es, get_response_json from django.http import HttpRequest from django.urls import reverse from django.contrib.auth.models import User, Group @@ -239,7 +239,7 @@ def test_temporal_only_search_1(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) self.assertCountEqual( extract_pks(response_json), @@ -262,7 +262,7 @@ def test_temporal_only_search_2(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -284,7 +284,7 @@ def test_temporal_only_search_3(self): "dateNodeId": self.search_model_creation_date_nodeid, "inverted": False, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -306,7 +306,7 @@ def test_temporal_only_search_4(self): "dateNodeId": self.search_model_creation_date_nodeid, "inverted": True, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) def test_temporal_only_search_5(self): @@ -321,7 +321,7 @@ def test_temporal_only_search_5(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -343,7 +343,7 @@ def test_temporal_only_search_6(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) self.assertCountEqual( extract_pks(response_json), @@ -366,7 +366,7 @@ def test_temporal_only_search_7(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) def test_temporal_only_search_8(self): @@ -381,7 +381,7 @@ def test_temporal_only_search_8(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) self.assertCountEqual( extract_pks(response_json), @@ -404,7 +404,7 @@ def test_temporal_only_search_9(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) self.assertCountEqual( extract_pks(response_json), @@ -427,7 +427,7 @@ def test_temporal_only_search_10(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) def test_temporal_only_search_11(self): @@ -442,7 +442,7 @@ def test_temporal_only_search_11(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -476,7 +476,7 @@ def test_temporal_and_term_search_1(self): } ] response_json = get_response_json( - self.client, temporal_filter=temporal_filter, term_filter=term_filter + self.client, time_filter=temporal_filter, term_filter=term_filter ) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) self.assertCountEqual(extract_pks(response_json), [str(self.date_resource.pk)]) @@ -505,7 +505,7 @@ def test_temporal_and_term_search_2(self): } ] response_json = get_response_json( - self.client, temporal_filter=temporal_filter, term_filter=term_filter + self.client, time_filter=temporal_filter, term_filter=term_filter ) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) @@ -685,7 +685,7 @@ def test_spatial_search_1(self): } ], } - response_json = get_response_json(self.client, spatial_filter=spatial_filter) + response_json = get_response_json(self.client, map_filter=spatial_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) self.assertCountEqual(extract_pks(response_json), [str(self.name_resource.pk)]) @@ -712,7 +712,7 @@ def test_spatial_search_2(self): } ], } - response_json = get_response_json(self.client, spatial_filter=spatial_filter) + response_json = get_response_json(self.client, map_filter=spatial_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) # self.assertCountEqual(extract_pks(response_json), [str(self.name_resource.pk)]) @@ -731,7 +731,7 @@ def test_temporal_and_permission_search_1(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -753,7 +753,7 @@ def test_temporal_and_permission_search_2(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, temporal_filter=temporal_filter) + response_json = get_response_json(self.client, time_filter=temporal_filter) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -815,23 +815,3 @@ def extract_pks(response_json): result["_source"]["resourceinstanceid"] for result in response_json["results"]["hits"]["hits"] ] - - -def get_response_json( - client, temporal_filter=None, term_filter=None, spatial_filter=None, query=None -): - # declared here due to mutability issues - query = query if query else {} - if temporal_filter is not None: - query["time-filter"] = JSONSerializer().serialize(temporal_filter) - if term_filter is not None: - query["term-filter"] = JSONSerializer().serialize(term_filter) - if spatial_filter is not None: - query["map-filter"] = JSONSerializer().serialize(spatial_filter) - resource_reviewer_group = Group.objects.get(name="Resource Reviewer") - test_user = User.objects.get(username="unpriviliged_user") - test_user.groups.add(resource_reviewer_group) - client.login(username="unpriviliged_user", password="test") - response = client.get("/search/resources", query) - response_json = json.loads(response.content) - return response_json From aa2fba03ae4d73a654c699c5d930163034da3999 Mon Sep 17 00:00:00 2001 From: Galen Date: Thu, 22 Aug 2024 15:41:15 -0700 Subject: [PATCH 56/68] clear filter geoms every time filterByFeatureGeom called, re #10816 --- .../app/media/js/views/components/search/map-filter.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/arches/app/media/js/views/components/search/map-filter.js b/arches/app/media/js/views/components/search/map-filter.js index f622353024c..2f00cce545d 100644 --- a/arches/app/media/js/views/components/search/map-filter.js +++ b/arches/app/media/js/views/components/search/map-filter.js @@ -276,17 +276,13 @@ define([ this.filterByFeatureGeom = function(feature) { if (feature.geometry.type == 'Point' && this.buffer() == 0) { this.buffer(25); } - let currentSearchGeoms = self.searchGeometries(); + self.searchGeometries.removeAll(); + this.draw.deleteAll(); this.draw.set({ "type": "FeatureCollection", "features": [feature] }); - if (currentSearchGeoms != null) { - currentSearchGeoms.push(feature); - } else { - currentSearchGeoms = [feature]; - } - self.searchGeometries(currentSearchGeoms); + self.searchGeometries([feature]); self.updateFilter(); }; From 5672e47596cbdbafa425a76daced09df60399ed9 Mon Sep 17 00:00:00 2001 From: Galen Date: Thu, 29 Aug 2024 20:22:04 -0700 Subject: [PATCH 57/68] restore setting feature_geom onto query_object.map_filter, re #10816 --- arches/app/search/components/map_filter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index ad86cd84b5e..4e2a2286872 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -54,6 +54,10 @@ def append_dsl(self, search_query_object, **kwargs): search_query, ) search_query_object["query"].add_query(search_query) + + # Add the buffered feature geometry to the search query object + if self.componentname not in search_query_object: + search_query_object[self.componentname] = {} search_query_object[self.componentname][ "search_buffer" ] = buffered_feature_geom From dc23575f3a0e61d84b6f5f48fcb44d229d973d47 Mon Sep 17 00:00:00 2001 From: Galen Mancino Date: Thu, 19 Sep 2024 11:04:50 -0700 Subject: [PATCH 58/68] Update arches/app/media/js/utils/map-popup-provider.js Co-authored-by: Brett Ferguson --- arches/app/media/js/utils/map-popup-provider.js | 1 + 1 file changed, 1 insertion(+) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 2440416ebe0..06f61d6752d 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -50,6 +50,7 @@ define([ sendFeatureToMapFilter: function(popupFeatureObject) { let foundFeature = null; + const stripppedFeatureId = popupFeatureObject.feature.properties.featureid.replace(/-/g,""); for (let geometry of popupFeatureObject.geometries()) { if (geometry.geom && Array.isArray(geometry.geom.features)) { foundFeature = geometry.geom.features.find(feature => feature.id === popupFeatureObject.featureid); From d85c4d2ed3ba672264b46010f410f78c92274bf8 Mon Sep 17 00:00:00 2001 From: Galen Mancino Date: Thu, 19 Sep 2024 11:05:13 -0700 Subject: [PATCH 59/68] Update arches/app/media/js/utils/map-popup-provider.js Co-authored-by: Brett Ferguson --- arches/app/media/js/utils/map-popup-provider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 06f61d6752d..b4a3d7b7839 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -53,7 +53,7 @@ define([ const stripppedFeatureId = popupFeatureObject.feature.properties.featureid.replace(/-/g,""); for (let geometry of popupFeatureObject.geometries()) { if (geometry.geom && Array.isArray(geometry.geom.features)) { - foundFeature = geometry.geom.features.find(feature => feature.id === popupFeatureObject.featureid); + foundFeature = geometry.geom.features.find(feature => feature.id.replace(/-/g, "") === strippedFeatureId); if (foundFeature) break; } From 0b735eb63fec7cf09a13ba8ca2549b0d2f930cbf Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 20 Sep 2024 13:36:07 -0700 Subject: [PATCH 60/68] update get_response_json helper method in tests, re #10816 --- tests/search/spatial_search_tests.py | 3 +- tests/utils/search_test_utils.py | 10 +-- tests/views/search_tests.py | 108 ++++++++++++++------------- 3 files changed, 63 insertions(+), 58 deletions(-) diff --git a/tests/search/spatial_search_tests.py b/tests/search/spatial_search_tests.py index 6cb7459e8ea..77b43484a61 100644 --- a/tests/search/spatial_search_tests.py +++ b/tests/search/spatial_search_tests.py @@ -154,5 +154,6 @@ def test_spatial_search_by_point_buffered(self): } ], } - response_json = get_response_json(self.client, map_filter=spatial_filter) + query = {"map-filter": spatial_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) diff --git a/tests/utils/search_test_utils.py b/tests/utils/search_test_utils.py index f797b02759e..1dd389ac969 100644 --- a/tests/utils/search_test_utils.py +++ b/tests/utils/search_test_utils.py @@ -7,11 +7,11 @@ def sync_es(search_engine, index="test_resources"): search_engine.es.indices.refresh(index=index) -def get_response_json(client, **kwargs): - query = kwargs.pop("query", {}) - for filter_name, filter_value in kwargs.items(): - query[filter_name.replace("_", "-")] = JSONSerializer().serialize(filter_value) - +def get_response_json(client, query={}): + for filter_type, query_string in list(query.items()): + if not isinstance(query_string, str): + query_json_string = JSONSerializer().serialize(query_string) + query[filter_type] = query_json_string resource_reviewer_group = Group.objects.get(name="Resource Reviewer") test_user = User.objects.get(username="unpriviliged_user") test_user.groups.add(resource_reviewer_group) diff --git a/tests/views/search_tests.py b/tests/views/search_tests.py index 2f406324ca4..c3332036bc4 100644 --- a/tests/views/search_tests.py +++ b/tests/views/search_tests.py @@ -223,7 +223,8 @@ def test_temporal_only_search_1(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) self.assertCountEqual( extract_pks(response_json), @@ -246,7 +247,8 @@ def test_temporal_only_search_2(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -268,7 +270,8 @@ def test_temporal_only_search_3(self): "dateNodeId": self.search_model_creation_date_nodeid, "inverted": False, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -290,7 +293,8 @@ def test_temporal_only_search_4(self): "dateNodeId": self.search_model_creation_date_nodeid, "inverted": True, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) def test_temporal_only_search_5(self): @@ -305,7 +309,8 @@ def test_temporal_only_search_5(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -327,7 +332,8 @@ def test_temporal_only_search_6(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) self.assertCountEqual( extract_pks(response_json), @@ -350,7 +356,8 @@ def test_temporal_only_search_7(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) def test_temporal_only_search_8(self): @@ -365,7 +372,8 @@ def test_temporal_only_search_8(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) self.assertCountEqual( extract_pks(response_json), @@ -388,7 +396,8 @@ def test_temporal_only_search_9(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) self.assertCountEqual( extract_pks(response_json), @@ -411,7 +420,8 @@ def test_temporal_only_search_10(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) def test_temporal_only_search_11(self): @@ -426,7 +436,8 @@ def test_temporal_only_search_11(self): "dateNodeId": "", "inverted": True, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -459,9 +470,8 @@ def test_temporal_and_term_search_1(self): "inverted": False, } ] - response_json = get_response_json( - self.client, time_filter=temporal_filter, term_filter=term_filter - ) + query = {"time-filter": temporal_filter, "term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) self.assertCountEqual(extract_pks(response_json), [str(self.date_resource.pk)]) @@ -488,9 +498,8 @@ def test_temporal_and_term_search_2(self): "inverted": False, } ] - response_json = get_response_json( - self.client, time_filter=temporal_filter, term_filter=term_filter - ) + query = {"time-filter": temporal_filter, "term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) def test_term_search_1(self): @@ -510,7 +519,8 @@ def test_term_search_1(self): "inverted": False, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -534,7 +544,8 @@ def test_term_search_2(self): "inverted": True, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -562,7 +573,8 @@ def test_term_search_3(self): "inverted": False, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) self.assertCountEqual(extract_pks(response_json), [str(self.name_resource.pk)]) @@ -584,7 +596,8 @@ def test_term_search_4(self): "inverted": True, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) self.assertCountEqual( extract_pks(response_json), @@ -629,7 +642,8 @@ def test_term_search_on_resource_instance_id(self): } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) def test_concept_search_1(self): @@ -649,7 +663,8 @@ def test_concept_search_1(self): "inverted": False, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -676,7 +691,8 @@ def test_concept_search_2(self): "inverted": True, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -706,7 +722,8 @@ def test_spatial_search_1(self): } ], } - response_json = get_response_json(self.client, map_filter=spatial_filter) + query = {"map-filter": spatial_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) self.assertCountEqual(extract_pks(response_json), [str(self.name_resource.pk)]) @@ -733,7 +750,8 @@ def test_spatial_search_2(self): } ], } - response_json = get_response_json(self.client, map_filter=spatial_filter) + query = {"map-filter": spatial_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) # self.assertCountEqual(extract_pks(response_json), [str(self.name_resource.pk)]) @@ -752,7 +770,8 @@ def test_temporal_and_permission_search_1(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -774,7 +793,8 @@ def test_temporal_and_permission_search_2(self): "dateNodeId": "", "inverted": False, } - response_json = get_response_json(self.client, time_filter=temporal_filter) + query = {"time-filter": temporal_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 2) self.assertCountEqual( extract_pks(response_json), @@ -877,7 +897,8 @@ def test_custom_resource_index(self): "inverted": False, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 1) term_filter = [ { @@ -890,7 +911,8 @@ def test_custom_resource_index(self): "inverted": True, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 3) term_filter = [ @@ -904,7 +926,8 @@ def test_custom_resource_index(self): "inverted": False, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 4) term_filter = [ @@ -918,7 +941,8 @@ def test_custom_resource_index(self): "inverted": True, } ] - response_json = get_response_json(self.client, term_filter=term_filter) + query = {"term-filter": term_filter} + response_json = get_response_json(self.client, query=query) self.assertEqual(response_json["results"]["hits"]["total"]["value"], 0) @@ -929,26 +953,6 @@ def extract_pks(response_json): ] -def get_response_json( - client, temporal_filter=None, term_filter=None, spatial_filter=None, query=None -): - # declared here due to mutability issues - query = query if query else {} - if temporal_filter is not None: - query["time-filter"] = JSONSerializer().serialize(temporal_filter) - if term_filter is not None: - query["term-filter"] = JSONSerializer().serialize(term_filter) - if spatial_filter is not None: - query["map-filter"] = JSONSerializer().serialize(spatial_filter) - resource_reviewer_group = Group.objects.get(name="Resource Reviewer") - test_user = User.objects.get(username="unpriviliged_user") - test_user.groups.add(resource_reviewer_group) - client.login(username="unpriviliged_user", password="test") - response = client.get("/search/resources", query) - response_json = json.loads(response.content) - return response_json - - class TestEsMappingModifier(EsMappingModifier): counter = 1 From 43348aea52bb72f6c77f881d7bfc5a9ee0f8f25f Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 20 Sep 2024 14:18:25 -0700 Subject: [PATCH 61/68] fix typo in const, add null check for foundFeature, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index b4a3d7b7839..98c9af788cf 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -50,7 +50,7 @@ define([ sendFeatureToMapFilter: function(popupFeatureObject) { let foundFeature = null; - const stripppedFeatureId = popupFeatureObject.feature.properties.featureid.replace(/-/g,""); + const strippedFeatureId = popupFeatureObject.feature.properties.featureid.replace(/-/g,""); for (let geometry of popupFeatureObject.geometries()) { if (geometry.geom && Array.isArray(geometry.geom.features)) { foundFeature = geometry.geom.features.find(feature => feature.id.replace(/-/g, "") === strippedFeatureId); @@ -58,7 +58,8 @@ define([ break; } } - popupFeatureObject.mapCard.filterByFeatureGeom(foundFeature); + if (foundFeature) + popupFeatureObject.mapCard.filterByFeatureGeom(foundFeature); }, /** From 75acb857d8ae5c84e053d4e28d926fe41beb7bc5 Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 20 Sep 2024 17:17:45 -0700 Subject: [PATCH 62/68] update trans text for filterByFeature, re #10816 --- arches/app/templates/javascript.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arches/app/templates/javascript.htm b/arches/app/templates/javascript.htm index 26cdacb266c..da286151cf7 100644 --- a/arches/app/templates/javascript.htm +++ b/arches/app/templates/javascript.htm @@ -667,7 +667,7 @@ map-add-line='{% trans "Add line" as mapAddLine %} "{{ mapAddLine|escapejs }}"' map-add-polygon='{% trans "Add polygon" as mapAddPolygon %} "{{ mapAddPolygon|escapejs }}"' map-select-drawing='{% trans "Select drawing" as mapSelectDrawing %} "{{ mapSelectDrawing|escapejs }}"' - filter-by-feature='{% trans "Add Feature to Map Filter" as filterByFeature %} "{{ filterByFeature|escapejs }}"' + filter-by-feature='{% trans "Filter by Map Feature" as filterByFeature %} "{{ filterByFeature|escapejs }}"' related-instance-map-sources='{% trans "Related instance map sources" as relatedInstanceMapSources %} "{{ relatedInstanceMapSources|escapejs }}"' related-instance-map-source-layers='{% trans "Related instance map source layers (optional)" as relatedInstanceMapSourceLayers %} "{{ relatedInstanceMapSourceLayers|escapejs }}"' intersection-layer-configuration='{% trans "Intersection layer configuration" as intersectionLayerConfiguration %} "{{ intersectionLayerConfiguration|escapejs }}"' From 54d79d5bb20b5f2f6a5b689ebb81549feec8564a Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 20 Sep 2024 17:18:57 -0700 Subject: [PATCH 63/68] change filterByFeature icon to fa-search, re #10816 --- arches/app/templates/views/components/map-popup.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index 92506dbcfd8..4cd2731a6e2 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -59,7 +59,7 @@ - +
From d0da3dd2a368ada7d96680569e9bb4332b76faba Mon Sep 17 00:00:00 2001 From: Galen Date: Fri, 20 Sep 2024 17:25:21 -0700 Subject: [PATCH 64/68] duplicate featureid match find logic in the show method, re #10816 --- arches/app/media/js/utils/map-popup-provider.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 98c9af788cf..9d6ba621ac5 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -58,8 +58,7 @@ define([ break; } } - if (foundFeature) - popupFeatureObject.mapCard.filterByFeatureGeom(foundFeature); + popupFeatureObject.mapCard.filterByFeatureGeom(foundFeature); }, /** @@ -69,7 +68,16 @@ define([ * typically dependent on at least 1 feature with a geometry and/or a featureid/resourceid combo */ showFilterByFeature: function(popupFeatureObject) { - return (ko.unwrap(popupFeatureObject.geometries) || []).length > 0; + let foundFeature = null; + const strippedFeatureId = popupFeatureObject.feature.properties.featureid.replace(/-/g,""); + for (let geometry of popupFeatureObject.geometries()) { + if (geometry.geom && Array.isArray(geometry.geom.features)) { + foundFeature = geometry.geom.features.find(feature => feature.id.replace(/-/g, "") === strippedFeatureId); + if (foundFeature) + break; + } + } + return foundFeature !== null; }, }; From 9b67cbc1dd3c3214d317dea2197b33a23ee49e87 Mon Sep 17 00:00:00 2001 From: Galen Date: Sat, 21 Sep 2024 18:43:27 -0700 Subject: [PATCH 65/68] add check for undefined featureid on popupfeature, refactor duplicate code, re #10816 --- .../app/media/js/utils/map-popup-provider.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 9d6ba621ac5..2da520e06ce 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -49,15 +49,7 @@ define([ */ sendFeatureToMapFilter: function(popupFeatureObject) { - let foundFeature = null; - const strippedFeatureId = popupFeatureObject.feature.properties.featureid.replace(/-/g,""); - for (let geometry of popupFeatureObject.geometries()) { - if (geometry.geom && Array.isArray(geometry.geom.features)) { - foundFeature = geometry.geom.features.find(feature => feature.id.replace(/-/g, "") === strippedFeatureId); - if (foundFeature) - break; - } - } + const foundFeature = this.findPopupFeatureById(popupFeatureObject); popupFeatureObject.mapCard.filterByFeatureGeom(foundFeature); }, @@ -68,6 +60,13 @@ define([ * typically dependent on at least 1 feature with a geometry and/or a featureid/resourceid combo */ showFilterByFeature: function(popupFeatureObject) { + const noFeatureId = popupFeatureObject.feature?.properties?.featureid === undefined; + if (noFeatureId) + return false; + return this.findPopupFeatureById(popupFeatureObject) !== null; + }, + + findPopupFeatureById: function(popupFeatureObject) { let foundFeature = null; const strippedFeatureId = popupFeatureObject.feature.properties.featureid.replace(/-/g,""); for (let geometry of popupFeatureObject.geometries()) { @@ -77,7 +76,7 @@ define([ break; } } - return foundFeature !== null; + return foundFeature; }, }; From a90dfaa888bc11f1dfa13008ebd24323e62fec50 Mon Sep 17 00:00:00 2001 From: Alexei Peters Date: Mon, 23 Sep 2024 14:31:28 -0700 Subject: [PATCH 66/68] fix "this" reference error, re #10816 --- arches/app/media/js/viewmodels/map.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arches/app/media/js/viewmodels/map.js b/arches/app/media/js/viewmodels/map.js index 6a82afa0371..80fea7f3b7c 100644 --- a/arches/app/media/js/viewmodels/map.js +++ b/arches/app/media/js/viewmodels/map.js @@ -344,8 +344,8 @@ define([ var data = feature.properties; var id = data.resourceinstanceid; data.showEditButton = self.canEdit; - data.sendFeatureToMapFilter = mapPopupProvider.sendFeatureToMapFilter; - data.showFilterByFeature = mapPopupProvider.showFilterByFeature; + data.sendFeatureToMapFilter = mapPopupProvider.sendFeatureToMapFilter.bind(mapPopupProvider); + data.showFilterByFeature = mapPopupProvider.showFilterByFeature.bind(mapPopupProvider); const descriptionProperties = ['displayname', 'graph_name', 'map_popup', 'geometries']; if (id) { if (!self.resourceLookup[id]){ From 98e92dd58eeb4a6ebd7780d44b3e2aacf709b746 Mon Sep 17 00:00:00 2001 From: Alexei Peters Date: Mon, 23 Sep 2024 15:27:02 -0700 Subject: [PATCH 67/68] nit, re #10816 --- arches/app/search/components/map_filter.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 4e2a2286872..67b2969bb8d 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -4,18 +4,8 @@ from django.utils.translation import gettext as _ from arches.app.models.system_settings import settings from arches.app.utils.betterJSONSerializer import JSONSerializer, JSONDeserializer -from arches.app.search.elasticsearch_dsl_builder import ( - Bool, - Match, - Query, - Nested, - Term, - Terms, - GeoShape, -) +from arches.app.search.elasticsearch_dsl_builder import Bool, Nested, Terms, GeoShape from arches.app.search.components.base import BaseSearchFilter -from arches.app.search.search_engine_factory import SearchEngineFactory -from arches.app.search.mappings import RESOURCES_INDEX logger = logging.getLogger(__name__) From 3665ef0640c00d2fe6ed4aa7ff6531c9974f56eb Mon Sep 17 00:00:00 2001 From: Alexei Peters Date: Mon, 23 Sep 2024 17:49:39 -0700 Subject: [PATCH 68/68] only show filter by feature button on the seach page, re #10816 --- arches/app/media/js/viewmodels/map.js | 1 + arches/app/templates/views/components/map-popup.htm | 2 ++ 2 files changed, 3 insertions(+) diff --git a/arches/app/media/js/viewmodels/map.js b/arches/app/media/js/viewmodels/map.js index 80fea7f3b7c..149dd06169f 100644 --- a/arches/app/media/js/viewmodels/map.js +++ b/arches/app/media/js/viewmodels/map.js @@ -343,6 +343,7 @@ define([ const popupFeatures = features.map(feature => { var data = feature.properties; var id = data.resourceinstanceid; + data.showFilterByFeatureButton = !!params.search; data.showEditButton = self.canEdit; data.sendFeatureToMapFilter = mapPopupProvider.sendFeatureToMapFilter.bind(mapPopupProvider); data.showFilterByFeature = mapPopupProvider.showFilterByFeature.bind(mapPopupProvider); diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index 4cd2731a6e2..53e0bcd0a7c 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -58,10 +58,12 @@ + +