diff --git a/arches/app/datatypes/core/geojson_feature_collection.py b/arches/app/datatypes/core/geojson_feature_collection.py index 8960921013d..1242f306282 100644 --- a/arches/app/datatypes/core/geojson_feature_collection.py +++ b/arches/app/datatypes/core/geojson_feature_collection.py @@ -117,15 +117,11 @@ def check_geojson_value(self, value): feature["geometry"] ) for new_feature in new_collection["features"]: - new_feature["id"] = ( - geojson["id"] if "id" in geojson else str(uuid.uuid4()) - ) + new_feature["id"] = geojson.get("id", str(uuid.uuid4())) features = features + new_collection["features"] else: # keep the feature id if it exists, or generate a fresh one. - feature["id"] = ( - feature["id"] if "id" in feature else str(uuid.uuid4()) - ) + feature["id"] = feature.get("id", str(uuid.uuid4())) features.append(feature) geojson["features"] = features return geojson diff --git a/arches/app/media/js/utils/map-popup-provider.js b/arches/app/media/js/utils/map-popup-provider.js index 8a660438831..2da520e06ce 100644 --- a/arches/app/media/js/utils/map-popup-provider.js +++ b/arches/app/media/js/utils/map-popup-provider.js @@ -1,4 +1,5 @@ -define(['arches', +define([ + 'arches', 'knockout', 'templates/views/components/map-popup.htm' ], function(arches, ko, popupTemplate) { @@ -40,6 +41,44 @@ 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). + * @required @method mapCard.filterByFeatureGeom() + * @required @send argument: @param feature - a geojson feature object + */ + sendFeatureToMapFilter: function(popupFeatureObject) + { + const foundFeature = this.findPopupFeatureById(popupFeatureObject); + popupFeatureObject.mapCard.filterByFeatureGeom(foundFeature); + }, + + /** + * 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) { + 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()) { + if (geometry.geom && Array.isArray(geometry.geom.features)) { + foundFeature = geometry.geom.features.find(feature => feature.id.replace(/-/g, "") === strippedFeatureId); + if (foundFeature) + break; + } + } + return foundFeature; + }, + }; return provider; }); diff --git a/arches/app/media/js/viewmodels/map.js b/arches/app/media/js/viewmodels/map.js index 2eae59c83d2..149dd06169f 100644 --- a/arches/app/media/js/viewmodels/map.js +++ b/arches/app/media/js/viewmodels/map.js @@ -343,8 +343,11 @@ define([ const popupFeatures = features.map(feature => { var data = feature.properties; var id = data.resourceinstanceid; + data.showFilterByFeatureButton = !!params.search; data.showEditButton = self.canEdit; - const descriptionProperties = ['displayname', 'graph_name', 'map_popup']; + 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]){ data = _.defaults(data, { @@ -352,6 +355,7 @@ define([ 'displayname': '', 'graph_name': '', 'map_popup': '', + 'geometries': [], 'feature': feature, }); if (data.permissions) { 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 8355290b6df..2f00cce545d 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,18 @@ define([ } }, this); + this.filterByFeatureGeom = function(feature) { + if (feature.geometry.type == 'Point' && this.buffer() == 0) { this.buffer(25); } + self.searchGeometries.removeAll(); + this.draw.deleteAll(); + this.draw.set({ + "type": "FeatureCollection", + "features": [feature] + }); + self.searchGeometries([feature]); + self.updateFilter(); + }; + var updateSearchResultPointLayer = function() { var pointSource = self.map().getSource('search-results-points'); var agg = ko.unwrap(self.searchAggregations); diff --git a/arches/app/search/components/map_filter.py b/arches/app/search/components/map_filter.py index 32bd6cfbcf1..67b2969bb8d 100644 --- a/arches/app/search/components/map_filter.py +++ b/arches/app/search/components/map_filter.py @@ -27,7 +27,7 @@ def append_dsl(self, search_query_object, **kwargs): permitted_nodegroups = kwargs.get("permitted_nodegroups") include_provisional = kwargs.get("include_provisional") search_query = Bool() - querystring_params = kwargs.get("querystring", "") + querystring_params = kwargs.get("querystring", "{}") spatial_filter = JSONDeserializer().deserialize(querystring_params) if "features" in spatial_filter: if len(spatial_filter["features"]) > 0: @@ -35,53 +35,22 @@ def append_dsl(self, search_query_object, **kwargs): 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) - # get the nodegroup_ids that the user has permission to search - spatial_query.filter( - Terms(field="geometries.nodegroup_id", terms=permitted_nodegroups) + buffered_feature_geom = add_geoshape_query_to_search_query( + feature_geom, + feature_properties, + permitted_nodegroups, + include_provisional, + search_query, ) + search_query_object["query"].add_query(search_query) - 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_query_object["query"].add_query(search_query) - - if self.componentname not in search_query_object: - search_query_object[self.componentname] = {} - - try: - search_query_object[self.componentname]["search_buffer"] = feature_geom - except NameError: - logger.info(_("Feature geometry is not defined")) + # 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 def _buffer(geojson, width=0, unit="ft"): @@ -111,3 +80,48 @@ def _buffer(geojson, width=0, unit="ft"): res = cursor.fetchone() 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, +): + + 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) + + # 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)) + + return feature_geom diff --git a/arches/app/templates/javascript.htm b/arches/app/templates/javascript.htm index 331e0c647f6..da286151cf7 100644 --- a/arches/app/templates/javascript.htm +++ b/arches/app/templates/javascript.htm @@ -667,6 +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 "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 }}"' diff --git a/arches/app/templates/views/components/map-popup.htm b/arches/app/templates/views/components/map-popup.htm index 3ff25881bd7..53e0bcd0a7c 100644 --- a/arches/app/templates/views/components/map-popup.htm +++ b/arches/app/templates/views/components/map-popup.htm @@ -9,65 +9,71 @@ - - - -
- -