iotlabkit 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,347 @@
1
+ """Template generator for Program 11: ISS Real-Time Location Tracking & Trajectory Analysis."""
2
+
3
+ from typing import Dict
4
+
5
+
6
+ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
7
+ files = {}
8
+
9
+ files["requirements.txt"] = (
10
+ "requests>=2.28.0\n"
11
+ "pandas>=1.5.0\n"
12
+ "matplotlib>=3.6.0\n"
13
+ "streamlit>=1.25.0\n"
14
+ )
15
+
16
+ files[".env.example"] = (
17
+ "# ISS Telemetry Collector Settings\n"
18
+ "# Default: 100 records at 5-second intervals (~8.3 minutes total duration)\n"
19
+ "SAMPLE_COUNT=100\n"
20
+ "SAMPLE_INTERVAL_SECONDS=5\n"
21
+ )
22
+
23
+ files[".gitignore"] = (
24
+ ".env\n"
25
+ "__pycache__/\n"
26
+ "*.py[cod]\n"
27
+ "output/*.png\n"
28
+ )
29
+
30
+ # collector.py
31
+ if mode == "learning":
32
+ col_doc = (
33
+ '"""ISS Live Orbital Position Collector - Program 11.\n\n'
34
+ "Retrieves authentic real-time orbital coordinates of the International Space Station\n"
35
+ "from live APIs: Open-Notify (`api.open-notify.org`) with automatic fallback to\n"
36
+ "WhereTheISS (`api.wheretheiss.at/v1/satellites/25544`).\n\n"
37
+ "Collects 100 observations at 5-second intervals (500 seconds ā‰ˆ 8.33 minutes)\n"
38
+ "and appends records to 'data/iss_locations.csv'.\n"
39
+ '"""\n'
40
+ )
41
+ else:
42
+ col_doc = '"""ISS Location Collector - Program 11 (Exam Mode)."""\n'
43
+
44
+ files["collector.py"] = col_doc + (
45
+ "import argparse\n"
46
+ "import csv\n"
47
+ "import os\n"
48
+ "import sys\n"
49
+ "import time\n"
50
+ "from datetime import datetime, timezone\n"
51
+ "import requests\n\n"
52
+ "PRIMARY_URL = 'http://api.open-notify.org/iss-now.json'\n"
53
+ "FALLBACK_URL = 'https://api.wheretheiss.at/v1/satellites/25544'\n"
54
+ "CSV_PATH = os.path.join(os.path.dirname(__file__), 'data', 'iss_locations.csv')\n\n"
55
+ "def fetch_iss_position() -> dict:\n"
56
+ " \"\"\"Fetches live ISS coordinates from primary API, falling back to backup HTTPS API.\"\"\"\n"
57
+ " # Try primary: Open-Notify\n"
58
+ " try:\n"
59
+ " resp = requests.get(PRIMARY_URL, timeout=4)\n"
60
+ " if resp.status_code == 200:\n"
61
+ " data = resp.json()\n"
62
+ " pos = data.get('iss_position', {})\n"
63
+ " return {\n"
64
+ " 'timestamp': data.get('timestamp', int(time.time())),\n"
65
+ " 'latitude': float(pos.get('latitude')),\n"
66
+ " 'longitude': float(pos.get('longitude')),\n"
67
+ " 'source': 'Open-Notify'\n"
68
+ " }\n"
69
+ " except Exception:\n"
70
+ " pass\n\n"
71
+ " # Fallback: WhereTheISS\n"
72
+ " try:\n"
73
+ " resp = requests.get(FALLBACK_URL, timeout=4)\n"
74
+ " if resp.status_code == 200:\n"
75
+ " data = resp.json()\n"
76
+ " return {\n"
77
+ " 'timestamp': int(data.get('timestamp', time.time())),\n"
78
+ " 'latitude': float(data.get('latitude')),\n"
79
+ " 'longitude': float(data.get('longitude')),\n"
80
+ " 'source': 'WhereTheISS'\n"
81
+ " }\n"
82
+ " except Exception as e:\n"
83
+ " raise RuntimeError(f'Both ISS APIs failed: {e}')\n\n"
84
+ "def main():\n"
85
+ " parser = argparse.ArgumentParser(description='Collect ISS orbital location telemetry')\n"
86
+ " parser.add_argument('--count', type=int, default=100, help='Total number of location samples (default: 100)')\n"
87
+ " parser.add_argument('--interval', type=float, default=5.0, help='Seconds between samples (default: 5.0)')\n"
88
+ " args = parser.parse_args()\n\n"
89
+ " total_seconds = args.count * args.interval\n"
90
+ " total_minutes = round(total_seconds / 60, 1)\n"
91
+ " print('=' * 70)\n"
92
+ " print('šŸ›°ļø ISS Real-Time Orbital Tracking Collector')\n"
93
+ " print('=' * 70)\n"
94
+ " print(f'Target Samples : {args.count} records')\n"
95
+ " print(f'Polling Interval : {args.interval} seconds')\n"
96
+ " print(f'Total Expected Time : {total_seconds} seconds (~{total_minutes} minutes)')\n"
97
+ " print(f'Output CSV File : {CSV_PATH}')\n"
98
+ " print('=' * 70)\n\n"
99
+ " os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True)\n"
100
+ " file_exists = os.path.exists(CSV_PATH)\n\n"
101
+ " with open(CSV_PATH, 'a', newline='', encoding='utf-8') as f:\n"
102
+ " writer = csv.writer(f)\n"
103
+ " if not file_exists or os.path.getsize(CSV_PATH) == 0:\n"
104
+ " writer.writerow(['timestamp', 'iso_datetime', 'latitude', 'longitude', 'source'])\n\n"
105
+ " collected = 0\n"
106
+ " for i in range(1, args.count + 1):\n"
107
+ " try:\n"
108
+ " pt = fetch_iss_position()\n"
109
+ " dt_str = datetime.fromtimestamp(pt['timestamp'], tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')\n"
110
+ " writer.writerow([pt['timestamp'], dt_str, pt['latitude'], pt['longitude'], pt['source']])\n"
111
+ " f.flush()\n"
112
+ " collected += 1\n"
113
+ " print(f\"[{i:03d}/{args.count:03d}] {dt_str} | Lat: {pt['latitude']:>8.4f}° | Lon: {pt['longitude']:>9.4f}° ({pt['source']})\")\n"
114
+ " except Exception as err:\n"
115
+ " print(f\"[{i:03d}/{args.count:03d}] āœ— Fetch error: {err}\")\n\n"
116
+ " if i < args.count:\n"
117
+ " time.sleep(args.interval)\n\n"
118
+ " print(f'\\nāœ“ Successfully collected {collected} real ISS location observations in {CSV_PATH}.')\n"
119
+ " print('Now run `python analyzer.py` to calculate orbital metrics and view plots.')\n\n"
120
+ "if __name__ == '__main__':\n"
121
+ " main()\n"
122
+ )
123
+
124
+ # analyzer.py
125
+ if mode == "learning":
126
+ ana_doc = (
127
+ '"""ISS Orbital Telemetry Analyzer.\n\n'
128
+ "Computes orbital statistics, distance traveled (Haversine formula),\n"
129
+ "orbital speed approximation, and plots Latitude vs Time and Longitude vs Time.\n"
130
+ '"""\n'
131
+ )
132
+ else:
133
+ ana_doc = '"""ISS Telemetry Analyzer - Program 11 (Exam Mode)."""\n'
134
+
135
+ files["analyzer.py"] = ana_doc + (
136
+ "import math\n"
137
+ "import os\n"
138
+ "import sys\n"
139
+ "import pandas as pd\n"
140
+ "try:\n"
141
+ " import matplotlib.pyplot as plt\n"
142
+ " HAS_MATPLOTLIB = True\n"
143
+ "except ImportError:\n"
144
+ " HAS_MATPLOTLIB = False\n\n"
145
+ "CSV_PATH = os.path.join(os.path.dirname(__file__), 'data', 'iss_locations.csv')\n"
146
+ "OUTPUT_DIR = os.path.join(os.path.dirname(__file__), 'output')\n\n"
147
+ "def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:\n"
148
+ " \"\"\"Calculates great-circle distance between two GPS coordinates in kilometers.\"\"\"\n"
149
+ " R = 6371.0 # Earth's mean radius in km\n"
150
+ " phi1 = math.radians(lat1)\n"
151
+ " phi2 = math.radians(lat2)\n"
152
+ " delta_phi = math.radians(lat2 - lat1)\n"
153
+ " delta_lambda = math.radians(lon2 - lon1)\n"
154
+ " a = math.sin(delta_phi / 2)**2 + math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2)**2\n"
155
+ " c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))\n"
156
+ " return R * c\n\n"
157
+ "def main():\n"
158
+ " if not os.path.exists(CSV_PATH) or os.path.getsize(CSV_PATH) == 0:\n"
159
+ " print(f\"āœ— Error: Dataset file '{CSV_PATH}' is missing or empty.\")\n"
160
+ " print(\" Run `python collector.py` first to collect live orbital points.\")\n"
161
+ " sys.exit(1)\n\n"
162
+ " df = pd.read_csv(CSV_PATH)\n"
163
+ " if len(df) < 2:\n"
164
+ " print(f\"āœ— Need at least 2 records for statistical trajectory analysis (found {len(df)}).\")\n"
165
+ " sys.exit(1)\n\n"
166
+ " os.makedirs(OUTPUT_DIR, exist_ok=True)\n"
167
+ " df['timestamp'] = pd.to_numeric(df['timestamp'])\n"
168
+ " df['latitude'] = pd.to_numeric(df['latitude'])\n"
169
+ " df['longitude'] = pd.to_numeric(df['longitude'])\n\n"
170
+ " # Compute distances between consecutive points\n"
171
+ " distances = []\n"
172
+ " durations = []\n"
173
+ " for i in range(1, len(df)):\n"
174
+ " d = haversine_distance(\n"
175
+ " df.iloc[i - 1]['latitude'], df.iloc[i - 1]['longitude'],\n"
176
+ " df.iloc[i]['latitude'], df.iloc[i]['longitude']\n"
177
+ " )\n"
178
+ " dt = df.iloc[i]['timestamp'] - df.iloc[i - 1]['timestamp']\n"
179
+ " distances.append(d)\n"
180
+ " durations.append(dt)\n\n"
181
+ " total_dist_km = sum(distances)\n"
182
+ " total_time_s = sum(durations)\n"
183
+ " avg_speed_km_h = (total_dist_km / (total_time_s / 3600)) if total_time_s > 0 else 0\n\n"
184
+ " print('=' * 65)\n"
185
+ " print('šŸ›°ļø ISS Trajectory & Orbital Statistical Summary')\n"
186
+ " print('=' * 65)\n"
187
+ " print(f\"Total Observations : {len(df)} records\")\n"
188
+ " print(f\"Latitude Range : {df['latitude'].min():.4f}° to {df['latitude'].max():.4f}° (Mean: {df['latitude'].mean():.4f}°, Std: {df['latitude'].std():.4f})\")\n"
189
+ " print(f\"Longitude Range : {df['longitude'].min():.4f}° to {df['longitude'].max():.4f}° (Mean: {df['longitude'].mean():.4f}°, Std: {df['longitude'].std():.4f})\")\n"
190
+ " print(f\"Total Ground Distance : {total_dist_km:.2f} km\")\n"
191
+ " print(f\"Elapsed Tracking Time : {total_time_s:.1f} seconds ({round(total_time_s/60, 2)} minutes)\")\n"
192
+ " print(f\"Estimated Ground Speed : {avg_speed_km_h:.1f} km/h (Theoretical ISS speed: ~27,600 km/h)\")\n"
193
+ " print('=' * 65)\n\n"
194
+ " if HAS_MATPLOTLIB:\n"
195
+ " # Plot 1: Latitude vs Time\n"
196
+ " plt.figure(figsize=(10, 4))\n"
197
+ " plt.plot(range(len(df)), df['latitude'], color='tab:blue', marker='o', markersize=3, label='Latitude (°)')\n"
198
+ " plt.title('ISS Latitude vs. Observation Index')\n"
199
+ " plt.xlabel('Observation Index (5s intervals)')\n"
200
+ " plt.ylabel('Latitude (°)')\n"
201
+ " plt.grid(True, linestyle='--', alpha=0.6)\n"
202
+ " plt.legend()\n"
203
+ " lat_plot_path = os.path.join(OUTPUT_DIR, 'iss_latitude_trend.png')\n"
204
+ " plt.savefig(lat_plot_path, dpi=150, bbox_inches='tight')\n"
205
+ " plt.close()\n"
206
+ " print(f\"āœ“ Saved Latitude Trend Chart to: {lat_plot_path}\")\n\n"
207
+ " # Plot 2: Longitude vs Time\n"
208
+ " plt.figure(figsize=(10, 4))\n"
209
+ " plt.plot(range(len(df)), df['longitude'], color='tab:orange', marker='s', markersize=3, label='Longitude (°)')\n"
210
+ " plt.title('ISS Longitude vs. Observation Index')\n"
211
+ " plt.xlabel('Observation Index (5s intervals)')\n"
212
+ " plt.ylabel('Longitude (°)')\n"
213
+ " plt.grid(True, linestyle='--', alpha=0.6)\n"
214
+ " plt.legend()\n"
215
+ " lon_plot_path = os.path.join(OUTPUT_DIR, 'iss_longitude_trend.png')\n"
216
+ " plt.savefig(lon_plot_path, dpi=150, bbox_inches='tight')\n"
217
+ " plt.close()\n"
218
+ " print(f\"āœ“ Saved Longitude Trend Chart to: {lon_plot_path}\")\n\n"
219
+ " # Plot 3: Ground Track Map\n"
220
+ " plt.figure(figsize=(10, 5))\n"
221
+ " plt.plot(df['longitude'], df['latitude'], color='red', marker='x', label='ISS Ground Track')\n"
222
+ " plt.title('ISS Ground Track Orbit Trajectory')\n"
223
+ " plt.xlabel('Longitude (°)')\n"
224
+ " plt.ylabel('Latitude (°)')\n"
225
+ " plt.xlim(-180, 180)\n"
226
+ " plt.ylim(-90, 90)\n"
227
+ " plt.grid(True, linestyle='--', alpha=0.6)\n"
228
+ " plt.legend()\n"
229
+ " track_path = os.path.join(OUTPUT_DIR, 'iss_ground_track.png')\n"
230
+ " plt.savefig(track_path, dpi=150, bbox_inches='tight')\n"
231
+ " plt.close()\n"
232
+ " print(f\"āœ“ Saved Ground Track Map to: {track_path}\")\n"
233
+ " else:\n"
234
+ " print(\"\\nā„¹ļø Notice: matplotlib not installed. Statistical metrics printed above; plot generation skipped.\")\n\n"
235
+ "if __name__ == '__main__':\n"
236
+ " main()\n"
237
+ )
238
+
239
+ # dashboard.py
240
+ files["dashboard.py"] = (
241
+ "import os\n"
242
+ "import pandas as pd\n"
243
+ "import streamlit as st\n\n"
244
+ "st.set_page_config(page_title='Program 11: ISS Orbit Tracker', layout='wide')\n"
245
+ "st.title('šŸ›°ļø ISS Real-Time Location & Orbital Trajectory Dashboard')\n"
246
+ "st.caption('MSc/MCA IoT Practical — Program 11 (ISS Location Tracking & Analytics)')\n\n"
247
+ "CSV_PATH = os.path.join(os.path.dirname(__file__), 'data', 'iss_locations.csv')\n\n"
248
+ "if not os.path.exists(CSV_PATH) or os.path.getsize(CSV_PATH) == 0:\n"
249
+ " st.warning('No ISS telemetry found. Run `python collector.py --count 100 --interval 5` to collect orbital data.')\n"
250
+ "else:\n"
251
+ " df = pd.read_csv(CSV_PATH)\n"
252
+ " st.success(f'Loaded {len(df)} orbital observations.')\n\n"
253
+ " latest = df.iloc[-1]\n"
254
+ " col1, col2, col3 = st.columns(3)\n"
255
+ " col1.metric('Latest Latitude', f\"{latest['latitude']:.4f} °\")\n"
256
+ " col2.metric('Latest Longitude', f\"{latest['longitude']:.4f} °\")\n"
257
+ " col3.metric('Telemetry Source', str(latest.get('source', 'API')))\n\n"
258
+ " st.subheader('šŸ—ŗļø Orbital Ground Track Map')\n"
259
+ " map_df = df[['latitude', 'longitude']].rename(columns={'latitude': 'lat', 'longitude': 'lon'})\n"
260
+ " st.map(map_df)\n\n"
261
+ " st.subheader('šŸ“ˆ Telemetry Trends')\n"
262
+ " tcol1, tcol2 = st.columns(2)\n"
263
+ " with tcol1:\n"
264
+ " st.line_chart(df['latitude'], use_container_width=True)\n"
265
+ " st.caption('Latitude vs Time')\n"
266
+ " with tcol2:\n"
267
+ " st.line_chart(df['longitude'], use_container_width=True)\n"
268
+ " st.caption('Longitude vs Time')\n\n"
269
+ " st.subheader('šŸ“‹ Collected Records Table')\n"
270
+ " st.dataframe(df, use_container_width=True)\n"
271
+ )
272
+
273
+ # Initial sample CSV records for offline verification
274
+ files["data/iss_locations.csv"] = (
275
+ "timestamp,iso_datetime,latitude,longitude,source\n"
276
+ "1730560000,2024-11-02 15:06:40 UTC,14.8521,52.1245,Open-Notify\n"
277
+ "1730560005,2024-11-02 15:06:45 UTC,15.1204,52.4120,Open-Notify\n"
278
+ "1730560010,2024-11-02 15:06:50 UTC,15.3887,52.6998,Open-Notify\n"
279
+ "1730560015,2024-11-02 15:06:55 UTC,15.6569,52.9878,Open-Notify\n"
280
+ "1730560020,2024-11-02 15:07:00 UTC,15.9251,53.2761,Open-Notify\n"
281
+ )
282
+
283
+ # README.md
284
+ if mode == "learning":
285
+ readme_content = (
286
+ "# Program 11 — ISS Real-Time Location Tracking & Trajectory Analysis\n\n"
287
+ "## 1. Overview\n"
288
+ "This practical tracks and analyzes the real-time orbit of the International Space Station (ISS):\n"
289
+ "- Collects authentic orbital coordinates from **Open-Notify API** (`http://api.open-notify.org/iss-now.json`) with verified fallback to **WhereTheISS** (`https://api.wheretheiss.at/v1/satellites/25544`).\n"
290
+ "- Samples 100 observations at 5-second intervals.\n"
291
+ "- Computes ground distance using the Haversine formula and estimates orbital velocity.\n"
292
+ "- Plots Latitude vs Time, Longitude vs Time, and renders a Streamlit geospatial trajectory dashboard.\n\n"
293
+ "## 2. Collection Time Estimation\n"
294
+ "The time required to collect the full 100-sample dataset is calculated as:\n"
295
+ "$$\\text{Total Duration} = N \\times \\Delta t = 100 \\times 5\\text{ seconds} = 500\\text{ seconds} \\approx 8.33\\text{ minutes}$$\n\n"
296
+ "## 3. Step-by-Step Execution Guide\n\n"
297
+ "### Step A: Install Dependencies\n"
298
+ "```bash\n"
299
+ "pip install -r requirements.txt\n"
300
+ "```\n\n"
301
+ "### Step B: Collect 100 Live Records\n"
302
+ "```bash\n"
303
+ "python collector.py --count 100 --interval 5\n"
304
+ "```\n\n"
305
+ "### Step C: Run Statistical & Trajectory Analysis\n"
306
+ "```bash\n"
307
+ "python analyzer.py\n"
308
+ "```\n"
309
+ "This generates 3 charts in `output/`:\n"
310
+ "- `iss_latitude_trend.png`\n"
311
+ "- `iss_longitude_trend.png`\n"
312
+ "- `iss_ground_track.png`\n\n"
313
+ "### Step D: Launch Interactive Dashboard\n"
314
+ "```bash\n"
315
+ "streamlit run dashboard.py\n"
316
+ "```\n\n"
317
+ "## 4. Viva Questions and Answers\n"
318
+ "**Q1: What is the approximate speed and altitude of the ISS?** \n"
319
+ "A: The ISS orbits Earth at an altitude of approximately 400–420 km and travels at approximately 27,600 km/h (~7.66 km/s), completing one full orbit every 90–93 minutes.\n\n"
320
+ "**Q2: Why does the ISS ground track appear sinusoidal on a 2D map?** \n"
321
+ "A: The ISS has an orbital inclination of 51.6° to Earth's equator. Projecting its circular orbit onto a cylindrical 2D map projection yields a characteristic sine-wave trajectory as Earth rotates underneath.\n"
322
+ )
323
+ else:
324
+ readme_content = (
325
+ "# Program 11 — ISS Location Tracking (Exam Mode)\n\n"
326
+ "## Execution\n"
327
+ "1. Collect telemetry:\n"
328
+ " ```bash\n"
329
+ " python collector.py --count 100 --interval 5\n"
330
+ " ```\n"
331
+ "2. Analyze records & generate plots:\n"
332
+ " ```bash\n"
333
+ " python analyzer.py\n"
334
+ " ```\n"
335
+ "3. View dashboard:\n"
336
+ " ```bash\n"
337
+ " streamlit run dashboard.py\n"
338
+ " ```\n\n"
339
+ "## Viva Q&A\n"
340
+ "- **Q: How long does collecting 100 samples take?** \n"
341
+ " A: 100 samples * 5 seconds = 500 seconds (~8 minutes 20 seconds).\n"
342
+ "- **Q: What is the Haversine formula?** \n"
343
+ " A: An equation in spherical trigonometry that calculates the great-circle distance between two pairs of latitude and longitude coordinates on a sphere.\n"
344
+ )
345
+
346
+ files["README.md"] = readme_content
347
+ return files