Add coordinate-based nearest endpoints to geo API
- Add nearestHubs(lat, lon, radius, productUuid?) - hubs near coordinates - Add nearestOffers(lat, lon, radius, productUuid?) - offers near coordinates - Add nearestSuppliers(lat, lon, radius, productUuid?) - suppliers near coordinates - Add routeToCoordinate(offerUuid, lat, lon) - route from offer to coordinates These unified endpoints work with coordinates instead of UUIDs, simplifying the frontend logic by removing the need for entity-specific queries like GetProductsNearHub, GetHubsNearOffer, etc.
This commit is contained in:
@@ -292,6 +292,45 @@ class Query(graphene.ObjectType):
|
||||
description="Get route from a specific offer to hub",
|
||||
)
|
||||
|
||||
# New unified endpoints (coordinate-based)
|
||||
nearest_hubs = graphene.List(
|
||||
NodeType,
|
||||
lat=graphene.Float(required=True, description="Latitude"),
|
||||
lon=graphene.Float(required=True, description="Longitude"),
|
||||
radius=graphene.Float(default_value=1000, description="Search radius in km"),
|
||||
product_uuid=graphene.String(description="Filter hubs by product availability"),
|
||||
limit=graphene.Int(default_value=12, description="Max results"),
|
||||
description="Find nearest hubs to coordinates (optionally filtered by product)",
|
||||
)
|
||||
|
||||
nearest_offers = graphene.List(
|
||||
OfferNodeType,
|
||||
lat=graphene.Float(required=True, description="Latitude"),
|
||||
lon=graphene.Float(required=True, description="Longitude"),
|
||||
radius=graphene.Float(default_value=500, description="Search radius in km"),
|
||||
product_uuid=graphene.String(description="Filter by product UUID"),
|
||||
limit=graphene.Int(default_value=50, description="Max results"),
|
||||
description="Find nearest offers to coordinates (optionally filtered by product)",
|
||||
)
|
||||
|
||||
nearest_suppliers = graphene.List(
|
||||
SupplierType,
|
||||
lat=graphene.Float(required=True, description="Latitude"),
|
||||
lon=graphene.Float(required=True, description="Longitude"),
|
||||
radius=graphene.Float(default_value=1000, description="Search radius in km"),
|
||||
product_uuid=graphene.String(description="Filter by product UUID"),
|
||||
limit=graphene.Int(default_value=12, description="Max results"),
|
||||
description="Find nearest suppliers to coordinates (optionally filtered by product)",
|
||||
)
|
||||
|
||||
route_to_coordinate = graphene.Field(
|
||||
ProductRouteOptionType,
|
||||
offer_uuid=graphene.String(required=True, description="Starting offer UUID"),
|
||||
lat=graphene.Float(required=True, description="Destination latitude"),
|
||||
lon=graphene.Float(required=True, description="Destination longitude"),
|
||||
description="Get route from offer to target coordinates (finds nearest hub to coordinate)",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_routes(db, from_uuid, to_uuid, limit):
|
||||
"""Shared helper to compute K shortest routes between two nodes."""
|
||||
@@ -1348,6 +1387,197 @@ class Query(graphene.ObjectType):
|
||||
logger.info("No route found from offer %s to hub %s", offer_uuid, hub_uuid)
|
||||
return None
|
||||
|
||||
def resolve_nearest_hubs(self, info, lat, lon, radius=1000, product_uuid=None, limit=12):
|
||||
"""Find nearest hubs to coordinates, optionally filtered by product availability."""
|
||||
db = get_db()
|
||||
|
||||
if product_uuid:
|
||||
# Find hubs that have offers for this product within radius
|
||||
aql = """
|
||||
FOR offer IN nodes
|
||||
FILTER offer.node_type == 'offer'
|
||||
FILTER offer.product_uuid == @product_uuid
|
||||
FILTER offer.latitude != null AND offer.longitude != null
|
||||
LET dist_to_offer = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000
|
||||
FILTER dist_to_offer <= @radius
|
||||
|
||||
FOR hub IN nodes
|
||||
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
||||
FILTER hub.product_uuid == null
|
||||
FILTER hub.latitude != null AND hub.longitude != null
|
||||
LET dist_to_hub = DISTANCE(hub.latitude, hub.longitude, @lat, @lon) / 1000
|
||||
FILTER dist_to_hub <= @radius
|
||||
COLLECT hub_uuid = hub._key INTO hub_group
|
||||
LET first_hub = FIRST(hub_group)[0].hub
|
||||
LET hub_dist = DISTANCE(first_hub.latitude, first_hub.longitude, @lat, @lon) / 1000
|
||||
SORT hub_dist ASC
|
||||
LIMIT @limit
|
||||
RETURN MERGE(first_hub, {_key: hub_uuid, distance_km: hub_dist})
|
||||
"""
|
||||
bind_vars = {'lat': lat, 'lon': lon, 'radius': radius, 'product_uuid': product_uuid, 'limit': limit}
|
||||
else:
|
||||
# Simple nearest hubs search
|
||||
aql = """
|
||||
FOR hub IN nodes
|
||||
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
||||
FILTER hub.product_uuid == null
|
||||
FILTER hub.latitude != null AND hub.longitude != null
|
||||
LET dist = DISTANCE(hub.latitude, hub.longitude, @lat, @lon) / 1000
|
||||
FILTER dist <= @radius
|
||||
SORT dist ASC
|
||||
LIMIT @limit
|
||||
RETURN MERGE(hub, {distance_km: dist})
|
||||
"""
|
||||
bind_vars = {'lat': lat, 'lon': lon, 'radius': radius, 'limit': limit}
|
||||
|
||||
try:
|
||||
cursor = db.aql.execute(aql, bind_vars=bind_vars)
|
||||
hubs = []
|
||||
for node in cursor:
|
||||
hubs.append(NodeType(
|
||||
uuid=node['_key'],
|
||||
name=node.get('name'),
|
||||
latitude=node.get('latitude'),
|
||||
longitude=node.get('longitude'),
|
||||
country=node.get('country'),
|
||||
country_code=node.get('country_code'),
|
||||
synced_at=node.get('synced_at'),
|
||||
transport_types=node.get('transport_types') or [],
|
||||
edges=[],
|
||||
))
|
||||
logger.info("Found %d hubs near (%.3f, %.3f) within %d km", len(hubs), lat, lon, radius)
|
||||
return hubs
|
||||
except Exception as e:
|
||||
logger.error("Error finding nearest hubs: %s", e)
|
||||
return []
|
||||
|
||||
def resolve_nearest_offers(self, info, lat, lon, radius=500, product_uuid=None, limit=50):
|
||||
"""Find nearest offers to coordinates, optionally filtered by product."""
|
||||
db = get_db()
|
||||
|
||||
aql = """
|
||||
FOR offer IN nodes
|
||||
FILTER offer.node_type == 'offer'
|
||||
FILTER offer.product_uuid != null
|
||||
FILTER offer.latitude != null AND offer.longitude != null
|
||||
"""
|
||||
|
||||
if product_uuid:
|
||||
aql += " FILTER offer.product_uuid == @product_uuid\n"
|
||||
|
||||
aql += """
|
||||
LET dist = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000
|
||||
FILTER dist <= @radius
|
||||
SORT dist ASC
|
||||
LIMIT @limit
|
||||
RETURN MERGE(offer, {distance_km: dist})
|
||||
"""
|
||||
|
||||
bind_vars = {'lat': lat, 'lon': lon, 'radius': radius, 'limit': limit}
|
||||
if product_uuid:
|
||||
bind_vars['product_uuid'] = product_uuid
|
||||
|
||||
try:
|
||||
cursor = db.aql.execute(aql, bind_vars=bind_vars)
|
||||
offers = []
|
||||
for node in cursor:
|
||||
offers.append(OfferNodeType(
|
||||
uuid=node['_key'],
|
||||
product_uuid=node.get('product_uuid'),
|
||||
product_name=node.get('product_name'),
|
||||
supplier_uuid=node.get('supplier_uuid'),
|
||||
supplier_name=node.get('supplier_name'),
|
||||
latitude=node.get('latitude'),
|
||||
longitude=node.get('longitude'),
|
||||
country=node.get('country'),
|
||||
country_code=node.get('country_code'),
|
||||
price_per_unit=node.get('price_per_unit'),
|
||||
currency=node.get('currency'),
|
||||
quantity=node.get('quantity'),
|
||||
unit=node.get('unit'),
|
||||
))
|
||||
logger.info("Found %d offers near (%.3f, %.3f) within %d km", len(offers), lat, lon, radius)
|
||||
return offers
|
||||
except Exception as e:
|
||||
logger.error("Error finding nearest offers: %s", e)
|
||||
return []
|
||||
|
||||
def resolve_nearest_suppliers(self, info, lat, lon, radius=1000, product_uuid=None, limit=12):
|
||||
"""Find nearest suppliers to coordinates, optionally filtered by product."""
|
||||
db = get_db()
|
||||
|
||||
aql = """
|
||||
FOR offer IN nodes
|
||||
FILTER offer.node_type == 'offer'
|
||||
FILTER offer.supplier_uuid != null
|
||||
FILTER offer.latitude != null AND offer.longitude != null
|
||||
"""
|
||||
|
||||
if product_uuid:
|
||||
aql += " FILTER offer.product_uuid == @product_uuid\n"
|
||||
|
||||
aql += """
|
||||
LET dist = DISTANCE(offer.latitude, offer.longitude, @lat, @lon) / 1000
|
||||
FILTER dist <= @radius
|
||||
COLLECT supplier_uuid = offer.supplier_uuid INTO offers
|
||||
LET first_offer = FIRST(offers).offer
|
||||
LET supplier_dist = DISTANCE(first_offer.latitude, first_offer.longitude, @lat, @lon) / 1000
|
||||
SORT supplier_dist ASC
|
||||
LIMIT @limit
|
||||
RETURN {
|
||||
uuid: supplier_uuid,
|
||||
distance_km: supplier_dist
|
||||
}
|
||||
"""
|
||||
|
||||
bind_vars = {'lat': lat, 'lon': lon, 'radius': radius, 'limit': limit}
|
||||
if product_uuid:
|
||||
bind_vars['product_uuid'] = product_uuid
|
||||
|
||||
try:
|
||||
cursor = db.aql.execute(aql, bind_vars=bind_vars)
|
||||
suppliers = []
|
||||
for s in cursor:
|
||||
suppliers.append(SupplierType(uuid=s['uuid']))
|
||||
logger.info("Found %d suppliers near (%.3f, %.3f) within %d km", len(suppliers), lat, lon, radius)
|
||||
return suppliers
|
||||
except Exception as e:
|
||||
logger.error("Error finding nearest suppliers: %s", e)
|
||||
return []
|
||||
|
||||
def resolve_route_to_coordinate(self, info, offer_uuid, lat, lon):
|
||||
"""Get route from offer to target coordinates (finds nearest hub automatically)."""
|
||||
db = get_db()
|
||||
|
||||
# Find nearest hub to target coordinates
|
||||
aql_hub = """
|
||||
FOR hub IN nodes
|
||||
FILTER hub.node_type == 'logistics' OR hub.node_type == null
|
||||
FILTER hub.product_uuid == null
|
||||
FILTER hub.latitude != null AND hub.longitude != null
|
||||
LET dist = DISTANCE(hub.latitude, hub.longitude, @lat, @lon) / 1000
|
||||
SORT dist ASC
|
||||
LIMIT 1
|
||||
RETURN hub
|
||||
"""
|
||||
|
||||
try:
|
||||
cursor = db.aql.execute(aql_hub, bind_vars={'lat': lat, 'lon': lon})
|
||||
hubs = list(cursor)
|
||||
if not hubs:
|
||||
logger.info("No hub found near coordinates (%.3f, %.3f)", lat, lon)
|
||||
return None
|
||||
|
||||
nearest_hub = hubs[0]
|
||||
hub_uuid = nearest_hub['_key']
|
||||
logger.info("Found nearest hub %s to coordinates (%.3f, %.3f)", hub_uuid, lat, lon)
|
||||
|
||||
# Use existing offer_to_hub logic
|
||||
return self.resolve_offer_to_hub(info, offer_uuid, hub_uuid)
|
||||
except Exception as e:
|
||||
logger.error("Error finding route to coordinates: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user