PageRenderTime 54ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/feed/flights_thread.c

https://gitlab.com/bradanlane/AirPRS
C | 406 lines | 295 code | 73 blank | 38 comment | 47 complexity | 108e81301b7a2de0cf14614afaa5a8fc MD5 | raw file
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <pthread.h>
  5. //#include <errno.h>
  6. #include <unistd.h>
  7. #include <time.h>
  8. #include <math.h>
  9. /* project headers - the order is important ... ugh */
  10. #include "config.h"
  11. #include "alive.h"
  12. #include "stats.h"
  13. #include "logging.h"
  14. #include "db_funcs.h"
  15. #include "flights_thread.h"
  16. #define LOCAL_BUF_SIZE 256
  17. #define FULL_BOX 1.5 /* a box 1.5 degrees square will be about 80nmx90nm varrying a bit across the USA */
  18. #define HALF_BOX (FULL_BOX) /* we will create a search area 'half of box size' in all directions */
  19. #define OUTER_LIMIT 99999 /* some place way far away / used as a starting point for a 'nearest place' loop */
  20. #define DEG2RAD(d) (((d)*M_PI)/180)
  21. #define RAD2DEG(d) (((d)*180)/M_PI)
  22. static float getDistance (float lat1, float lon1, float lat2, float lon2)
  23. {
  24. float result;
  25. result = (RAD2DEG(
  26. acos(
  27. (sin(DEG2RAD(lat1)) * sin(DEG2RAD(lat2))) +
  28. (cos(DEG2RAD(lat1)) * cos(DEG2RAD(lat2)) * cos(DEG2RAD(lon1 - lon2)))
  29. )
  30. )
  31. * 60);
  32. return result;
  33. }
  34. static int getCourse (float lat1, float lon1, float lat2, float lon2)
  35. {
  36. int result;
  37. result = (round (
  38. RAD2DEG(
  39. atan2(
  40. ((DEG2RAD(lon2)-DEG2RAD(lon1)) * cos(DEG2RAD(lat1))),
  41. (DEG2RAD(lat2)-DEG2RAD(lat1))
  42. )
  43. )
  44. )
  45. );
  46. if (result < 0) /* course will be between -180 and 180 */
  47. result += 360;
  48. return result;
  49. }
  50. static int getNearestPlace (sql_parms *db, float lat, float lon, int *coursep, float *distancep)
  51. {
  52. float distance, this_distance;
  53. float this_lat, this_lon;
  54. int place;
  55. int course;
  56. if (coursep)
  57. *coursep = 0;
  58. if (distancep)
  59. *distancep = 0;
  60. /*
  61. assuming we find anything, we encode the result as ppppphhddd
  62. 5 digit place id, 2 digit degree heading, 3 digit distance in tenths
  63. */
  64. if (!dbOpenAsNeeded(db))
  65. return 0;
  66. dbCleanup(db);
  67. /* initial query if for the area +/- 0.5 degrees lat/lon around point */
  68. /* results will be 0=id, 1=latitude, 2=longitude */
  69. if (!dbQueryCommandStoreResult (db,
  70. "SELECT id, Latitude, Longitude FROM airports WHERE (Latitude>'%f') AND (Latitude<'%f') AND (Longitude>'%f') AND (Longitude<'%f') AND Size='A'",
  71. lat-HALF_BOX, lat+HALF_BOX, lon-HALF_BOX,lon+HALF_BOX))
  72. {
  73. dbCleanup(db);
  74. return 0;
  75. }
  76. place = 0;
  77. course = 0;
  78. distance = OUTER_LIMIT; /* this guarnatees we start with a point outside the search area */
  79. while (dbFetchRow(db)) {
  80. this_lat = dbGetColumnFloat(db, 1);
  81. this_lon = dbGetColumnFloat(db, 2);
  82. this_distance = getDistance (lat, lon, this_lat, this_lon);
  83. if (this_distance < distance) {
  84. distance = this_distance;
  85. /* we use this order of the points to get where we are 'from' the placename */
  86. /* the oposite of the course is what we fly to get 'to' the placename */
  87. course = getCourse (this_lat, this_lon, lat, lon);
  88. place = dbGetColumnInt(db, 0);
  89. }
  90. }
  91. if (distance == OUTER_LIMIT)
  92. return 1; // this is a dummy entry in the airports database
  93. distance = (round(distance*100)/100);
  94. if (coursep)
  95. *coursep = course;
  96. if (distancep)
  97. *distancep = distance;
  98. return place;
  99. }
  100. int processFlights (flights_thread_parms *parms, char *callsign, int duration)
  101. {
  102. config_settings *conf;
  103. sql_parms *db; /* local varable to shorted code */
  104. time_t processs_begin; /* we will watch how much time we spend processing flights and quit if its too long */
  105. bool more_flights = true;
  106. bool found_gap;
  107. int flight_count = 0;
  108. time_t current, previous; /* used to detect gap between flights */
  109. int speed, max_speed, avg_speed;
  110. long flight;
  111. int record_count;
  112. time_t first, last; /* first and last datetimes for a flight */
  113. int first_place, last_place;
  114. int first_course, last_course;
  115. float first_distance, last_distance;
  116. float first_lat, first_lon, last_lat, last_lon;
  117. char first_string[TIME_STRING_LENGTH], last_string[TIME_STRING_LENGTH];
  118. #define BEGINOFTIME ((unsigned long) 1) /* when EPOC started ... 01-JAN-1970 00:00:01 GMT */
  119. #define ENDOFTIME ((unsigned long) 4294967265) /* when EPOC will run out ... 07-FEB-2106 06:28:15 GMT */
  120. db = &(parms->db); /* local varable to shorted code */
  121. conf = parms->conf; /* local varable to shorted code */
  122. LogMessageNowExt (conf, MTYPE_DEBUG, LMSG_FLIGHTS,
  123. "processing flights for %s",
  124. callsign);
  125. processs_begin = time(NULL);
  126. /* this is not needed if we were called from processPacket but we want to support being call at other times */
  127. if (!dbOpenAsNeeded(db))
  128. return 0;
  129. while (more_flights) {
  130. flight = 0;
  131. first_place = 0; last_place = 0;
  132. first = BEGINOFTIME; last = BEGINOFTIME;
  133. current = BEGINOFTIME; previous = BEGINOFTIME;
  134. record_count = 0;
  135. found_gap = false;
  136. speed = 0; max_speed = 0; avg_speed = 0;
  137. /* safety check so we don't risk a memory leak */
  138. dbCleanup(db);
  139. /* find all un-initialized track data for callsign */
  140. if (!dbQueryCommandStoreResult (db,
  141. "SELECT ReportTime, Latitude, Longitude, Speed FROM aprstrack "
  142. "WHERE CallsignSSID='%s' AND FlightNum='0' ORDER BY ReportTime ASC",
  143. callsign))
  144. {
  145. more_flights = false;
  146. break; /* break outer while loop */
  147. }
  148. if (dbGetFieldCount(db)) {
  149. /* loop each track record and update with a new flight number */
  150. /* fields: 0=ReportTime, 1=Latitude, 2=Longitude, 3=Speed */
  151. while (dbFetchRow(db)) {
  152. previous = current;
  153. current = dbGetColumnTime(db, 0);
  154. speed = dbGetColumnInt(db, 3);
  155. avg_speed += speed; // we add them all up and will divice by 'count' at the end
  156. max_speed = MAX(speed, max_speed);
  157. if ((current > BEGINOFTIME) && (previous > BEGINOFTIME)) {
  158. /* check if we have encountered a large gap in the records - indicates start of a new flight */
  159. /* detect a pitstop that was quicker than the FlightSeparation */ /* XYZZY TODO should this speed threashold be in config */
  160. if (((current - previous) > conf->FlightSeparation) ||
  161. (((current - previous) > 10) && (max_speed < conf->FlightSpeed)))
  162. {
  163. found_gap = true;
  164. break; /* break from inner loop - we have found the end of a flight */
  165. }
  166. }
  167. record_count++;
  168. last = current;
  169. last_lat = dbGetColumnFloat(db, 1);
  170. last_lon = dbGetColumnFloat(db, 2);
  171. if (!(first > BEGINOFTIME)) {
  172. first = current;
  173. first_lat = last_lat;
  174. first_lon = last_lon;
  175. convertDatetimeToString (first, first_string, TIME_STRING_LENGTH);
  176. }
  177. }
  178. dbCleanup (db);
  179. if (first > BEGINOFTIME) {
  180. flight = first;
  181. /* we already have the string version of 'first' and now we get it for 'last */
  182. convertDatetimeToString (last, last_string, TIME_STRING_LENGTH);
  183. LogMessageNowExt (conf, MTYPE_INFO, LMSG_FLIGHTS,
  184. "proccessing flight for %s from %s to %s with %d packets",
  185. callsign, first_string, last_string, record_count );
  186. /* if we did not find a gap, we likely detected a portion of an active flight. we may not want toprocess it */
  187. if (!found_gap && (max_speed > conf->FlightSpeed)) {
  188. if (!(last < (time(NULL) - conf->FlightSeparation))) {
  189. /* we are going to leave this one for later */
  190. LogMessageNowExt (conf, MTYPE_NORM, LMSG_FLIGHTS,
  191. "skipping active flight for %s",
  192. callsign);
  193. more_flights = false;
  194. break; /* break outer while loop */
  195. }
  196. }
  197. /*
  198. there are three scenarios to address:
  199. normal flight with a first and last or
  200. orphan records so we only have a first
  201. tracks that ere porbably not real flights (eg airplane trackers in cars
  202. for orpan records and non-flights, we tag the track table record with a never-used flight ID
  203. and donot create the flight for it.
  204. for the non-flights, we will risk lettings some thru and risk dropping legitimate flights
  205. TODO XYZZY: the flight record threshold and speed threasholds should be in the config file
  206. */
  207. avg_speed = avg_speed / record_count;
  208. if ((first == last) ||
  209. (record_count < conf->MinFlight) ||
  210. (max_speed < conf->MinSpeed) ||
  211. (avg_speed < conf->FlightSpeed))
  212. flight = BEGINOFTIME;
  213. /* the UPDATES and the INSERT should be made atomic for rollback but we will risk it */
  214. /* we have a complete flight so lets tag all of the records */
  215. if (!dbExecuteCommand (db,
  216. "UPDATE aprstrack SET FlightNum='%ld' WHERE CallsignSSID='%s' AND ReportTime>='%s' AND ReportTime<='%s'",
  217. (signed long)flight, callsign, first_string, last_string))
  218. {
  219. more_flights = false;
  220. break; /* break outer while loop */
  221. }
  222. if (flight > BEGINOFTIME) {
  223. /* if we have a real flight, add it to the database */
  224. first_place = getNearestPlace (db, first_lat, first_lon, &first_course, &first_distance);
  225. last_place = getNearestPlace (db, last_lat, last_lon, &last_course, &last_distance );
  226. if (!dbExecuteCommand (db,
  227. "UPDATE aprsposits SET Location='%d', GoodLocation='%d' WHERE CallsignSSID='%s'",
  228. last_place, last_place, callsign))
  229. {
  230. more_flights = false;
  231. break;
  232. }
  233. /* we create a flight record to correspond to all of the track data */
  234. if (!dbExecuteCommand(db,
  235. "INSERT INTO flights SET CallsignSSID='%s', FlightNum='%ld', RecordCount='%d', "
  236. "FlightTime1='%s', Location1='%d', Course1='%d', Distance1='%f', Latitude1='%f', Longitude1='%f', "
  237. "FlightTime2='%s', Location2='%d', Course2='%d', Distance2='%f', Latitude2='%f', Longitude2='%f'",
  238. callsign, flight, record_count,
  239. first_string, first_place, first_course, first_distance, first_lat, first_lon,
  240. last_string, last_place, last_course, last_distance, last_lat, last_lon))
  241. {
  242. more_flights = false;
  243. break; /* break outer while loop */
  244. }
  245. }
  246. else {
  247. if (!dbExecuteCommand (db,
  248. "UPDATE aprsposits SET Location='1' WHERE CallsignSSID='%s'",
  249. callsign))
  250. {
  251. more_flights = false;
  252. break;
  253. }
  254. }
  255. if (more_flights)
  256. flight_count++;
  257. /* if we've spent too long at this, we stop and will pick it up in the furture */
  258. if (time(NULL) > (processs_begin + duration)) { /* only accurate to the second so this can be as much as +1 seconds long */
  259. LogMessageNowExt (conf, MTYPE_WARN, LMSG_FLIGHTS,
  260. "exceeded process time for %s - processed %d flight(s) in %d seconds",
  261. callsign, flight_count, (int)(time(NULL) - processs_begin));
  262. more_flights = false;
  263. }
  264. }
  265. else
  266. more_flights = false;
  267. }
  268. else
  269. more_flights = false;
  270. }/* while loop for more_flights */
  271. LogMessageNowExt (conf, MTYPE_INFO, LMSG_FLIGHTS,
  272. "processed %d flight(s) for %s",
  273. flight_count, callsign);
  274. return 1;
  275. }
  276. void *threadProcessFlights(void *arg)
  277. {
  278. flights_thread_parms *parms;
  279. config_settings *conf;
  280. sql_parms *db;
  281. bool keep_running = true;
  282. char *p, *list;
  283. char msg[LOCAL_BUF_SIZE];
  284. parms = (flights_thread_parms *)arg;
  285. conf = parms->conf;
  286. db = &(parms->db);
  287. updateThreadAlive (&(parms->clock));
  288. if (!dbInit (db, conf))
  289. return 0;
  290. pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  291. pthread_detach(pthread_self());
  292. LogMessageNow (conf, -(MTYPE_INFO), LMSG_FT_START, NULL );
  293. do {
  294. p = NULL; list = NULL;
  295. sleep (conf->FlightsAlive-5); /* sleep for 15 minutes */
  296. updateThreadAlive (&(parms->clock));
  297. /* very cool SQL!! gets a list of all aircraft with unprocessed flights as a single comma delimeted string */
  298. if (dbQueryCommandStoreResult (db, "SELECT GROUP_CONCAT(DISTINCT CallsignSSID ORDER BY CallsignSSID ASC SEPARATOR ',') FROM aprstrack WHERE FlightNum='0' AND ReportTime <= DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d SECOND)",
  299. conf->FlightSeparation) &&
  300. dbFetchRow(db))
  301. {
  302. p = dbGetColumnString (db, 0);
  303. if (p && *p) {
  304. list = malloc(strlen(p) + 1);
  305. if (list) {
  306. strcpy (list, p);
  307. p = list;
  308. }
  309. }
  310. }
  311. dbCleanup(db);
  312. if (list) {
  313. snprintf (msg, LOCAL_BUF_SIZE, "process list: %s", list);
  314. LogMessageNow (conf, MTYPE_INFO, LMSG_FLIGHTS, msg);
  315. p = strtok (list, ",");
  316. while (p) {
  317. processFlights (parms, p, 2);
  318. updateThreadAlive (&(parms->clock));
  319. sleep (15); /* sleep 15 seconds between aircraft */
  320. p = strtok(NULL, ",");
  321. }
  322. free (list);
  323. }
  324. p = NULL;
  325. list = NULL;
  326. updateThreadAlive (&(parms->clock));
  327. } while (keep_running);
  328. dbClose(db);
  329. LogMessageNow (conf, MTYPE_ERROR, LMSG_PT_TERM, NULL );
  330. return NULL;
  331. }