DemoWalkAPI / app.py
npc0's picture
Update app.py
5bf53f5 verified
Raw
History Blame Contribute Delete
6.43 kB
import gradio as gr
import time
import json
import os
from collections import deque, defaultdict
import gspread
from oauth2client.service_account import ServiceAccountCredentials
# --- Configuration ---
MAX_RECENT_EVENTS = 1000 # Trigger snapshot compression at this count
GRID_PRECISION = 4 # ~11 meters precision
RADIUS_BASE = 20
GOOGLE_SHEET_NAME = "UrbanScout_Data" # Name of your Google Sheet
GOOGLE_CREDS_FILE = "credentials.json" # Path to your Service Account JSON
# --- Data Structures ---
# 1. Movement Data (Fog of War)
recent_events = deque()
archived_grid = defaultdict(int)
# 2. Static Markers (The Rental Spaces)
# We store these in memory for fast serving to map,
# and also send them to Google Sheets for persistence/analysis.
reported_markers = []
# 3. Version Control
snapshot_version = 0
last_snapshot_time = time.time()
# --- Google Sheets Setup ---
def init_google_sheets():
try:
scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
# You need to generate this JSON from Google Cloud Console
creds = ServiceAccountCredentials.from_json_keyfile_name(GOOGLE_CREDS_FILE, scope)
client = gspread.authorize(creds)
sheet = client.open(GOOGLE_SHEET_NAME).sheet1
return sheet
except Exception as e:
print(f"[Warning] Google Sheets not connected: {e}")
return None
# Initialize Sheet (Global)
gsheet = init_google_sheets()
# --- Helper Functions ---
def get_radius_by_speed(speed_kmh):
if speed_kmh is None: return 10
if speed_kmh < 6: return 35 # Walking
if speed_kmh < 25: return 15 # Biking
return 8 # Driving
def trigger_snapshot():
"""Compress recent movement events."""
global recent_events, snapshot_version, last_snapshot_time
if len(recent_events) > MAX_RECENT_EVENTS:
events_to_archive = []
count_to_remove = int(MAX_RECENT_EVENTS / 2)
for _ in range(count_to_remove):
events_to_archive.append(recent_events.popleft())
for event in events_to_archive:
lat, lng = event['l']
radius = event['r']
grid_key = (round(lat, GRID_PRECISION), round(lng, GRID_PRECISION))
if radius > archived_grid[grid_key]:
archived_grid[grid_key] = radius
snapshot_version += 1
print(f"[Server] Snapshot v{snapshot_version}")
# --- API Endpoints ---
def sync_game_state(client_last_time, client_ver, user_id, lat, lng, speed):
"""
Handles Fog of War updates AND returns reported markers.
"""
current_time = time.time()
# 1. Record Movement
if lat is not None and lng is not None:
if speed is None or speed < 150:
radius = get_radius_by_speed(speed)
new_event = {
"t": current_time,
"u": user_id,
"l": [lat, lng],
"r": radius
}
recent_events.append(new_event)
trigger_snapshot()
# 2. Prepare Movement Payload
response = {
"server_time": current_time,
"snapshot_version": snapshot_version,
"sync_type": "delta",
"payload": [],
"markers": reported_markers # Send the pins to the client
}
if client_ver < snapshot_version:
response["sync_type"] = "snapshot"
response["payload"] = [[k[0], k[1], v] for k, v in archived_grid.items()]
response["recent_events"] = [[e['l'][0], e['l'][1], e['r']] for e in recent_events]
else:
delta = [[e['l'][0], e['l'][1], e['r']] for e in recent_events if e['t'] > client_last_time]
response["payload"] = delta
return response
def report_discovery(report_json):
"""
New Endpoint: Receives the form data from the Report Modal.
"""
try:
data = report_json if isinstance(report_json, dict) else json.loads(report_json)
# 1. Add to In-Memory list (so other users see it immediately on map)
marker_data = {
"lat": data.get("lat"),
"lng": data.get("lng"),
"name": data.get("spaceName"),
"type": "rental",
"info": data.get("additionalInfo"),
"price": data.get("price"),
"cap": data.get("capacity"),
"addr": data.get("address"),
"book": data.get("bookingMethod")
}
reported_markers.append(marker_data)
# 2. Send to Google Sheets (Async/Best Effort)
if gsheet:
row = [
data.get("timestamp"),
data.get("lat"),
data.get("lng"),
data.get("address"),
data.get("spaceName"),
data.get("rentableCount"),
data.get("capacity"),
data.get("price"),
data.get("bookingMethod"),
data.get("additionalInfo")
]
gsheet.append_row(row)
print(f"[Sheet] Logged: {data.get('spaceName')}")
return {"status": "success", "message": "Discovery logged"}
except Exception as e:
print(f"Error reporting: {e}")
return {"status": "error", "message": str(e)}
# --- Gradio App ---
with gr.Blocks() as demo:
gr.Markdown("## City Explorer Backend")
# Hidden inputs for the Game Loop
with gr.Row(visible=False):
in_time = gr.Number()
in_ver = gr.Number()
in_uid = gr.Textbox()
in_lat = gr.Number()
in_lng = gr.Number()
in_speed = gr.Number()
out_sync_json = gr.JSON()
# Endpoint 1: The fast sync loop
btn_sync = gr.Button("Sync Loop (Hidden)")
btn_sync.click(
fn=sync_game_state,
inputs=[in_time, in_ver, in_uid, in_lat, in_lng, in_speed],
outputs=out_sync_json,
api_name="sync"
)
# Endpoint 2: The report form submission
with gr.Row(visible=False):
in_report_json = gr.JSON()
out_report_status = gr.JSON()
btn_report = gr.Button("Report Discovery (Hidden)")
btn_report.click(
fn=report_discovery,
inputs=[in_report_json],
outputs=out_report_status,
api_name="report"
)
if __name__ == "__main__":
demo.queue().launch(server_name="0.0.0.0", server_port=7860)