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. """Fetch nodes from database for a bounding box.
Args: Args:
@@ -31,6 +42,9 @@ def _fetch_nodes(db, west, south, east, north, transport_type=None, node_type=No
'south': south, 'south': south,
'east': east, 'east': east,
'north': north, 'north': north,
'product_uuid': product_uuid,
'hub_uuid': hub_uuid,
'supplier_uuid': supplier_uuid,
} }
# Select AQL query based on node_type # 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 != null AND node.longitude != null
FILTER node.latitude >= @south AND node.latitude <= @north FILTER node.latitude >= @south AND node.latitude <= @north
FILTER node.longitude >= @west AND node.longitude <= @east 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 RETURN node
""" """
elif node_type == 'supplier': 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 FOR offer IN nodes
FILTER offer.node_type == 'offer' FILTER offer.node_type == 'offer'
FILTER offer.supplier_uuid != null 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)) LET supplier = DOCUMENT(CONCAT('nodes/', offer.supplier_uuid))
FILTER supplier != null FILTER supplier != null
FILTER supplier.latitude != null AND supplier.longitude != 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 != null AND node.longitude != null
FILTER node.latitude >= @south AND node.latitude <= @north FILTER node.latitude >= @south AND node.latitude <= @north
FILTER node.longitude >= @west AND node.longitude <= @east 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 RETURN node
""" """
@@ -97,7 +151,19 @@ def _fetch_nodes(db, west, south, east, north, transport_type=None, node_type=No
return nodes 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. 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') node_type: Type of nodes ('logistics', 'offer', 'supplier')
""" """
resolution = ZOOM_TO_RES.get(int(zoom), 5) 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: if not nodes:
return [] 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) logger.info("Returning %d clusters/points for zoom=%d res=%d", len(results), zoom, resolution)
return results return results

View File

@@ -248,6 +248,9 @@ class Query(graphene.ObjectType):
zoom=graphene.Int(required=True, description="Map zoom level (0-16)"), zoom=graphene.Int(required=True, description="Map zoom level (0-16)"),
transport_type=graphene.String(description="Filter by transport type"), transport_type=graphene.String(description="Filter by transport type"),
node_type=graphene.String(description="Node type: logistics, offer, supplier"), 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)", description="Get clustered nodes for map display (server-side clustering)",
) )
@@ -848,10 +851,35 @@ class Query(graphene.ObjectType):
return None 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.""" """Get clustered nodes for map display using server-side SuperCluster."""
db = get_db() 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] return [ClusterPointType(**c) for c in clusters]
def resolve_products(self, info): def resolve_products(self, info):