diff --git a/geo_app/cluster_index.py b/geo_app/cluster_index.py index adbde0b..d750107 100644 --- a/geo_app/cluster_index.py +++ b/geo_app/cluster_index.py @@ -17,7 +17,18 @@ ZOOM_TO_RES = { } -def _fetch_nodes(db, west, south, east, north, transport_type=None, node_type=None): +def _fetch_nodes( + db, + west, + south, + east, + north, + transport_type=None, + node_type=None, + product_uuid=None, + hub_uuid=None, + supplier_uuid=None, +): """Fetch nodes from database for a bounding box. Args: @@ -31,6 +42,9 @@ def _fetch_nodes(db, west, south, east, north, transport_type=None, node_type=No 'south': south, 'east': east, 'north': north, + 'product_uuid': product_uuid, + 'hub_uuid': hub_uuid, + 'supplier_uuid': supplier_uuid, } # Select AQL query based on node_type @@ -41,6 +55,19 @@ def _fetch_nodes(db, west, south, east, north, transport_type=None, node_type=No FILTER node.latitude != null AND node.longitude != null FILTER node.latitude >= @south AND node.latitude <= @north FILTER node.longitude >= @west AND node.longitude <= @east + FILTER @product_uuid == null OR node.product_uuid == @product_uuid + FILTER @supplier_uuid == null OR node.supplier_uuid == @supplier_uuid + LET has_hub = @hub_uuid == null ? true : LENGTH( + FOR edge IN edges + FILTER edge.transport_type == 'offer' + FILTER ( + (edge._from == CONCAT('nodes/', node._key) AND edge._to == CONCAT('nodes/', @hub_uuid)) OR + (edge._to == CONCAT('nodes/', node._key) AND edge._from == CONCAT('nodes/', @hub_uuid)) + ) + LIMIT 1 + RETURN 1 + ) > 0 + FILTER has_hub RETURN node """ elif node_type == 'supplier': @@ -49,6 +76,19 @@ def _fetch_nodes(db, west, south, east, north, transport_type=None, node_type=No FOR offer IN nodes FILTER offer.node_type == 'offer' FILTER offer.supplier_uuid != null + FILTER @product_uuid == null OR offer.product_uuid == @product_uuid + FILTER @supplier_uuid == null OR offer.supplier_uuid == @supplier_uuid + LET has_hub = @hub_uuid == null ? true : LENGTH( + FOR edge IN edges + FILTER edge.transport_type == 'offer' + FILTER ( + (edge._from == CONCAT('nodes/', offer._key) AND edge._to == CONCAT('nodes/', @hub_uuid)) OR + (edge._to == CONCAT('nodes/', offer._key) AND edge._from == CONCAT('nodes/', @hub_uuid)) + ) + LIMIT 1 + RETURN 1 + ) > 0 + FILTER has_hub LET supplier = DOCUMENT(CONCAT('nodes/', offer.supplier_uuid)) FILTER supplier != null FILTER supplier.latitude != null AND supplier.longitude != null @@ -74,6 +114,20 @@ def _fetch_nodes(db, west, south, east, north, transport_type=None, node_type=No FILTER node.latitude != null AND node.longitude != null FILTER node.latitude >= @south AND node.latitude <= @north FILTER node.longitude >= @west AND node.longitude <= @east + FILTER @hub_uuid == null OR node._key == @hub_uuid + LET has_offer = (@product_uuid == null AND @supplier_uuid == null) ? true : LENGTH( + FOR edge IN edges + FILTER edge.transport_type == 'offer' + FILTER edge._from == CONCAT('nodes/', node._key) OR edge._to == CONCAT('nodes/', node._key) + LET offer_id = edge._from == CONCAT('nodes/', node._key) ? edge._to : edge._from + LET offer = DOCUMENT(offer_id) + FILTER offer != null AND offer.node_type == 'offer' + FILTER @product_uuid == null OR offer.product_uuid == @product_uuid + FILTER @supplier_uuid == null OR offer.supplier_uuid == @supplier_uuid + LIMIT 1 + RETURN 1 + ) > 0 + FILTER has_offer RETURN node """ @@ -97,7 +151,19 @@ def _fetch_nodes(db, west, south, east, north, transport_type=None, node_type=No return nodes -def get_clustered_nodes(db, west, south, east, north, zoom, transport_type=None, node_type=None): +def get_clustered_nodes( + db, + west, + south, + east, + north, + zoom, + transport_type=None, + node_type=None, + product_uuid=None, + hub_uuid=None, + supplier_uuid=None, +): """ Get clustered nodes for given bounding box and zoom level. @@ -111,7 +177,18 @@ def get_clustered_nodes(db, west, south, east, north, zoom, transport_type=None, node_type: Type of nodes ('logistics', 'offer', 'supplier') """ resolution = ZOOM_TO_RES.get(int(zoom), 5) - nodes = _fetch_nodes(db, west, south, east, north, transport_type, node_type) + nodes = _fetch_nodes( + db, + west, + south, + east, + north, + transport_type, + node_type, + product_uuid, + hub_uuid, + supplier_uuid, + ) if not nodes: return [] @@ -157,4 +234,3 @@ def get_clustered_nodes(db, west, south, east, north, zoom, transport_type=None, logger.info("Returning %d clusters/points for zoom=%d res=%d", len(results), zoom, resolution) return results - diff --git a/geo_app/schema.py b/geo_app/schema.py index 1611b78..bb77261 100644 --- a/geo_app/schema.py +++ b/geo_app/schema.py @@ -248,6 +248,9 @@ class Query(graphene.ObjectType): zoom=graphene.Int(required=True, description="Map zoom level (0-16)"), transport_type=graphene.String(description="Filter by transport type"), node_type=graphene.String(description="Node type: logistics, offer, supplier"), + product_uuid=graphene.String(description="Filter by product UUID"), + hub_uuid=graphene.String(description="Filter by hub UUID"), + supplier_uuid=graphene.String(description="Filter by supplier UUID"), description="Get clustered nodes for map display (server-side clustering)", ) @@ -848,10 +851,35 @@ class Query(graphene.ObjectType): return None - def resolve_clustered_nodes(self, info, west, south, east, north, zoom, transport_type=None, node_type=None): + def resolve_clustered_nodes( + self, + info, + west, + south, + east, + north, + zoom, + transport_type=None, + node_type=None, + product_uuid=None, + hub_uuid=None, + supplier_uuid=None, + ): """Get clustered nodes for map display using server-side SuperCluster.""" db = get_db() - clusters = get_clustered_nodes(db, west, south, east, north, zoom, transport_type, node_type) + clusters = get_clustered_nodes( + db, + west, + south, + east, + north, + zoom, + transport_type, + node_type, + product_uuid, + hub_uuid, + supplier_uuid, + ) return [ClusterPointType(**c) for c in clusters] def resolve_products(self, info):