feat(geo): filter clustered nodes by product/hub/supplier
All checks were successful
Build Docker Image / build (push) Successful in 2m42s

This commit is contained in:
Ruslan Bakiev
2026-02-07 08:27:54 +07:00
parent f5f261ff89
commit eb73c5b1a1
2 changed files with 110 additions and 6 deletions

View File

@@ -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

View File

@@ -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):