msabia commited on
Commit
cd20d56
·
verified ·
1 Parent(s): 946d618

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +200 -200
app.py CHANGED
@@ -1,200 +1,200 @@
1
- import requests
2
- import sys
3
- import math
4
-
5
- import numpy as np
6
- from shapely.geometry import Point, MultiPoint
7
- from sklearn.cluster import DBSCAN
8
- import geopandas as gpd
9
- import gradio as gr
10
- import geopy.distance
11
- import folium
12
-
13
-
14
-
15
- def load_countries():
16
- countries_dict = {}
17
- countries = gpd.read_file("https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_admin_0_countries.geojson")
18
- countries = countries[['name', 'geometry']]
19
- for index, row in countries.iterrows():
20
- centroid = row['geometry'].centroid
21
- # store as (lat, lon)
22
- countries_dict[row['name']] = (centroid.y, centroid.x)
23
-
24
- return countries_dict
25
-
26
- def find_closest_country(coordinate, countries):
27
- closest_country = None
28
- smallest_distance = None
29
-
30
- for country in countries:
31
- distance = geopy.distance.geodesic(coordinate, countries[country]).km
32
- if smallest_distance is None or distance < smallest_distance:
33
- smallest_distance = distance
34
- closest_country = country
35
-
36
- return closest_country, smallest_distance
37
-
38
- def track_balloon(index):
39
- index = int(index)
40
- global countries
41
-
42
- points = []
43
- annotations = []
44
- for i in range(24):
45
- url = f"https://a.windbornesystems.com/treasure/{i:02}.json"
46
- try:
47
- response = requests.get(url, timeout=8)
48
- if response.status_code != 200:
49
- print("failed to fetch from url")
50
- continue
51
- data = response.json()
52
- except Exception as e:
53
- print("Failed to retrieve data.", e)
54
- continue
55
- balloon = data[index - 1]
56
-
57
- lat, lon = balloon[:2]
58
- points.append((lat, lon))
59
-
60
- country, distance_km = find_closest_country((lat, lon), countries)
61
- description = f"Hour offset: {i} — Closest country: {country} ({distance_km:.1f} km)"
62
- annotations.append((lat, lon, description))
63
-
64
-
65
- m = folium.Map(location=points[-1])
66
- folium.PolyLine(locations=points, weight=3, opacity=1).add_to(m)
67
- for idx, (lat, lon, desc) in enumerate(annotations):
68
- popup_text = desc
69
- if idx == 0:
70
- folium.CircleMarker(location=(lat, lon),
71
- radius=10,
72
- popup=popup_text,
73
- tooltip="Last known location",
74
- fill=True).add_to(m)
75
- else:
76
- folium.Marker(location=(lat, lon),
77
- popup=popup_text,
78
- tooltip=f"Hour {idx}").add_to(m)
79
-
80
- return m._repr_html_()
81
-
82
- def cluster_balloons(hour, eps_km, samples, show_hulls):
83
- earth_radius = 6371
84
-
85
- points = []
86
- url = f"https://a.windbornesystems.com/treasure/{hour:02}.json"
87
- r = requests.get(url, timeout=8)
88
- if r.status_code != 200:
89
- return f"Error: Problem querying {url}"
90
- data = r.json()
91
-
92
- for balloon in data:
93
- lat, lon = balloon[:2]
94
- points.append((lat, lon))
95
-
96
- if not points:
97
- return "Error: no points found"
98
-
99
- coords = np.array(points)
100
- coords_rad = np.radians(coords)
101
- eps_rad = eps_km /earth_radius
102
-
103
- labels = DBSCAN(eps=eps_rad, min_samples=samples, metric='haversine').fit_predict(coords_rad)
104
- # print(labels)
105
- unique_labels = sorted(set(labels))
106
- # print(unique_labels)
107
- n_clusters = len(unique_labels)
108
- if -1 in unique_labels:
109
- n_clusters -=1
110
-
111
- base_colors = [
112
- "#000ddd", "#929203", "#2ca02c", "#d62728", "#5b3181", "#5e403a",
113
- "#e377c2", "#796060", "#ffff00", "#00e5ff", "#3a657c", "#2b4217"
114
- ]
115
-
116
- def color_for_label(label):
117
- if label == -1:
118
- return "#000000"
119
- return base_colors[label % len(base_colors)]
120
-
121
- map = folium.Map(location=coords.mean(axis=0).tolist())
122
- for (lat, lon), label in zip(coords, labels):
123
- folium.CircleMarker(
124
- location=(lat, lon),
125
- radius=3,
126
- color=color_for_label(label),
127
- fill=True,
128
- fill_opacity=1,
129
- popup=f"Cluster: {label}" if label > -1 else "Noise"
130
- ).add_to(map)
131
-
132
- # draw border around clusters
133
- if show_hulls:
134
- for label in unique_labels:
135
- if label == -1:
136
- continue
137
- mask = (labels == label)
138
- curr_cluster = coords[mask]
139
- if len(curr_cluster) == 0:
140
- continue
141
-
142
- hull_points = []
143
- for point in curr_cluster:
144
- hull_points.append(Point(point[1], point[0])) # change lattitude, longitude order hull calculation
145
- mp = MultiPoint(hull_points)
146
-
147
- hull = mp.convex_hull
148
- hull_coords = []
149
- for lon, lat in hull.exterior.coords:
150
- hull_coords.append((lat, lon)) # change back for folium map again
151
- l_color = color_for_label(label)
152
- folium.Polygon(
153
- locations=hull_coords,
154
- color=l_color,
155
- weight=2,
156
- fill=True,
157
- fill_color=l_color,
158
- fill_opacity=0.2,
159
- popup=f"Cluster {label}, size: {len(curr_cluster)}"
160
- ).add_to(map)
161
-
162
- title_for_html = f"""
163
- <div style="position: fixed;
164
- top: 10px; left: 10px; width: auto; height: auto;
165
- z-index: 9999; font-size: 15px;
166
- background-color: white; padding: 8px; border: 2px solid #000;">
167
- <b>Clusters (data from {hour} hours ago)</b><br/>
168
- DBSCAN eps = {eps_km} km <br/>
169
- clusters found = {n_clusters}, total points = {len(coords)}
170
- </div>
171
- """
172
- map.get_root().html.add_child(folium.Element(title_for_html))
173
-
174
- return map._repr_html_()
175
-
176
- countries = load_countries()
177
-
178
- with gr.Blocks() as demo:
179
- gr.Markdown("Cluster Visualization + Balloon Tracker")
180
- with gr.Tabs():
181
- with gr.TabItem("Cluster balloons (all balloons)"):
182
- with gr.Row():
183
- hours_slider = gr.Slider(label="Hours ago", minimum=0, maximum=23, step=1, value=0)
184
- num_samples = gr.Number(label="DBSCAN min_samples", value=5, precision=0)
185
- eps_km = gr.Number(label="DBSCAN eps (km)", value=750, precision=1)
186
- with gr.Row():
187
- hull_checkbox = gr.Checkbox(label="Draw hull around clusters", value=True)
188
- cluster_btn = gr.Button("Show Clusters")
189
- clusters_map_html = gr.HTML()
190
- cluster_btn.click(fn=cluster_balloons, inputs=[hours_slider, eps_km, num_samples, hull_checkbox], outputs=[clusters_map_html])
191
- with gr.TabItem("Track balloon"):
192
- with gr.Row():
193
- balloon = gr.Number(label="Balloon index", minimum=1, maximum=1000, value=1)
194
- track_btn = gr.Button("Track")
195
- map_html = gr.HTML()
196
- track_btn.click(fn=track_balloon, inputs=[balloon], outputs=[map_html])
197
-
198
-
199
-
200
- demo.launch(share=True)
 
