feat: v0.4 — satellite imagery, KiwiSDR radio, LOCATE bar & security cleanup

New features:
- NASA GIBS (MODIS Terra) daily satellite imagery with 30-day time slider
- Esri World Imagery high-res satellite layer (sub-meter, zoom 18+)
- KiwiSDR SDR receivers on map with embedded radio tuner
- Sentinel-2 intel card — right-click for recent satellite photo popup
- LOCATE bar — search by coordinates or place name (Nominatim geocoding)
- SATELLITE style preset in bottom bar cycling
- v0.4 changelog modal on first launch

Fixes:
- Satellite imagery renders below data icons (imagery-ceiling anchor)
- Sentinel-2 opens full-res PNG directly (not STAC catalog JSON)
- Light/dark theme: UI stays dark, only map basemap changes

Security:
- Removed test files with hardcoded API keys from tracking
- Removed .git_backup directory from tracking
- Updated .gitignore to exclude test files, dev scripts, cache files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
anoracleofra-code
2026-03-09 17:46:33 -06:00
parent 85748a6fea
commit e89e992293
294 changed files with 1348 additions and 2456 deletions
-112
View File
@@ -1,112 +0,0 @@
import zipfile
import xml.etree.ElementTree as ET
import re
import csv
import os
xlsx_path = r"f:\Codebase\Oracle\live-risk-dashboard\TheAirTraffic Database.xlsx"
output_path = r"f:\Codebase\Oracle\live-risk-dashboard\backend\xlsx_analysis.txt"
def parse_xlsx_sheet(z, shared_strings, sheet_num):
ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
sheet_file = f'xl/worksheets/sheet{sheet_num}.xml'
if sheet_file not in z.namelist():
return []
ws_xml = z.read(sheet_file)
ws_root = ET.fromstring(ws_xml)
rows = []
for row in ws_root.findall('.//s:sheetData/s:row', ns):
cells = {}
for cell in row.findall('s:c', ns):
cell_ref = cell.get('r', '')
cell_type = cell.get('t', '')
val_elem = cell.find('s:v', ns)
val = val_elem.text if val_elem is not None else ''
if cell_type == 's' and val:
val = shared_strings[int(val)]
col = re.match(r'([A-Z]+)', cell_ref).group(1) if re.match(r'([A-Z]+)', cell_ref) else ''
cells[col] = val
rows.append(cells)
return rows
with open(output_path, 'w', encoding='utf-8') as out:
with zipfile.ZipFile(xlsx_path, 'r') as z:
shared_strings = []
if 'xl/sharedStrings.xml' in z.namelist():
ss_xml = z.read('xl/sharedStrings.xml')
root = ET.fromstring(ss_xml)
ns = {'s': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
for si in root.findall('.//s:si', ns):
texts = si.findall('.//s:t', ns)
val = ''.join(t.text or '' for t in texts)
shared_strings.append(val)
all_entries = []
for sheet_idx in range(1, 5):
rows = parse_xlsx_sheet(z, shared_strings, sheet_idx)
if not rows:
continue
out.write(f"\n=== SHEET {sheet_idx}: {len(rows)} rows ===\n")
# Print first 5 rows
for i in range(min(5, len(rows))):
for col in sorted(rows[i].keys(), key=lambda x: (len(x), x)):
val = rows[i][col]
if val:
out.write(f" Row{i} {col}: '{val[:80]}'\n")
out.write("\n")
for r in rows[1:]:
for col, val in r.items():
val = str(val).strip()
n_regs = re.findall(r'N\d{1,5}[A-Z]{0,2}', val)
owner = r.get('B', r.get('A', '')).strip()
aircraft_type = r.get('C', r.get('D', '')).strip()
for reg in n_regs:
all_entries.append({
'registration': reg.upper(),
'owner': owner,
'type': aircraft_type,
'sheet': sheet_idx
})
unique_regs = set(e['registration'] for e in all_entries)
out.write(f"\nTOTAL ENTRIES: {len(all_entries)}\n")
out.write(f"UNIQUE REGISTRATIONS: {len(unique_regs)}\n")
csv_path = r"f:\Codebase\Oracle\live-risk-dashboard\PLANEALERTLIST\plane-alert-db-main\plane-alert-db.csv"
existing = {}
with open(csv_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
icao = row.get('$ICAO', '').strip().upper()
reg = row.get('$Registration', '').strip().upper()
if reg:
existing[reg] = {
'icao': icao,
'category': row.get('Category', ''),
'operator': row.get('$Operator', ''),
}
already_in = unique_regs & set(existing.keys())
missing = unique_regs - set(existing.keys())
out.write(f"\nplane-alert-db: {len(existing)} registrations\n")
out.write(f"Already covered: {len(already_in)}\n")
out.write(f"MISSING: {len(missing)}\n")
out.write(f"\n--- ALREADY TRACKED ---\n")
seen = set()
for e in all_entries:
if e['registration'] in already_in and e['registration'] not in seen:
info = existing[e['registration']]
out.write(f" {e['owner'][:40]:40s} {e['registration']:10s} DB_CAT: {info['category'][:25]:25s} DB_OP: {info['operator'][:40]}\n")
seen.add(e['registration'])
out.write(f"\n--- MISSING (NEED TO ADD) ---\n")
seen = set()
for e in all_entries:
if e['registration'] in missing and e['registration'] not in seen:
out.write(f" {e['owner'][:40]:40s} {e['registration']:10s} TYPE: {e['type'][:30]}\n")
seen.add(e['registration'])
print(f"Analysis written to {output_path}")
+9 -1
View File
@@ -92,7 +92,8 @@ async def live_data_slow(request: Request):
"frontlines": d.get("frontlines"),
"gdelt": d.get("gdelt", []),
"airports": d.get("airports", []),
"satellites": d.get("satellites", [])
"satellites": d.get("satellites", []),
"kiwisdr": d.get("kiwisdr", [])
}
# ETag based on last_updated + item counts
last_updated = d.get("last_updated", "")
@@ -187,6 +188,13 @@ def api_region_dossier(lat: float, lng: float):
"""Sync def so FastAPI runs it in a threadpool — prevents blocking the event loop."""
return get_region_dossier(lat, lng)
from services.sentinel_search import search_sentinel2_scene
@app.get("/api/sentinel2/search")
def api_sentinel2_search(lat: float, lng: float):
"""Search for latest Sentinel-2 imagery at a point. Sync for threadpool execution."""
return search_sentinel2_scene(lat, lng)
# ---------------------------------------------------------------------------
# API Settings — key registry & management
# ---------------------------------------------------------------------------
+1
View File
@@ -17,3 +17,4 @@ reverse_geocoder>=1.5
sgp4>=2.23
geopy>=2.4.0
pytz>=2023.3
pystac-client>=0.7.0
File diff suppressed because one or more lines are too long
+11 -1
View File
@@ -100,7 +100,8 @@ latest_data = {
"uavs": [],
"frontlines": None,
"gdelt": [],
"liveuamap": []
"liveuamap": [],
"kiwisdr": []
}
# Thread lock for safe reads/writes to latest_data
@@ -1250,6 +1251,14 @@ def fetch_cctv():
logger.error(f"Error fetching cctv from DB: {e}")
latest_data["cctv"] = []
def fetch_kiwisdr():
try:
from services.kiwisdr_fetcher import fetch_kiwisdr_nodes
latest_data["kiwisdr"] = fetch_kiwisdr_nodes()
except Exception as e:
logger.error(f"Error fetching KiwiSDR nodes: {e}")
latest_data["kiwisdr"] = []
def fetch_bikeshare():
bikes = []
try:
@@ -1805,6 +1814,7 @@ def update_slow_data():
fetch_cctv,
fetch_earthquakes,
fetch_geopolitics,
fetch_kiwisdr,
]
with concurrent.futures.ThreadPoolExecutor(max_workers=len(slow_funcs)) as executor:
futures = [executor.submit(func) for func in slow_funcs]
+97
View File
@@ -0,0 +1,97 @@
"""
KiwiSDR public receiver list fetcher.
Scrapes the kiwisdr.com public page for active SDR receivers worldwide.
Data is embedded as HTML comments inside each entry div.
"""
import re
import logging
from cachetools import TTLCache, cached
logger = logging.getLogger(__name__)
kiwisdr_cache = TTLCache(maxsize=1, ttl=600) # 10-minute cache
def _parse_comment(html: str, field: str) -> str:
"""Extract a field value from HTML comment like <!-- field=value -->"""
m = re.search(rf'<!--\s*{field}=(.*?)\s*-->', html)
return m.group(1).strip() if m else ""
def _parse_gps(html: str):
"""Extract lat/lon from <!-- gps=(lat, lon) --> comment."""
m = re.search(r'<!--\s*gps=\(([^,]+),\s*([^)]+)\)\s*-->', html)
if m:
try:
return float(m.group(1)), float(m.group(2))
except ValueError:
return None, None
return None, None
@cached(kiwisdr_cache)
def fetch_kiwisdr_nodes() -> list[dict]:
"""Fetch and parse the KiwiSDR public receiver list."""
from services.network_utils import smart_request
try:
res = smart_request("http://kiwisdr.com/.public/", timeout=20)
if not res or res.status_code != 200:
logger.error(f"KiwiSDR fetch failed: HTTP {res.status_code if res else 'no response'}")
return []
html = res.text
# Split by entry divs
entries = re.findall(r"<div class='cl-entry[^']*'>(.*?)</div>\s*</div>", html, re.DOTALL)
nodes = []
for entry in entries:
lat, lon = _parse_gps(entry)
if lat is None or lon is None:
continue
if abs(lat) > 90 or abs(lon) > 180:
continue
offline = _parse_comment(entry, "offline")
if offline == "yes":
continue
name = _parse_comment(entry, "name") or "Unknown SDR"
users_str = _parse_comment(entry, "users")
users_max_str = _parse_comment(entry, "users_max")
bands = _parse_comment(entry, "bands")
antenna = _parse_comment(entry, "antenna")
location = _parse_comment(entry, "loc")
# Extract the URL from the href
url_match = re.search(r"href='(https?://[^']+)'", entry)
url = url_match.group(1) if url_match else ""
try:
users = int(users_str) if users_str else 0
except ValueError:
users = 0
try:
users_max = int(users_max_str) if users_max_str else 0
except ValueError:
users_max = 0
nodes.append({
"name": name[:120], # Truncate long names
"lat": round(lat, 5),
"lon": round(lon, 5),
"url": url,
"users": users,
"users_max": users_max,
"bands": bands,
"antenna": antenna[:200] if antenna else "",
"location": location[:100] if location else "",
})
logger.info(f"KiwiSDR: parsed {len(nodes)} online receivers")
return nodes
except Exception as e:
logger.error(f"KiwiSDR fetch exception: {e}")
return []
+81
View File
@@ -0,0 +1,81 @@
"""
Sentinel-2 satellite imagery search via Microsoft Planetary Computer STAC API.
Free, keyless search for metadata + thumbnails. Used in the right-click dossier.
"""
import logging
from datetime import datetime, timedelta
from cachetools import TTLCache
logger = logging.getLogger(__name__)
# Cache by rounded lat/lon (0.02° grid ~= 2km), TTL 1 hour
_sentinel_cache = TTLCache(maxsize=200, ttl=3600)
def search_sentinel2_scene(lat: float, lng: float) -> dict:
"""Search for the latest Sentinel-2 L2A scene covering a point."""
cache_key = f"{round(lat, 2)}_{round(lng, 2)}"
if cache_key in _sentinel_cache:
return _sentinel_cache[cache_key]
try:
from pystac_client import Client
catalog = Client.open("https://planetarycomputer.microsoft.com/api/stac/v1")
end = datetime.utcnow()
start = end - timedelta(days=30)
search = catalog.search(
collections=["sentinel-2-l2a"],
intersects={"type": "Point", "coordinates": [lng, lat]},
datetime=f"{start.isoformat()}Z/{end.isoformat()}Z",
sortby=[{"field": "datetime", "direction": "desc"}],
max_items=3,
query={"eo:cloud_cover": {"lt": 30}},
)
items = list(search.items())
if not items:
result = {"found": False, "message": "No clear scenes in last 30 days"}
_sentinel_cache[cache_key] = result
return result
item = items[0]
# Try to sign item first for Azure blob URLs
try:
import planetary_computer
item = planetary_computer.sign_item(item)
except ImportError:
pass # planetary_computer not installed, try unsigned URLs
except Exception as e:
logger.warning(f"Sentinel-2 signing failed: {e}")
# Get the rendered_preview (full-res PNG) and thumbnail separately
rendered = item.assets.get("rendered_preview")
thumbnail = item.assets.get("thumbnail")
# Full-res image URL — what opens when user clicks
fullres_url = rendered.href if rendered else (thumbnail.href if thumbnail else None)
# Thumbnail URL — what shows in the popup card
thumb_url = thumbnail.href if thumbnail else (rendered.href if rendered else None)
result = {
"found": True,
"scene_id": item.id,
"datetime": item.datetime.isoformat() if item.datetime else None,
"cloud_cover": item.properties.get("eo:cloud_cover"),
"thumbnail_url": thumb_url,
"fullres_url": fullres_url,
"bbox": list(item.bbox) if item.bbox else None,
"platform": item.properties.get("platform", "Sentinel-2"),
}
_sentinel_cache[cache_key] = result
return result
except ImportError:
logger.warning("pystac-client not installed — Sentinel-2 search unavailable")
return {"found": False, "error": "pystac-client not installed"}
except Exception as e:
logger.error(f"Sentinel-2 search failed for ({lat}, {lng}): {e}")
return {"found": False, "error": str(e)}
-17
View File
@@ -1,17 +0,0 @@
import sys
import logging
logging.basicConfig(level=logging.DEBUG)
# Add backend directory to sys path so we can import modules
sys.path.append(r'f:\Codebase\Oracle\live-risk-dashboard\backend')
from services.data_fetcher import fetch_flights, latest_data
print("Testing fetch_flights...")
try:
fetch_flights()
print("Commercial flights count:", len(latest_data.get('commercial_flights', [])))
print("Private jets count:", len(latest_data.get('private_jets', [])))
except Exception as e:
import traceback
traceback.print_exc()
-38
View File
@@ -1,38 +0,0 @@
import json
from playwright.sync_api import sync_playwright
def scrape_liveuamap():
print("Launching playwright...")
with sync_playwright() as p:
# User agents are important for headless browsing
browser = p.chromium.launch(headless=True)
page = browser.new_page(user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
def handle_response(response):
try:
if not response.url.endswith(('js', 'css', 'png', 'jpg', 'woff2', 'svg', 'ico')):
print(f"Intercepted API Call: {response.url}")
except Exception:
pass
page.on("response", handle_response)
print("Navigating to liveuamap...")
try:
page.goto("https://liveuamap.com/", timeout=30000, wait_until="domcontentloaded")
page.wait_for_timeout(5000)
print("Grabbing all script tags...")
scripts = page.evaluate("() => Array.from(document.querySelectorAll('script')).map(s => s.innerText)")
for i, s in enumerate(scripts):
if 'JSON.parse' in s or 'markers' in s or 'JSON' in s:
with open(f"script_{i}.txt", "w", encoding="utf-8") as f:
f.write(s)
except Exception as e:
print("Playwright timeout or error:", e)
print("Closing browser...")
browser.close()
if __name__ == "__main__":
scrape_liveuamap()
-59
View File
@@ -1,59 +0,0 @@
import requests
import json
import time
import cloudscraper
def scrape_openmhz_systems():
print("Testing OpenMHZ undocumented API with Cloudscraper...")
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
}
scraper = cloudscraper.create_scraper(browser={'browser': 'chrome', 'platform': 'windows', 'desktop': True})
try:
# Step 1: Hit the public systems list that the front-end map uses
res = scraper.get("https://api.openmhz.com/systems", headers=headers, timeout=15)
json_data = res.json()
systems = json_data.get('systems', []) if isinstance(json_data, dict) else []
print(f"Successfully spoofed OpenMHZ frontend. Found {len(systems)} active police/fire systems.")
if not systems:
return
# Inspect the first system (usually a major city)
city = systems[0]
sys_name = city.get('shortName')
print(f"Targeting System: {city.get('name')} ({sys_name})")
if not sys_name:
return
time.sleep(2) # Ethical delay
# Step 2: Query the recent calls for this specific system
# The frontend queries: https://api.openmhz.com/<system_name>/calls
calls_url = f"https://api.openmhz.com/{sys_name}/calls"
print(f"Fetching recent bursts: {calls_url}")
call_res = scraper.get(calls_url, headers=headers, timeout=15)
if call_res.status_code == 200:
call_json = call_res.json()
calls = call_json.get('calls', []) if isinstance(call_json, dict) else []
if calls and len(calls) > 0:
print(f"Intercepted {len(calls)} audio bursts.")
latest = calls[0]
print("LATEST INTERCEPT:")
print(f"Talkgroup: {latest.get('talkgroupNum')}")
print(f"Audio URL: {latest.get('url')}")
else:
print("No recent calls found for this system.")
else:
print(f"Failed to fetch calls. HTTP {call_res.status_code}")
except Exception as e:
print(f"Scrape Exception: {e}")
if __name__ == "__main__":
scrape_openmhz_systems()
-19
View File
@@ -1,19 +0,0 @@
import requests
def test_openmhz():
print("Testing OpenMHZ...")
res = requests.get("https://api.openmhz.com/systems")
if res.status_code == 200:
data = res.json()
print(f"OpenMHZ returned {len(data)} systems.")
print(f"Example: {data[0]['name']} ({data[0]['shortName']})")
else:
print(f"OpenMHZ Failed: {res.status_code}")
def test_scanner_radio():
print("Testing Scanner Radio...")
# Gordon Edwards app often uses something like this
# We will just try broadcastify public page scrape as a secondary fallback
pass
test_openmhz()
-55
View File
@@ -1,55 +0,0 @@
import feedparser
import requests
import re
feeds = {
"NPR": "https://feeds.npr.org/1004/rss.xml",
"BBC": "http://feeds.bbci.co.uk/news/world/rss.xml"
}
keyword_coords = {
"venezuela": (7.119, -66.589), "brazil": (-14.235, -51.925), "argentina": (-38.416, -63.616),
"colombia": (4.570, -74.297), "mexico": (23.634, -102.552), "united states": (38.907, -77.036),
" usa ": (38.907, -77.036), " us ": (38.907, -77.036), "washington": (38.907, -77.036),
"canada": (56.130, -106.346), "ukraine": (49.487, 31.272), "kyiv": (50.450, 30.523),
"russia": (61.524, 105.318), "moscow": (55.755, 37.617), "israel": (31.046, 34.851),
"gaza": (31.416, 34.333), "iran": (32.427, 53.688), "lebanon": (33.854, 35.862),
"syria": (34.802, 38.996), "yemen": (15.552, 48.516), "china": (35.861, 104.195),
"beijing": (39.904, 116.407), "taiwan": (23.697, 120.960), "north korea": (40.339, 127.510),
"south korea": (35.907, 127.766), "pyongyang": (39.039, 125.762), "seoul": (37.566, 126.978),
"japan": (36.204, 138.252), "afghanistan": (33.939, 67.709), "pakistan": (30.375, 69.345),
"india": (20.593, 78.962), " uk ": (55.378, -3.435), "london": (51.507, -0.127),
"france": (46.227, 2.213), "paris": (48.856, 2.352), "germany": (51.165, 10.451),
"berlin": (52.520, 13.405), "sudan": (12.862, 30.217), "congo": (-4.038, 21.758),
"south africa": (-30.559, 22.937), "nigeria": (9.082, 8.675), "egypt": (26.820, 30.802),
"zimbabwe": (-19.015, 29.154), "australia": (-25.274, 133.775), "middle east": (31.500, 34.800),
"europe": (48.800, 2.300), "africa": (0.000, 25.000), "america": (38.900, -77.000),
"south america": (-14.200, -51.900), "asia": (34.000, 100.000),
"california": (36.778, -119.417), "texas": (31.968, -99.901), "florida": (27.994, -81.760),
"new york": (40.712, -74.006), "virginia": (37.431, -78.656),
"british columbia": (53.726, -127.647), "ontario": (51.253, -85.323), "quebec": (52.939, -73.549),
"delhi": (28.704, 77.102), "new delhi": (28.613, 77.209), "mumbai": (19.076, 72.877),
"shanghai": (31.230, 121.473), "hong kong": (22.319, 114.169), "istanbul": (41.008, 28.978),
"dubai": (25.204, 55.270), "singapore": (1.352, 103.819)
}
for name, url in feeds.items():
r = requests.get(url)
feed = feedparser.parse(r.text)
for entry in feed.entries[:10]:
title = entry.get('title', '')
summary = entry.get('summary', '')
text = (title + " " + summary).lower()
padded_text = f" {text} "
matched_kw = None
for kw, coords in keyword_coords.items():
if kw.startswith(" ") or kw.endswith(" "):
if kw in padded_text:
matched_kw = kw
break
else:
if re.search(r'\b' + re.escape(kw) + r'\b', text):
matched_kw = kw
break
print(f"[{name}] {title}\n Matched: {matched_kw}\n Text: {text}\n")
-67
View File
@@ -1,67 +0,0 @@
import requests
from bs4 import BeautifulSoup
import json
def scrape_broadcastify_top():
print("Scraping Broadcastify Top Feeds...")
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
try:
# The top 50 feeds page provides a wealth of listening data
res = requests.get("https://www.broadcastify.com/listen/top", headers=headers, timeout=10)
if res.status_code != 200:
print(f"Failed HTTP {res.status_code}")
return []
soup = BeautifulSoup(res.text, 'html.parser')
# The table of feeds is in a standard class
table = soup.find('table', {'class': 'btable'})
if not table:
print("Could not find feeds table.")
return []
feeds = []
rows = table.find_all('tr')[1:] # Skip header
for row in rows:
cols = row.find_all('td')
if len(cols) >= 5:
# Top layout: [Listeners, Feed ID (hidden), Location, Feed Name, Category, Genre]
listeners_str = cols[0].text.strip().replace(',', '')
listeners = int(listeners_str) if listeners_str.isdigit() else 0
# The link is usually in the Feed Name column
link_tag = cols[2].find('a')
if not link_tag:
continue
href = link_tag.get('href', '')
feed_id = href.split('/')[-1] if '/listen/feed/' in href else None
if not feed_id:
continue
location = cols[1].text.strip()
name = cols[2].text.strip()
feeds.append({
"id": feed_id,
"listeners": listeners,
"location": location,
"name": name,
"stream_url": f"https://broadcastify.cdnstream1.com/{feed_id}"
})
print(f"Successfully scraped {len(feeds)} top feeds.")
return feeds
except Exception as e:
print(f"Scrape error: {e}")
return []
if __name__ == "__main__":
top_feeds = scrape_broadcastify_top()
print(json.dumps(top_feeds[:3], indent=2))
-59
View File
@@ -1,59 +0,0 @@
import requests
import time
import math
import random
def test_fetch_and_triangulate():
t0 = time.time()
url = "https://api.adsb.lol/v2/lat/39.8/lon/-98.5/dist/1000"
try:
r = requests.get(url, timeout=10)
data = r.json()
print(f"Downloaded in {time.time() - t0:.2f}s")
if "ac" in data:
sampled = data["ac"]
print("Flights:", len(sampled))
else:
print("No 'ac' in response:", data)
# Load airports (mock for test)
airports = [{"lat": random.uniform(-90, 90), "lng": random.uniform(-180, 180), "iata": f"A{i}"} for i in range(4000)]
t1 = time.time()
for f in sampled:
lat = f.get("lat")
lng = f.get("lon")
heading = f.get("track", 0)
if lat is None or lng is None: continue
# Project 15 degrees (~1000 miles) backwards and forwards
dist_deg = 15.0
h_rad = math.radians(heading)
dy = math.cos(h_rad) * dist_deg
dx = math.sin(h_rad) * dist_deg
cos_lat = max(0.2, math.cos(math.radians(lat)))
origin_lat = lat - dy
origin_lng = lng - (dx / cos_lat)
dest_lat = lat + dy
dest_lng = lng + (dx / cos_lat)
# Find closest origin airport
best_o, min_o = None, float('inf')
for a in airports:
d = (a['lat'] - origin_lat)**2 + (a['lng'] - origin_lng)**2
if d < min_o: min_o = d; best_o = a
# Find closest dest airport
best_d, min_d = None, float('inf')
for a in airports:
d = (a['lat'] - dest_lat)**2 + (a['lng'] - dest_lng)**2
if d < min_d: min_d = d; best_d = a
print(f"Triangulated 500 flights against {len(airports)} airports in {time.time() - t1:.2f}s")
except Exception as e:
print("Error:", e)
test_fetch_and_triangulate()
-13
View File
@@ -1,13 +0,0 @@
from services.data_fetcher import fetch_airports, fetch_flights, cached_airports, latest_data
fetch_airports()
# We patch logger to see what happens inside fetch_flights
import logging
logging.basicConfig(level=logging.DEBUG)
# let's run fetch_flights
fetch_flights()
flights = latest_data.get('flights', [])
print(f"Total flights: {len(flights)}")
-45
View File
@@ -1,45 +0,0 @@
import json
import subprocess
import os
import time
proxy_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ais_proxy.js")
API_KEY = "75cc39af03c9cc23c90e8a7b3c3bc2b2a507c5fb"
print(f"Proxy script: {proxy_script}")
print(f"Exists: {os.path.exists(proxy_script)}")
process = subprocess.Popen(
['node', proxy_script, API_KEY],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE, # Separate stderr!
text=True,
bufsize=1
)
print("Process started, reading stdout...")
count = 0
start = time.time()
for line in iter(process.stdout.readline, ''):
line = line.strip()
if not line:
continue
try:
data = json.loads(line)
msg_type = data.get("MessageType", "?")
mmsi = data.get("MetaData", {}).get("MMSI", 0)
count += 1
if count <= 5:
print(f" MSG {count}: type={msg_type} mmsi={mmsi}")
if count == 20:
elapsed = time.time() - start
print(f"\nReceived {count} messages in {elapsed:.1f}s — proxy is working!")
process.terminate()
break
except json.JSONDecodeError as e:
print(f" BAD JSON: {line[:100]}... err={e}")
if count == 0:
# Check stderr
stderr_out = process.stderr.read()
print(f"Zero messages received. stderr: {stderr_out[:500]}")
-54
View File
@@ -1,54 +0,0 @@
import json
import subprocess
import os
import time
import sys
proxy_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ais_proxy.js")
API_KEY = "75cc39af03c9cc23c90e8a7b3c3bc2b2a507c5fb"
print(f"Proxy script: {proxy_script}")
process = subprocess.Popen(
['node', proxy_script, API_KEY],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
import threading
def read_stderr():
for line in iter(process.stderr.readline, ''):
print(f"[STDERR] {line.strip()}", file=sys.stderr)
t = threading.Thread(target=read_stderr, daemon=True)
t.start()
print("Process started, reading stdout for 15 seconds...")
count = 0
start = time.time()
while time.time() - start < 15:
line = process.stdout.readline()
if not line:
if process.poll() is not None:
print(f"Process exited with code {process.returncode}")
break
continue
line = line.strip()
if not line:
continue
try:
data = json.loads(line)
msg_type = data.get("MessageType", "?")
mmsi = data.get("MetaData", {}).get("MMSI", 0)
count += 1
if count <= 5:
print(f" MSG {count}: type={msg_type} mmsi={mmsi}")
except json.JSONDecodeError as e:
print(f" BAD LINE: {line[:80]}...")
elapsed = time.time() - start
print(f"\nTotal {count} messages in {elapsed:.1f}s")
process.terminate()
-13
View File
@@ -1,13 +0,0 @@
import requests
import traceback
try:
print("Testing adsb.lol...")
r = requests.get("https://api.adsb.lol/v2/lat/39.8/lon/-98.5/dist/100", timeout=15)
print(f"Status: {r.status_code}")
d = r.json()
print(f"Aircraft: {len(d.get('ac', []))}")
except Exception as e:
print(f"Error type: {type(e).__name__}")
print(f"Error: {e}")
traceback.print_exc()
-11
View File
@@ -1,11 +0,0 @@
import json
import urllib.request
import time
time.sleep(5)
try:
data = urllib.request.urlopen('http://localhost:8000/api/live-data').read()
d = json.loads(data)
print(f"News: {len(d.get('news', []))} | Earthquakes: {len(d.get('earthquakes', []))} | Satellites: {len(d.get('satellites', []))} | CCTV: {len(d.get('cctv', []))}")
except Exception as e:
print(f"Error fetching API: {e}")
-56
View File
@@ -1,56 +0,0 @@
import requests
import json
# Step 1: Fetch some real flights from adsb.lol
print("Fetching real flights from adsb.lol...")
r = requests.get("https://api.adsb.lol/v2/lat/39.8/lon/-98.5/dist/250", timeout=10)
data = r.json()
ac = data.get("ac", [])
print("Got", len(ac), "aircraft")
# Step 2: Build a batch of real callsigns
planes = []
for f in ac[:20]: # Just 20 real flights
cs = str(f.get("flight", "")).strip()
lat = f.get("lat")
lon = f.get("lon")
if cs and lat and lon:
planes.append({"callsign": cs, "lat": lat, "lng": lon})
print("Built batch of", len(planes), "planes")
print("Sample plane:", json.dumps(planes[0]) if planes else "NONE")
# Step 3: Test routeset with real data
if planes:
payload = {"planes": planes}
print("Payload size:", len(json.dumps(payload)), "bytes")
r2 = requests.post("https://api.adsb.lol/api/0/routeset", json=payload, timeout=15)
print("Routeset HTTP:", r2.status_code)
if r2.status_code == 200:
result = r2.json()
print("Response type:", type(result).__name__)
print("Routes found:", len(result) if isinstance(result, list) else "dict")
if isinstance(result, list) and len(result) > 0:
print("First route:", json.dumps(result[0], indent=2))
else:
print("Error body:", r2.text[:500])
# Step 4: Test with bigger batch
print("\n--- Testing with 100 real flights ---")
planes100 = []
for f in ac[:120]:
cs = str(f.get("flight", "")).strip()
lat = f.get("lat")
lon = f.get("lon")
if cs and lat and lon:
planes100.append({"callsign": cs, "lat": lat, "lng": lon})
planes100 = planes100[:100]
print("Built batch of", len(planes100), "planes")
r3 = requests.post("https://api.adsb.lol/api/0/routeset", json={"planes": planes100}, timeout=15)
print("Routeset HTTP:", r3.status_code)
if r3.status_code == 200:
result3 = r3.json()
print("Routes found:", len(result3) if isinstance(result3, list) else "dict")
else:
print("Error body:", r3.text[:500])
-10
View File
@@ -1,10 +0,0 @@
from services.cctv_pipeline import init_db, TFLJamCamIngestor, LTASingaporeIngestor
init_db()
print("Initialized DB")
tfl = TFLJamCamIngestor()
print(f"TFL Cameras: {len(tfl.fetch_data())}")
nyc = LTASingaporeIngestor()
print(f"SGP Cameras: {len(nyc.fetch_data())}")
-24
View File
@@ -1,24 +0,0 @@
import requests
try:
print('Testing Seattle SDOT...')
r_sea = requests.get('https://data.seattle.gov/resource/65fc-btcc.json?$limit=5', headers={'X-App-Token': 'f2jdDBw5JMXPFOQyk64SKlPkn'})
print(r_sea.status_code)
try:
print(r_sea.json()[0])
except:
pass
except:
pass
try:
print('Testing NYC 511...')
r_nyc = requests.get('https://webcams.nyctmc.org/api/cameras', timeout=5)
print(r_nyc.status_code)
try:
print(len(r_nyc.json()))
print(r_nyc.json()[0])
except:
pass
except:
pass
-10
View File
@@ -1,10 +0,0 @@
import json, urllib.request
data = json.loads(urllib.request.urlopen('http://localhost:8000/api/live-data').read())
print(f"Commercial flights: {len(data.get('commercial_flights', []))}")
print(f"Private flights: {len(data.get('private_flights', []))}")
print(f"Private jets: {len(data.get('private_jets', []))}")
print(f"Military flights: {len(data.get('military_flights', []))}")
print(f"Tracked flights: {len(data.get('tracked_flights', []))}")
print(f"Ships: {len(data.get('ships', []))}")
print(f"CCTV: {len(data.get('cctv', []))}")
-38
View File
@@ -1,38 +0,0 @@
import json
import urllib.request
try:
data = json.loads(urllib.request.urlopen('http://localhost:8000/api/live-data').read())
# Tracked flights
tracked = data.get('tracked_flights', [])
print(f"=== TRACKED FLIGHTS: {len(tracked)} ===")
if tracked:
colors = {}
for t in tracked:
c = t.get('alert_color', 'NONE')
colors[c] = colors.get(c, 0) + 1
print(f" Colors: {colors}")
print(f" Sample: {json.dumps(tracked[0], indent=2)[:500]}")
# Ships
ships = data.get('ships', [])
print(f"\n=== SHIPS: {len(ships)} ===")
types = {}
for s in ships:
t = s.get('type', 'unknown')
types[t] = types.get(t, 0) + 1
print(f" Types: {types}")
if ships:
print(f" Sample: {json.dumps(ships[0], indent=2)[:300]}")
# News
news = data.get('news', [])
print(f"\n=== NEWS: {len(news)} ===")
# Earthquakes
quakes = data.get('earthquakes', [])
print(f"=== EARTHQUAKES: {len(quakes)} ===")
except Exception as e:
print(f"Error: {e}")
-23
View File
@@ -1,23 +0,0 @@
import json
import urllib.request
try:
data = json.loads(urllib.request.urlopen('http://localhost:8000/api/live-data').read())
tracked = data.get('tracked_flights', [])
colors = {}
for t in tracked:
c = t.get('alert_color', 'NONE')
colors[c] = colors.get(c, 0) + 1
print(f"TRACKED FLIGHTS: {len(tracked)} | Colors: {colors}")
ships = data.get('ships', [])
types = {}
for s in ships:
t = s.get('type', 'unknown')
types[t] = types.get(t, 0) + 1
print(f"SHIPS: {len(ships)} | Types: {types}")
print(f"NEWS: {len(data.get('news', []))} | EARTHQUAKES: {len(data.get('earthquakes', []))} | CCTV: {len(data.get('cctv', []))}")
except Exception as e:
print(f"Error: {e}")
-10
View File
@@ -1,10 +0,0 @@
import requests, json
url = "https://api.us.socrata.com/api/catalog/v1?domains=data.cityofnewyork.us&q=camera"
try:
r = requests.get(url)
res = r.json().get('results', [])
for d in res:
print(f"{d['resource']['id']} - {d['resource']['name']}")
except Exception as e:
print(e)
-36
View File
@@ -1,36 +0,0 @@
import json, urllib.request
data = json.loads(urllib.request.urlopen('http://localhost:8000/api/live-data').read())
# Check trail data
comm = data.get('commercial_flights', [])
mil = data.get('military_flights', [])
tracked = data.get('tracked_flights', [])
pvt = data.get('private_flights', [])
# Count flights with trails
comm_trails = [f for f in comm if f.get('trail') and len(f['trail']) > 0]
mil_trails = [f for f in mil if f.get('trail') and len(f['trail']) > 0]
tracked_trails = [f for f in tracked if f.get('trail') and len(f['trail']) > 0]
pvt_trails = [f for f in pvt if f.get('trail') and len(f['trail']) > 0]
print(f"Commercial: {len(comm)} total, {len(comm_trails)} with trails")
print(f"Military: {len(mil)} total, {len(mil_trails)} with trails")
print(f"Tracked: {len(tracked)} total, {len(tracked_trails)} with trails")
print(f"Private: {len(pvt)} total, {len(pvt_trails)} with trails")
# Show a sample trail
if mil_trails:
f = mil_trails[0]
print(f"\nSample trail ({f['callsign']}):")
print(f" Points: {len(f['trail'])}")
if f['trail']:
print(f" First: {f['trail'][0]}")
print(f" Last: {f['trail'][-1]}")
# Check for grounded planes
grounded = [f for f in comm if f.get('alt', 999) <= 500 and f.get('speed_knots', 999) < 30]
print(f"\nGrounded commercial: {len(grounded)}")
if grounded:
g = grounded[0]
print(f" Example: {g['callsign']} alt={g.get('alt')} speed={g.get('speed_knots')}")
-13
View File
@@ -1,13 +0,0 @@
import sqlite3
try:
conn = sqlite3.connect('cctv.db')
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute("SELECT source_agency, COUNT(*) as count FROM cameras WHERE id LIKE 'OSM-%' GROUP BY source_agency")
rows = cur.fetchall()
print('OSM Cameras by City:')
for r in rows:
print(f"{r['source_agency']}: {r['count']}")
except Exception as e:
print('DB Error:', e)
-12
View File
@@ -1,12 +0,0 @@
import json
import urllib.request
import time
time.sleep(5)
try:
data = urllib.request.urlopen('http://localhost:8000/api/live-data').read()
d = json.loads(data)
ships = d.get('ships', [])
print(f"Ships: {len(ships)}")
except Exception as e:
print(f"Error fetching API: {e}")
-13
View File
@@ -1,13 +0,0 @@
import requests, json
print("Searching Socrata NYC/Seattle Cameras...")
try:
url = "https://api.us.socrata.com/api/catalog/v1?q=traffic cameras&limit=100"
r = requests.get(url)
res = r.json().get('results', [])
for d in res:
domain = d['metadata']['domain'].lower()
if 'seattle' in domain or 'newyork' in domain or 'nyc' in domain:
print(f"{d['resource']['id']} - {d['resource']['name']} ({domain})")
except Exception as e:
print(e)
-61
View File
@@ -1,61 +0,0 @@
"""Test trace endpoints with explicit output."""
import json, subprocess
hex_code = "a34bac" # DOJ166
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
date_str = now.strftime("%Y/%m/%d")
hex_prefix = hex_code[-2:]
# Test 1: adsb.fi trace_full
url1 = f"https://globe.adsb.fi/data/traces/{date_str}/{hex_prefix}/trace_full_{hex_code}.json"
print(f"URL1: {url1}")
r = subprocess.run(["curl", "-s", "--max-time", "10", url1], capture_output=True, text=True, timeout=15)
if r.stdout.strip().startswith("{"):
data = json.loads(r.stdout)
print(f"SUCCESS! Keys: {list(data.keys())}")
if 'trace' in data:
pts = data['trace']
print(f"Trace points: {len(pts)}")
if pts:
print(f"FIRST (takeoff): {pts[0]}")
print(f"LAST (now): {pts[-1]}")
else:
print(f"Not JSON (first 100 chars): {r.stdout[:100]}")
# That response was behind cloudflare, try adsb.lol instead
# Test 2: adsb.lol hex lookup
url2 = f"https://api.adsb.lol/v2/hex/{hex_code}"
print(f"\nURL2: {url2}")
r2 = subprocess.run(["curl", "-s", "--max-time", "10", url2], capture_output=True, text=True, timeout=15)
if r2.stdout.strip().startswith("{"):
data = json.loads(r2.stdout)
if 'ac' in data and data['ac']:
ac = data['ac'][0]
keys = sorted(ac.keys())
print(f"All keys ({len(keys)}): {keys}")
else:
print(f"Not JSON: {r2.stdout[:100]}")
# Test 3: Try adsb.lol trace
url3 = f"https://api.adsb.lol/trace/{hex_code}"
print(f"\nURL3: {url3}")
r3 = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "10", url3], capture_output=True, text=True, timeout=15)
print(f"HTTP status: {r3.stdout}")
# Test 4: Try globe.adsb.lol format
url4 = f"https://globe.adsb.lol/data/traces/{date_str}/{hex_prefix}/trace_full_{hex_code}.json"
print(f"\nURL4: {url4}")
r4 = subprocess.run(["curl", "-s", "--max-time", "10", url4], capture_output=True, text=True, timeout=15)
if r4.stdout.strip().startswith("{"):
data = json.loads(r4.stdout)
print(f"SUCCESS! Keys: {list(data.keys())}")
if 'trace' in data:
pts = data['trace']
print(f"Trace points: {len(pts)}")
if pts:
print(f"FIRST (takeoff): {pts[0]}")
print(f"LAST (now): {pts[-1]}")
else:
print(f"Response: {r4.stdout[:150]}")
-8
View File
@@ -1,8 +0,0 @@
import asyncio, websockets
async def main():
try:
async with websockets.connect('wss://stream.aisstream.io/v0/stream') as ws:
print('Connected to AIS Stream!')
except Exception as e:
print(f"Error: {e}")
asyncio.run(main())