MetaImGUI 1.0.0
ImGui Application Template for C++20
Loading...
Searching...
No Matches
ISSTracker.cpp
Go to the documentation of this file.
1/*
2 MetaImGUI
3 Copyright (C) 2026 A P Nicholson
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
17*/
18
19#include "ISSTracker.h"
20
21#include "Logger.h"
22
23#include <nlohmann/json.hpp>
24
25#include <chrono>
26#include <stop_token>
27#include <thread>
28
29// HTTP requests using libcurl (cross-platform)
30#include <curl/curl.h>
31
32namespace MetaImGUI {
33
34ISSTracker::ISSTracker() : m_tracking(false) {}
35
39
40void ISSTracker::StartTracking(std::function<void(const ISSPosition&)> callback) {
41 const std::lock_guard<std::mutex> lock(m_threadMutex);
42
43 if (m_tracking) {
44 LOG_INFO("ISS Tracker: Already tracking, skipping");
45 return;
46 }
47
48 m_tracking = true;
49
50 // Store callback under lock protection
51 {
52 const std::lock_guard<std::mutex> callbackLock(m_callbackMutex);
53 m_callback = callback;
54 }
55
56 // C++20: std::jthread with stop_token for clean cancellation
57 // jthread creates its own stop_source internally
58 m_trackingThread = std::jthread([this](const std::stop_token& stopToken) { TrackingLoop(stopToken); });
59
60 LOG_INFO("ISS Tracker: Started tracking");
61}
62
64 const std::lock_guard<std::mutex> lock(m_threadMutex);
65
66 if (!m_tracking) {
67 return;
68 }
69
70 // Request stop using jthread's internal stop_source
71 m_trackingThread.request_stop();
72 m_tracking = false;
73
74 LOG_INFO("ISS Tracker: Stopped tracking");
75}
76
78 return m_tracking;
79}
80
82 const std::lock_guard<std::mutex> lock(m_dataMutex);
83 return m_currentPosition;
84}
85
86void ISSTracker::GetPositionHistory(std::vector<double>& latitudes, std::vector<double>& longitudes) const {
87 const std::lock_guard<std::mutex> lock(m_dataMutex);
88
89 latitudes.clear();
90 longitudes.clear();
91 latitudes.reserve(m_positionHistory.size());
92 longitudes.reserve(m_positionHistory.size());
93
94 for (const auto& pos : m_positionHistory) {
95 if (pos.valid) {
96 latitudes.push_back(pos.latitude);
97 longitudes.push_back(pos.longitude);
98 }
99 }
100}
101
103 return FetchPositionImpl();
104}
105
106void ISSTracker::TrackingLoop(const std::stop_token& stopToken) {
107 while (!stopToken.stop_requested()) {
108 try {
109 const ISSPosition position = FetchPositionImpl();
110
111 // Check if stop was requested after fetch (important for slow networks)
112 if (stopToken.stop_requested()) {
113 LOG_INFO("ISS Tracker: Stop requested, discarding fetched data");
114 break;
115 }
116
117 if (position.valid) {
118 // Update current position and add to history
119 {
120 const std::lock_guard<std::mutex> lock(m_dataMutex);
121 m_currentPosition = position;
122 AddToHistory(position);
123 }
124
125 // Invoke callback if set (copy under lock to avoid data race)
126 std::function<void(const ISSPosition&)> callback;
127 {
128 const std::lock_guard<std::mutex> lock(m_callbackMutex);
129 callback = m_callback;
130 }
131
132 if (callback) {
133 try {
134 callback(position);
135 } catch (const std::exception& e) {
136 LOG_ERROR("ISS Tracker: Callback threw exception: {}", e.what());
137 } catch (...) {
138 LOG_ERROR("ISS Tracker: Callback threw unknown exception");
139 }
140 }
141
142 LOG_INFO("ISS Tracker: Position updated - Lat: {}, Long: {}, Alt: {} km, Vel: {} km/h",
143 position.latitude, position.longitude, position.altitude, position.velocity);
144 }
145 } catch (const std::exception& e) {
146 LOG_ERROR("ISS Tracker: Error fetching position: {}", e.what());
147 } catch (...) {
148 LOG_ERROR("ISS Tracker: Unknown error fetching position");
149 }
150
151 // Wait 5 seconds before next update (if not stopped)
152 auto start = std::chrono::steady_clock::now();
153 while (!stopToken.stop_requested()) {
154 auto now = std::chrono::steady_clock::now();
155 auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - start).count();
156 if (elapsed >= 5) {
157 break;
158 }
159 std::this_thread::sleep_for(std::chrono::milliseconds(100));
160 }
161 }
162
163 LOG_INFO("ISS Tracker: Tracking loop exited");
164}
165
166ISSPosition ISSTracker::FetchPositionImpl() {
167 ISSPosition position;
168 position.valid = false;
169
170 try {
171 const std::string jsonResponse = FetchJSON(ISS_API_URL);
172 if (jsonResponse.empty()) {
173 LOG_ERROR("ISS Tracker: Empty response from server");
174 return position;
175 }
176
177 position = ParseJSON(jsonResponse);
178 } catch (const std::bad_alloc& e) {
179 LOG_ERROR("ISS Tracker: Memory allocation failed: {}", e.what());
180 } catch (const std::exception& e) {
181 LOG_ERROR("ISS Tracker: Fetch failed: {}", e.what());
182 } catch (...) {
183 LOG_ERROR("ISS Tracker: Unknown error during fetch");
184 }
185
186 return position;
187}
188
189namespace {
190// Callback for libcurl to write response data
191size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
192 static_cast<std::string*>(userp)->append(static_cast<char*>(contents), size * nmemb);
193 return size * nmemb;
194}
195} // namespace
196
197std::string ISSTracker::FetchJSON(const std::string& url) {
198 std::string result;
199
200 CURL* curl = curl_easy_init();
201 if (curl == nullptr) {
202 LOG_ERROR("ISS Tracker: Failed to initialize CURL");
203 return result;
204 }
205
206 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
207 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
208 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
209 curl_easy_setopt(curl, CURLOPT_USERAGENT, "MetaImGUI-ISSTracker/1.0");
210 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
211 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // Increased to 30 seconds for slow networks
212 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
213 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
214
215 const CURLcode res = curl_easy_perform(curl);
216
217 if (res != CURLE_OK) {
218 LOG_ERROR("ISS Tracker: Request failed: {}", curl_easy_strerror(res));
219 result.clear();
220 }
221
222 curl_easy_cleanup(curl);
223
224 return result;
225}
226
227ISSPosition ISSTracker::ParseJSON(const std::string& jsonResponse) {
228 ISSPosition position;
229 position.valid = false;
230
231 try {
232 auto json = nlohmann::json::parse(jsonResponse);
233
234 // Parse required fields
235 if (json.contains("latitude") && json.contains("longitude")) {
236 position.latitude = json["latitude"].get<double>();
237 position.longitude = json["longitude"].get<double>();
238
239 // Parse optional fields
240 if (json.contains("altitude")) {
241 position.altitude = json["altitude"].get<double>();
242 }
243 if (json.contains("velocity")) {
244 position.velocity = json["velocity"].get<double>();
245 }
246 if (json.contains("timestamp")) {
247 position.timestamp = json["timestamp"].get<long>();
248 }
249
250 position.valid = true;
251 } else {
252 LOG_ERROR("ISS Tracker: Missing required fields in JSON response");
253 }
254 } catch (const nlohmann::json::parse_error& e) {
255 LOG_ERROR("ISS Tracker: JSON parse error: {}", e.what());
256 } catch (const nlohmann::json::type_error& e) {
257 LOG_ERROR("ISS Tracker: JSON type error: {}", e.what());
258 } catch (const std::exception& e) {
259 LOG_ERROR("ISS Tracker: Error parsing JSON: {}", e.what());
260 }
261
262 return position;
263}
264
265void ISSTracker::AddToHistory(const ISSPosition& position) {
266 // Note: Caller must hold m_dataMutex
267
268 if (!position.valid) {
269 return;
270 }
271
272 // Add to history
273 m_positionHistory.push_back(position);
274
275 // Keep only the last maxHistorySize positions
276 while (m_positionHistory.size() > m_maxHistorySize) {
277 m_positionHistory.pop_front();
278 }
279}
280
281} // namespace MetaImGUI
nlohmann::json json
#define LOG_INFO(...)
Definition Logger.h:218
#define LOG_ERROR(...)
Definition Logger.h:220
void GetPositionHistory(std::vector< double > &latitudes, std::vector< double > &longitudes) const
Get position history for orbit trail (thread-safe)
ISSPosition GetCurrentPosition() const
Get the current ISS position (thread-safe)
void StopTracking()
Stop tracking.
void StartTracking(std::function< void(const ISSPosition &)> callback=nullptr)
Start tracking ISS position asynchronously.
bool IsTracking() const
Check if tracking is active.
ISSPosition FetchPositionSync()
Manually fetch ISS position once (synchronous)