1
+ import requests
2
+ import sys
3
+ import math
4
+
5
+ import numpy as np
6
+ from shapely.geometry import Point, MultiPoint
7
+ from sklearn.cluster import DBSCAN
8
+ import geopandas as gpd
9
+ import gradio as gr
10
+ import geopy.distance
11
+ import folium
12
+
13
+
14
+
15
+ def load_countries():
16
+ countries_dict = {}
17
+ countries = gpd.read_file("https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_admin_0_countries.geojson")
18
+ countries = countries[['name', 'geometry']]
19
+ for index, row in countries.iterrows():
20
+ centroid = row['geometry'].centroid
21
+ # store as (lat, lon)
22
+ countries_dict[row['name']] = (centroid.y, centroid.x)
23
+
24
+ return countries_dict
25
+
26
+ def find_closest_country(coordinate, countries):
27
+ closest_country = None
28
+ smallest_distance = None
29
+
30
+ for country in countries:
31
+ distance = geopy.distance.geodesic(coordinate, countries[country]).km
32
+ if smallest_distance is None or distance < smallest_distance:
33
+ smallest_distance = distance
34
+ closest_country = country
35
+
36
+ return closest_country, smallest_distance
37
+
38
+ def track_balloon(index):
39
+ index = int(index)
40
+ global countries
41
+
42
+ points = []
43
+ annotations = []
44
+ for i in range(24):
45
+ url = f"https://a.windbornesystems.com/treasure/{i:02}.json"
46
+ try:
47
+ response = requests.get(url, timeout=8)
48
+ if response.status_code != 200:
49
+ print("failed to fetch from url")
50
+ continue
51
+ data = response.json()
52
+ except Exception as e:
53
+ print("Failed to retrieve data.", e)
54
+ continue
55
+ balloon = data[index - 1]
56
+
57
+ lat, lon = balloon[:2]
58
+ points.append((lat, lon))
59
+
60
+ country, distance_km = find_closest_country((lat, lon), countries)
61
+ description = f"Hour offset: {i} — Closest country: {country} ({distance_km:.1f} km)"
62
+ annotations.append((lat, lon, description))
63
+
64
+
65
+ m = folium.Map(location=points[-1])
66
+ folium.PolyLine(locations=points, weight=3, opacity=1).add_to(m)
67
+ for idx, (lat, lon, desc) in enumerate(annotations):
68
+ popup_text = desc
69
+ if idx == 0:
70
+ folium.CircleMarker(location=(lat, lon),
71
+ radius=10,
72
+ popup=popup_text,
73
+ tooltip="Last known location",
74
+ fill=True).add_to(m)
75
+ else:
76
+ folium.Marker(location=(lat, lon),
77
+ popup=popup_text,
78
+ tooltip=f"Hour {idx}").add_to(m)
79
+
80
+ return m._repr_html_()
81
+
82
+ def cluster_balloons(hour, eps_km, samples, show_hulls):
83
+ earth_radius = 6371
84
+
85
+ points = []
86
+ url = f"https://a.windbornesystems.com/treasure/{hour:02}.json"
87
+ r = requests.get(url, timeout=8)
88
+ if r.status_code != 200:
89
+ return f"Error: Problem querying {url}"
90
+ data = r.json()
91
+
92
+ for balloon in data:
93
+ lat, lon = balloon[:2]
94
+ points.append((lat, lon))
95
+
96
+ if not points:
97
+ return "Error: no points found"
98
+
99
+ coords = np.array(points)
100
+ coords_rad = np.radians(coords)
101
+ eps_rad = eps_km /earth_radius
102
+
103
+ labels = DBSCAN(eps=eps_rad, min_samples=samples, metric='haversine').fit_predict(coords_rad)
104
+ # print(labels)
105
+ unique_labels = sorted(set(labels))
106
+ # print(unique_labels)
107
+ n_clusters = len(unique_labels)
108
+ if -1 in unique_labels:
109
+ n_clusters -=1
110
+
111
+ base_colors = [
112
+ "#000ddd", "#929203", "#2ca02c", "#d62728", "#5b3181", "#5e403a",
113
+ "#e377c2", "#796060", "#ffff00", "#00e5ff", "#3a657c", "#2b4217"
114
+ ]
115
+
116
+ def color_for_label(label):
117
+ if label == -1:
118
+ return "#000000"
119
+ return base_colors[label % len(base_colors)]
120
+
121
+ map = folium.Map(location=coords.mean(axis=0).tolist(), zoom_start=3)
122
+ for (lat, lon), label in zip(coords, labels):
123
+ folium.CircleMarker(
124
+ location=(lat, lon),
125
+ radius=3,
126
+ color=color_for_label(label),
127
+ fill=True,
128
+ fill_opacity=1,
129
+ popup=f"Cluster: {label}" if label > -1 else "Noise"
130
+ ).add_to(map)
131
+
132
+ # draw border around clusters
133
+ if show_hulls:
134
+ for label in unique_labels:
135
+ if label == -1:
136
+ continue
137
+ mask = (labels == label)
138
+ curr_cluster = coords[mask]
139
+ if len(curr_cluster) == 0:
140
+ continue
141
+
142
+ hull_points = []
143
+ for point in curr_cluster:
144
+ hull_points.append(Point(point[1], point[0])) # change lattitude, longitude order hull calculation
145
+ mp = MultiPoint(hull_points)
146
+
147
+ hull = mp.convex_hull
148
+ hull_coords = []
149
+ for lon, lat in hull.exterior.coords:
150
+ hull_coords.append((lat, lon)) # change back for folium map again
151
+ l_color = color_for_label(label)
152
+ folium.Polygon(
153
+ locations=hull_coords,
154
+ color=l_color,
155
+ weight=2,
156
+ fill=True,
157
+ fill_color=l_color,
158
+ fill_opacity=0.2,
159
+ popup=f"Cluster {label}, size: {len(curr_cluster)}"
160
+ ).add_to(map)
161
+
162
+ title_for_html = f"""
163
+ <div style="position: fixed;
164
+ top: 10px; left: 10px; width: auto; height: auto;
165
+ z-index: 9999; font-size: 15px;
166
+ background-color: white; padding: 8px; border: 2px solid #000;">
167
+ <b>Clusters (data from {hour} hours ago)</b><br/>
168
+ DBSCAN eps = {eps_km} km <br/>
169
+ clusters found = {n_clusters}, total points = {len(coords)}
170
+ </div>
171
+ """
172
+ map.get_root().html.add_child(folium.Element(title_for_html))
173
+
174
+ return map._repr_html_()
175
+
176
+ countries = load_countries()
177
+
178
+ with gr.Blocks() as demo:
179
+ gr.Markdown("Cluster Visualization + Balloon Tracker")
180
+ with gr.Tabs():
181
+ with gr.TabItem("Cluster balloons (all balloons)"):
182
+ with gr.Row():
183
+ hours_slider = gr.Slider(label="Hours ago", minimum=0, maximum=23, step=1, value=0)
184
+ num_samples = gr.Number(label="DBSCAN min_samples", value=5, precision=0)
185
+ eps_km = gr.Number(label="DBSCAN eps (km)", value=750, precision=1)
186
+ with gr.Row():
187
+ hull_checkbox = gr.Checkbox(label="Draw hull around clusters", value=True)
188
+ cluster_btn = gr.Button("Show Clusters")
189
+ clusters_map_html = gr.HTML()
190
+ cluster_btn.click(fn=cluster_balloons, inputs=[hours_slider, eps_km, num_samples, hull_checkbox], outputs=[clusters_map_html])
191
+ with gr.TabItem("Track balloon"):
192
+ with gr.Row():
193
+ balloon = gr.Number(label="Balloon index", minimum=1, maximum=1000, value=1)
194
+ track_btn = gr.Button("Track")
195
+ map_html = gr.HTML()
196
+ track_btn.click(fn=track_balloon, inputs=[balloon], outputs=[map_html])
197
+
198
+
199
+
200
+ demo.launch(share=True)