From de53b760c43c417fdb9f9d42a898f31c65a7efa2 Mon Sep 17 00:00:00 2001 From: Matteo Rosati Date: Sun, 18 Jan 2026 11:41:55 +0100 Subject: [PATCH] add center and radius params --- README.md | 75 ++++++++++++++++++++++++++++++++++++ app.py | 113 ++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 177 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b68e245..63af9fc 100644 --- a/README.md +++ b/README.md @@ -401,6 +401,81 @@ curl "http://localhost:7000/plexts?min_timestamp=2026-01-12T10:00:00Z&max_timest curl "http://localhost:7000/plexts?event_types=PORTAL_CAPTURED&player_name=Albicocca&min_timestamp=2026-01-12T10:00:00Z" ``` +##### GET /plexts/from-db + +Get plexts from MongoDB with optional filters. Returns plexts sorted by timestamp (most recent first). + +**Query Parameters:** + +| Parameter | Type | Default | Description | +| --------------- | ------- | -------- | --------------------------------------------------- | +| `player_name` | String | None | Player name to filter by | +| `timestamp_from` | Integer | None | Minimum timestamp in milliseconds (optional) | +| `timestamp_to` | Integer | None | Maximum timestamp in milliseconds (optional) | +| `limit` | Integer | 100 | Maximum number of results to return | +| `center` | String | None | Geographic center point in format "latitude,longitude" (optional, requires radius) | +| `radius` | Integer | None | Search radius in meters (optional, requires center) | + +**Geospatial Query:** + +When both `center` and `radius` parameters are provided, the endpoint returns plexts within the specified radius from the center point. The response includes a `distance` field (in meters) for each result. + +**Response with geospatial query:** +```json +{ + "count": 5, + "plexts": [ + { + "id": "abc123", + "timestamp": 1736659207000, + "timestamp_formatted": "2026-01-12 11:00:07", + "text": "Albicocca captured L' Arboreto", + "team": "RESISTANCE", + "plext_type": "PLAYER_GENERATED", + "categories": 0, + "event_type": "PORTAL_CAPTURED", + "player_name": "Albicocca", + "portal_name": "L' Arboreto", + "coordinates": { + "type": "Point", + "coordinates": [12.365208, 45.573661] + }, + "distance": 125.5, + "markup": [...] + } + ] +} +``` + +**Note:** Both `center` and `radius` parameters must be provided together. If neither is provided, the endpoint returns all plexts matching other filters without geospatial filtering. + +**Examples:** + +```bash +# Get all plexts from MongoDB +curl "http://localhost:7000/plexts/from-db" + +# Filter by player name +curl "http://localhost:7000/plexts/from-db?player_name=Albicocca" + +# Filter by time range +curl "http://localhost:7000/plexts/from-db?timestamp_from=17366592000000×tamp_to=1736745600000" + +# Geospatial query: Get plexts within 1km of a specific location +curl "http://localhost:7000/plexts/from-db?center=45.573661,12.365208&radius=1000" + +# Geospatial query with player filter +curl "http://localhost:7000/plexts/from-db?center=45.573661,12.365208&radius=1000&player_name=Albicocca" + +# Geospatial query with time filter +curl "http://localhost:7000/plexts/from-db?center=45.573661,12.365208&radius=1000×tamp_from=17366592000000" + +# Combine all filters +curl "http://localhost:7000/plexts/from-db?center=45.573661,12.365208&radius=1000&player_name=Albicocca×tamp_from=17366592000000&limit=10" +``` + +**Authentication:** Basic Auth required + ### Scheduler The scheduler automatically collects Ingress events every minute and stores them in MongoDB. diff --git a/app.py b/app.py index 75f16be..8b3f0ac 100644 --- a/app.py +++ b/app.py @@ -32,9 +32,12 @@ def get_plexts_from_db(): timestamp_from: Minimum timestamp in milliseconds (optional) timestamp_to: Maximum timestamp in milliseconds (optional) limit: Maximum number of results to return (optional, default: 100) + center: Geographic center point in format "latitude,longitude" (optional, requires radius) + radius: Search radius in meters (optional, requires center) Returns: - JSON response with list of plexts (without _id field), limited by the limit parameter + JSON response with list of plexts (without _id field), limited by the limit parameter. + When using geospatial query (center and radius), includes a 'distance' field in meters. """ try: # Parse query parameters @@ -42,6 +45,8 @@ def get_plexts_from_db(): timestamp_from = request.args.get("timestamp_from") timestamp_to = request.args.get("timestamp_to") limit_param = request.args.get("limit") + center = request.args.get("center") + radius = request.args.get("radius") # Validate and convert timestamp parameters to integers if provided if timestamp_from is not None: @@ -67,6 +72,44 @@ def get_plexts_from_db(): else: limit = 100 + # Validate geospatial parameters + # Both center and radius must be provided together, or neither + if (center is not None) != (radius is not None): + return jsonify({ + "error": "Both 'center' and 'radius' parameters must be provided together" + }), 400 + + # Parse and validate center parameter if provided + center_coords = None + if center is not None: + try: + # Parse center in format "latitude,longitude" + lat_str, lng_str = center.split(',') + lat = float(lat_str) + lng = float(lng_str) + + # Validate latitude and longitude ranges + if not (-90 <= lat <= 90): + return jsonify({"error": "Latitude must be between -90 and 90"}), 400 + if not (-180 <= lng <= 180): + return jsonify({"error": "Longitude must be between -180 and 180"}), 400 + + center_coords = (lat, lng) + except ValueError: + return jsonify({ + "error": "Invalid center format. Use format: 'latitude,longitude' (e.g., '45.573661,12.365208')" + }), 400 + + # Parse and validate radius parameter if provided + radius_meters = None + if radius is not None: + try: + radius_meters = int(radius) + if radius_meters <= 0: + return jsonify({"error": "Radius must be a positive integer"}), 400 + except ValueError: + return jsonify({"error": "Radius must be an integer"}), 400 + # Build MongoDB filter query filter_query = {} @@ -91,18 +134,59 @@ def get_plexts_from_db(): collection = db[collection_name] try: - # Projection to exclude _id field - projection = {"_id": 0} + # Ensure geospatial index exists + try: + collection.create_index([("coordinates", "2dsphere")], background=True) + except PyMongoError as e: + logger.warning(f"Could not create geospatial index: {e}") - # Execute query with sorting (timestamp DESC - most recent first) and dynamic limit - cursor = ( - collection.find(filter=filter_query, projection=projection) - .sort("timestamp", -1) - .limit(limit) - ) + # Execute query based on whether geospatial parameters are provided + if center_coords and radius_meters: + # Use $geoNear aggregation for geospatial query + lat, lng = center_coords + pipeline = [ + { + "$geoNear": { + "near": { + "type": "Point", + "coordinates": [lng, lat] # GeoJSON uses [longitude, latitude] + }, + "distanceField": "distance", # Add distance to results + "maxDistance": radius_meters, # in meters + "spherical": True # Use spherical geometry + } + }, + { + "$match": filter_query # Apply other filters + }, + { + "$sort": {"timestamp": -1} # Sort by timestamp (most recent first) + }, + { + "$limit": limit + }, + { + "$project": { + "_id": 0 # Exclude _id field + } + } + ] - # Convert cursor to list - plexts = list(cursor) + # Execute aggregation pipeline + plexts = list(collection.aggregate(pipeline)) + else: + # Use standard find() query for non-geospatial queries + projection = {"_id": 0} + + # Execute query with sorting (timestamp DESC - most recent first) and dynamic limit + cursor = ( + collection.find(filter=filter_query, projection=projection) + .sort("timestamp", -1) + .limit(limit) + ) + + # Convert cursor to list + plexts = list(cursor) return jsonify({"count": len(plexts), "plexts": plexts}), 200 @@ -237,8 +321,15 @@ def index(): "timestamp_from": "Minimum timestamp in milliseconds (optional)", "timestamp_to": "Maximum timestamp in milliseconds (optional)", "limit": "Maximum number of results to return (optional, default: 100)", + "center": "Geographic center point in format 'latitude,longitude' (optional, requires radius)", + "radius": "Search radius in meters (optional, requires center)", }, "authentication": "Basic Auth required", + "geospatial_query": { + "description": "When both center and radius are provided, returns plexts within the specified radius from the center point. If neither is provided, returns all plexts matching other filters.", + "example": "center=45.573661,12.365208&radius=1000", + "response_field": "distance (meters) - included in results when using geospatial query", + }, }, }, }