-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
182 lines (152 loc) · 6.07 KB
/
Copy pathapp.py
File metadata and controls
182 lines (152 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
from flask import Flask, jsonify, send_from_directory
import requests
import json
import math
app = Flask(__name__, static_folder='static')
def process_coordinate(value):
"""
Convert a value to a float.
If the conversion fails or if the value is NaN, return None.
"""
try:
f = float(value)
if math.isnan(f):
return None
return f
except (ValueError, TypeError):
return None
def process_altitude(value):
"""Convert a value to a float, and return None if it is NaN or invalid."""
try:
alt_val = float(value)
if math.isnan(alt_val):
return None
return alt_val
except (ValueError, TypeError):
return None
def is_valid_balloon_record(record):
"""
- For a dict, it must have 'lat' and 'lon' keys.
- For a list, it must have at least two values.
"""
if isinstance(record, dict):
if 'lat' not in record or 'lon' not in record:
return False
return True
elif isinstance(record, list):
return len(record) >= 2
return False
def are_same_balloon(rec1, rec2, threshold=1.0):
"""
Figure out if two balloon records are from the same balloon based on prox.
"""
try:
lat1 = rec1['lat']
lon1 = rec1['lon']
lat2 = rec2['lat']
lon2 = rec2['lon']
dist = math.sqrt((lat1 - lat2) ** 2 + (lon1 - lon2) ** 2)
return dist <= threshold
except Exception:
return False
@app.route('/api/balloon-history')
def balloon_history():
"""
Aggregates the flight history from the last 24 hours.
Each JSON file (00.json through 23.json) is snapshot taken N hours ago.
We fetch each file, validate data,
and then group records from different snapshots together if they appear to be the same balloon.
"""
base_url = "https://a.windbornesystems.com/treasure/"
aggregated_data = {}
balloon_counter = 0
for i in range(5):
file_name = f"{i:02d}.json"
url = base_url + file_name
print(f"\n=== Processing snapshot {i} from URL: {url} ===")
try:
response = requests.get(url, timeout=5)
print(f"Received response for snapshot {i} with status code: {response.status_code}")
response.raise_for_status()
try:
data = response.json()
print(f"Successfully parsed JSON for snapshot {i}.")
except ValueError as e:
print(f"Error parsing JSON from {url}: {e}")
text = response.text.strip()
last_bracket = text.rfind(']')
if last_bracket != -1:
trimmed_text = text[:last_bracket+1]
try:
data = json.loads(trimmed_text)
print(f"Successfully trimmed and parsed JSON for snapshot {i} (records: {len(data)}).")
except Exception as e2:
print(f"Failed to parse trimmed JSON from {url}: {e2}")
continue
else:
print(f"No closing bracket found in response from {url}. Skipping snapshot {i}.")
continue
if not isinstance(data, list):
print(f"Warning: Data from {file_name} is not a list. Skipping this snapshot.")
continue
print(f"Snapshot {i} contains {len(data)} records.")
for idx, record in enumerate(data):
if not is_valid_balloon_record(record):
print(f"Warning: Invalid record in {file_name}, index {idx}: {record}")
continue
if isinstance(record, list):
temp_id = f"temp_{i}_{idx}"
lat = process_coordinate(record[0])
lon = process_coordinate(record[1])
if lat is None or lon is None:
print(f"Skipping record in {file_name}, index {idx} due to invalid coordinates.")
continue
new_record = {
'id': temp_id,
'lat': lat,
'lon': lon,
'hours_ago': i
}
if len(record) > 2:
new_record['alt'] = process_altitude(record[2])
record = new_record
else:
lat = process_coordinate(record['lat'])
lon = process_coordinate(record['lon'])
if lat is None or lon is None:
print(f"Skipping dict record in {file_name}, index {idx} due to invalid coordinates: {record}")
continue
record['lat'] = lat
record['lon'] = lon
record['hours_ago'] = i
if 'alt' in record:
record['alt'] = process_altitude(record['alt'])
merged = False
for balloon_id, history in aggregated_data.items():
if are_same_balloon(history[-1], record, threshold=1.0):
history.append(record)
merged = True
break
if not merged:
new_balloon_id = f"balloon_{balloon_counter}"
balloon_counter += 1
aggregated_data[new_balloon_id] = [record]
except Exception as e:
print(f"Error fetching {url}: {e}")
continue
balloons_list = []
for balloon_id, history in aggregated_data.items():
history.sort(key=lambda r: r['hours_ago'])
balloons_list.append({
"id": balloon_id,
"history": history
})
print("\n=== Aggregation complete ===")
print(f"Total balloons aggregated: {len(balloons_list)}")
return jsonify({"balloons": balloons_list})
@app.route('/')
def index():
"""Serve the main HTML page."""
return send_from_directory(app.static_folder, 'index.html')
if __name__ == '__main__':
app.run(debug=True)