/feed/flights_thread.c
C | 406 lines | 295 code | 73 blank | 38 comment | 47 complexity | 108e81301b7a2de0cf14614afaa5a8fc MD5 | raw file
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <pthread.h>
- //#include <errno.h>
- #include <unistd.h>
- #include <time.h>
- #include <math.h>
- /* project headers - the order is important ... ugh */
- #include "config.h"
- #include "alive.h"
- #include "stats.h"
- #include "logging.h"
- #include "db_funcs.h"
- #include "flights_thread.h"
- #define LOCAL_BUF_SIZE 256
- #define FULL_BOX 1.5 /* a box 1.5 degrees square will be about 80nmx90nm varrying a bit across the USA */
- #define HALF_BOX (FULL_BOX) /* we will create a search area 'half of box size' in all directions */
- #define OUTER_LIMIT 99999 /* some place way far away / used as a starting point for a 'nearest place' loop */
- #define DEG2RAD(d) (((d)*M_PI)/180)
- #define RAD2DEG(d) (((d)*180)/M_PI)
- static float getDistance (float lat1, float lon1, float lat2, float lon2)
- {
- float result;
- result = (RAD2DEG(
- acos(
- (sin(DEG2RAD(lat1)) * sin(DEG2RAD(lat2))) +
- (cos(DEG2RAD(lat1)) * cos(DEG2RAD(lat2)) * cos(DEG2RAD(lon1 - lon2)))
- )
- )
- * 60);
- return result;
- }
- static int getCourse (float lat1, float lon1, float lat2, float lon2)
- {
- int result;
- result = (round (
- RAD2DEG(
- atan2(
- ((DEG2RAD(lon2)-DEG2RAD(lon1)) * cos(DEG2RAD(lat1))),
- (DEG2RAD(lat2)-DEG2RAD(lat1))
- )
- )
- )
- );
- if (result < 0) /* course will be between -180 and 180 */
- result += 360;
- return result;
- }
- static int getNearestPlace (sql_parms *db, float lat, float lon, int *coursep, float *distancep)
- {
- float distance, this_distance;
- float this_lat, this_lon;
- int place;
- int course;
- if (coursep)
- *coursep = 0;
- if (distancep)
- *distancep = 0;
-
- /*
- assuming we find anything, we encode the result as ppppphhddd
- 5 digit place id, 2 digit degree heading, 3 digit distance in tenths
- */
- if (!dbOpenAsNeeded(db))
- return 0;
- dbCleanup(db);
-
- /* initial query if for the area +/- 0.5 degrees lat/lon around point */
- /* results will be 0=id, 1=latitude, 2=longitude */
- if (!dbQueryCommandStoreResult (db,
- "SELECT id, Latitude, Longitude FROM airports WHERE (Latitude>'%f') AND (Latitude<'%f') AND (Longitude>'%f') AND (Longitude<'%f') AND Size='A'",
- lat-HALF_BOX, lat+HALF_BOX, lon-HALF_BOX,lon+HALF_BOX))
- {
- dbCleanup(db);
- return 0;
- }
- place = 0;
- course = 0;
- distance = OUTER_LIMIT; /* this guarnatees we start with a point outside the search area */
-
- while (dbFetchRow(db)) {
- this_lat = dbGetColumnFloat(db, 1);
- this_lon = dbGetColumnFloat(db, 2);
- this_distance = getDistance (lat, lon, this_lat, this_lon);
- if (this_distance < distance) {
- distance = this_distance;
- /* we use this order of the points to get where we are 'from' the placename */
- /* the oposite of the course is what we fly to get 'to' the placename */
- course = getCourse (this_lat, this_lon, lat, lon);
- place = dbGetColumnInt(db, 0);
- }
- }
- if (distance == OUTER_LIMIT)
- return 1; // this is a dummy entry in the airports database
- distance = (round(distance*100)/100);
- if (coursep)
- *coursep = course;
- if (distancep)
- *distancep = distance;
-
- return place;
- }
- int processFlights (flights_thread_parms *parms, char *callsign, int duration)
- {
- config_settings *conf;
- sql_parms *db; /* local varable to shorted code */
- time_t processs_begin; /* we will watch how much time we spend processing flights and quit if its too long */
- bool more_flights = true;
- bool found_gap;
- int flight_count = 0;
- time_t current, previous; /* used to detect gap between flights */
- int speed, max_speed, avg_speed;
- long flight;
- int record_count;
- time_t first, last; /* first and last datetimes for a flight */
- int first_place, last_place;
- int first_course, last_course;
- float first_distance, last_distance;
- float first_lat, first_lon, last_lat, last_lon;
- char first_string[TIME_STRING_LENGTH], last_string[TIME_STRING_LENGTH];
- #define BEGINOFTIME ((unsigned long) 1) /* when EPOC started ... 01-JAN-1970 00:00:01 GMT */
- #define ENDOFTIME ((unsigned long) 4294967265) /* when EPOC will run out ... 07-FEB-2106 06:28:15 GMT */
- db = &(parms->db); /* local varable to shorted code */
- conf = parms->conf; /* local varable to shorted code */
- LogMessageNowExt (conf, MTYPE_DEBUG, LMSG_FLIGHTS,
- "processing flights for %s",
- callsign);
-
- processs_begin = time(NULL);
-
- /* this is not needed if we were called from processPacket but we want to support being call at other times */
- if (!dbOpenAsNeeded(db))
- return 0;
- while (more_flights) {
- flight = 0;
- first_place = 0; last_place = 0;
- first = BEGINOFTIME; last = BEGINOFTIME;
- current = BEGINOFTIME; previous = BEGINOFTIME;
- record_count = 0;
- found_gap = false;
- speed = 0; max_speed = 0; avg_speed = 0;
- /* safety check so we don't risk a memory leak */
- dbCleanup(db);
- /* find all un-initialized track data for callsign */
- if (!dbQueryCommandStoreResult (db,
- "SELECT ReportTime, Latitude, Longitude, Speed FROM aprstrack "
- "WHERE CallsignSSID='%s' AND FlightNum='0' ORDER BY ReportTime ASC",
- callsign))
- {
- more_flights = false;
- break; /* break outer while loop */
- }
- if (dbGetFieldCount(db)) {
- /* loop each track record and update with a new flight number */
- /* fields: 0=ReportTime, 1=Latitude, 2=Longitude, 3=Speed */
- while (dbFetchRow(db)) {
- previous = current;
- current = dbGetColumnTime(db, 0);
- speed = dbGetColumnInt(db, 3);
- avg_speed += speed; // we add them all up and will divice by 'count' at the end
- max_speed = MAX(speed, max_speed);
-
- if ((current > BEGINOFTIME) && (previous > BEGINOFTIME)) {
- /* check if we have encountered a large gap in the records - indicates start of a new flight */
- /* detect a pitstop that was quicker than the FlightSeparation */ /* XYZZY TODO should this speed threashold be in config */
- if (((current - previous) > conf->FlightSeparation) ||
- (((current - previous) > 10) && (max_speed < conf->FlightSpeed)))
- {
- found_gap = true;
- break; /* break from inner loop - we have found the end of a flight */
- }
- }
- record_count++;
-
- last = current;
- last_lat = dbGetColumnFloat(db, 1);
- last_lon = dbGetColumnFloat(db, 2);
- if (!(first > BEGINOFTIME)) {
- first = current;
- first_lat = last_lat;
- first_lon = last_lon;
- convertDatetimeToString (first, first_string, TIME_STRING_LENGTH);
- }
- }
-
- dbCleanup (db);
- if (first > BEGINOFTIME) {
- flight = first;
- /* we already have the string version of 'first' and now we get it for 'last */
- convertDatetimeToString (last, last_string, TIME_STRING_LENGTH);
- LogMessageNowExt (conf, MTYPE_INFO, LMSG_FLIGHTS,
- "proccessing flight for %s from %s to %s with %d packets",
- callsign, first_string, last_string, record_count );
- /* if we did not find a gap, we likely detected a portion of an active flight. we may not want toprocess it */
- if (!found_gap && (max_speed > conf->FlightSpeed)) {
- if (!(last < (time(NULL) - conf->FlightSeparation))) {
- /* we are going to leave this one for later */
- LogMessageNowExt (conf, MTYPE_NORM, LMSG_FLIGHTS,
- "skipping active flight for %s",
- callsign);
- more_flights = false;
- break; /* break outer while loop */
- }
- }
- /*
- there are three scenarios to address:
- normal flight with a first and last or
- orphan records so we only have a first
- tracks that ere porbably not real flights (eg airplane trackers in cars
- for orpan records and non-flights, we tag the track table record with a never-used flight ID
- and donot create the flight for it.
- for the non-flights, we will risk lettings some thru and risk dropping legitimate flights
- TODO XYZZY: the flight record threshold and speed threasholds should be in the config file
- */
- avg_speed = avg_speed / record_count;
- if ((first == last) ||
- (record_count < conf->MinFlight) ||
- (max_speed < conf->MinSpeed) ||
- (avg_speed < conf->FlightSpeed))
- flight = BEGINOFTIME;
- /* the UPDATES and the INSERT should be made atomic for rollback but we will risk it */
- /* we have a complete flight so lets tag all of the records */
- if (!dbExecuteCommand (db,
- "UPDATE aprstrack SET FlightNum='%ld' WHERE CallsignSSID='%s' AND ReportTime>='%s' AND ReportTime<='%s'",
- (signed long)flight, callsign, first_string, last_string))
- {
- more_flights = false;
- break; /* break outer while loop */
- }
- if (flight > BEGINOFTIME) {
- /* if we have a real flight, add it to the database */
- first_place = getNearestPlace (db, first_lat, first_lon, &first_course, &first_distance);
- last_place = getNearestPlace (db, last_lat, last_lon, &last_course, &last_distance );
- if (!dbExecuteCommand (db,
- "UPDATE aprsposits SET Location='%d', GoodLocation='%d' WHERE CallsignSSID='%s'",
- last_place, last_place, callsign))
- {
- more_flights = false;
- break;
- }
-
- /* we create a flight record to correspond to all of the track data */
- if (!dbExecuteCommand(db,
- "INSERT INTO flights SET CallsignSSID='%s', FlightNum='%ld', RecordCount='%d', "
- "FlightTime1='%s', Location1='%d', Course1='%d', Distance1='%f', Latitude1='%f', Longitude1='%f', "
- "FlightTime2='%s', Location2='%d', Course2='%d', Distance2='%f', Latitude2='%f', Longitude2='%f'",
- callsign, flight, record_count,
- first_string, first_place, first_course, first_distance, first_lat, first_lon,
- last_string, last_place, last_course, last_distance, last_lat, last_lon))
- {
- more_flights = false;
- break; /* break outer while loop */
- }
- }
- else {
- if (!dbExecuteCommand (db,
- "UPDATE aprsposits SET Location='1' WHERE CallsignSSID='%s'",
- callsign))
- {
- more_flights = false;
- break;
- }
- }
- if (more_flights)
- flight_count++;
- /* if we've spent too long at this, we stop and will pick it up in the furture */
- if (time(NULL) > (processs_begin + duration)) { /* only accurate to the second so this can be as much as +1 seconds long */
- LogMessageNowExt (conf, MTYPE_WARN, LMSG_FLIGHTS,
- "exceeded process time for %s - processed %d flight(s) in %d seconds",
- callsign, flight_count, (int)(time(NULL) - processs_begin));
- more_flights = false;
- }
- }
- else
- more_flights = false;
- }
- else
- more_flights = false;
- }/* while loop for more_flights */
- LogMessageNowExt (conf, MTYPE_INFO, LMSG_FLIGHTS,
- "processed %d flight(s) for %s",
- flight_count, callsign);
- return 1;
- }
- void *threadProcessFlights(void *arg)
- {
- flights_thread_parms *parms;
- config_settings *conf;
- sql_parms *db;
- bool keep_running = true;
- char *p, *list;
- char msg[LOCAL_BUF_SIZE];
-
- parms = (flights_thread_parms *)arg;
- conf = parms->conf;
- db = &(parms->db);
- updateThreadAlive (&(parms->clock));
-
- if (!dbInit (db, conf))
- return 0;
- pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
- pthread_detach(pthread_self());
- LogMessageNow (conf, -(MTYPE_INFO), LMSG_FT_START, NULL );
-
- do {
- p = NULL; list = NULL;
- sleep (conf->FlightsAlive-5); /* sleep for 15 minutes */
- updateThreadAlive (&(parms->clock));
- /* very cool SQL!! gets a list of all aircraft with unprocessed flights as a single comma delimeted string */
- 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)",
- conf->FlightSeparation) &&
- dbFetchRow(db))
- {
- p = dbGetColumnString (db, 0);
- if (p && *p) {
- list = malloc(strlen(p) + 1);
- if (list) {
- strcpy (list, p);
- p = list;
- }
- }
- }
- dbCleanup(db);
- if (list) {
- snprintf (msg, LOCAL_BUF_SIZE, "process list: %s", list);
- LogMessageNow (conf, MTYPE_INFO, LMSG_FLIGHTS, msg);
- p = strtok (list, ",");
- while (p) {
- processFlights (parms, p, 2);
- updateThreadAlive (&(parms->clock));
- sleep (15); /* sleep 15 seconds between aircraft */
- p = strtok(NULL, ",");
- }
- free (list);
- }
-
- p = NULL;
- list = NULL;
- updateThreadAlive (&(parms->clock));
- } while (keep_running);
- dbClose(db);
- LogMessageNow (conf, MTYPE_ERROR, LMSG_PT_TERM, NULL );
- return NULL;
- }