messageboard-2020-06-09-1531.py
01234567890123456789012345678901234567890123456789012345678901234567890123456789









239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281








41564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196








44274428442944304431443244334434443544364437443844394440444144424443444444454446 444744484449445044514452445344544455445644574458445944604461446244634464446544664467








47324733473447354736473747384739474047414742474347444745474647474748474947504751 475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802








57105711571257135714571557165717571857195720572157225723572457255726572757285729  5730  5731 57325733573457355736573757385739574057415742574357445745574657475748574957505751











                            <----SKIPPED LINES---->





#if running on raspberry, then need to prepend path to file names
if RASPBERRY_PI:
  PICKLE_FLIGHTS = MESSAGEBOARD_PATH + PICKLE_FLIGHTS
  PICKLE_DASHBOARD = MESSAGEBOARD_PATH + PICKLE_DASHBOARD
  LOGFILE = MESSAGEBOARD_PATH + LOGFILE
  PICKLE_DUMP_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_DUMP_JSON_FILE
  PICKLE_FA_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_FA_JSON_FILE
  CODE_REPOSITORY = MESSAGEBOARD_PATH

  HISTOGRAM_CONFIG_FILE = WEBSERVER_PATH + HISTOGRAM_CONFIG_FILE
  CONFIG_FILE = WEBSERVER_PATH + CONFIG_FILE
  ROLLING_MESSAGE_FILE = WEBSERVER_PATH + ROLLING_MESSAGE_FILE
  ALL_MESSAGE_FILE = WEBSERVER_PATH + ALL_MESSAGE_FILE
  ROLLING_LOGFILE = WEBSERVER_PATH + ROLLING_LOGFILE
  STDERR_FILE = WEBSERVER_PATH + STDERR_FILE
  BACKUP_FILE = WEBSERVER_PATH + BACKUP_FILE
  SERVICE_VERIFICATION_FILE = WEBSERVER_PATH + SERVICE_VERIFICATION_FILE
  UPTIMES_FILE = WEBSERVER_PATH + UPTIMES_FILE

  HISTOGRAM_IMAGE_PREFIX = WEBSERVER_PATH + WEBSERVER_IMAGE_FOLDER + HISTOGRAM_IMAGE_PREFIX
  HISTOGRAM_IMAGE_HTML = WEBSERVER_PATH + HISTOGRAM_IMAGE_HTML
  HOURLY_IMAGE_FILE = WEBSERVER_PATH + WEBSERVER_IMAGE_FOLDER + HOURLY_IMAGE_FILE
  VERSION_REPOSITORY = WEBSERVER_PATH + VERSION_REPOSITORY



TIMEZONE = 'US/Pacific' # timezone of display
TZ = pytz.timezone(TIMEZONE)

KNOWN_AIRPORTS = ('SJC', 'SFO', 'OAK')  # iata codes that we don't need to expand

SPLITFLAP_CHARS_PER_LINE = 22
SPLITFLAP_LINE_COUNT = 6

DIRECTIONS_4 = ['N', 'E', 'S', 'W']
DIRECTIONS_8 = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
DIRECTIONS_16 = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
                 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']

HOURS = ['12a', ' 1a', ' 2a', ' 3a', ' 4a', ' 5a', ' 6a', ' 7a',
         ' 8a', ' 9a', '10a', '11a', '12p', ' 1p', ' 2p', ' 3p',
         ' 4p', ' 5p', ' 6p', ' 7p', ' 8p', ' 9p', '10p', '11p']




                            <----SKIPPED LINES---->




      max_distance_feet=max_distance_feet,
      max_altitude_feet=max_altitude_feet,
      normalize_factor=normalize_factor,
      exhaustive=exhaustive)
  if position:
    matplotlib.pyplot.subplot(*position)
  matplotlib.pyplot.figure(figsize=figsize_inches)
  values_coordinates = numpy.arange(len(keys))
  matplotlib.pyplot.bar(values_coordinates, values)

  # The filtering may have removed any flight data, or there may be none to start
  if not filtered_data:
    return

  earliest_flight_time = int(filtered_data[0]['now'])
  last_flight_time = int(filtered_data[-1]['now'])
  date_range_string = ' %d flights over last %s hours' % (
      sum(values), SecondsToDdHh(last_flight_time - earliest_flight_time))

  timestamp_string = 'Last updated %s' % EpochDisplayTime(
      time.time(), format_string='%b %-d %H:%M')

  full_title = '\n'.join([title, date_range_string, timestamp_string])

  matplotlib.pyplot.title(full_title)

  matplotlib.pyplot.subplots_adjust(bottom=0.15, left=0.09, right=0.99, top=0.89)

  matplotlib.pyplot.xticks(
      values_coordinates, keys, rotation='vertical', wrap=True,
      horizontalalignment='right',
      verticalalignment='center')


def HistogramSettingsHours(how_much_history):
  """Extracts the desired history (in hours) from the histogram configuration string.

  Args:
    how_much_history: string from the histogram config file.

  Returns:




                            <----SKIPPED LINES---->




    if this_histogram == 'all':
      this_histogram = histogram['generate']
    (key, sort, title, hours) = HistogramSettingsKeySortTitle(
        this_histogram, hours, flights)

    # if multiple histograms are getting generated, this might take a few seconds;
    # logging a heartbeat with each histogram ensures that monitoring.py does not
    # mistake this pause for a hang.
    if heartbeat:
      Heartbeat()

    CreateSingleHistogramChart(
        flights,
        key,
        sort,
        title,
        truncate=histogram.get('truncate', TRUNCATE),
        hours=hours,
        exhaustive=histogram.get('exhaustive', False))
    filename = filename_prefix + histogram['generate'] + '.' + filename_suffix

    matplotlib.pyplot.savefig(filename)
    matplotlib.pyplot.close()
    histograms_generated.append((histogram['generate'], filename))

  return histograms_generated


def MessageboardHistograms(
    flights,
    which_histograms,
    how_much_history,
    max_screens,
    data_summary):
  """Generates multiple split flap screen histograms.

  Args:
    flights: the iterable of the raw data from which the histogram will be generated;
      each element of the iterable is a dictionary, that contains at least the key
      'now', and depending on other parameters, also potentially
      'min_feet' amongst others.
    which_histograms: string paramater indicating which histogram(s) to generate, which




                            <----SKIPPED LINES---->




  Returns:
    List of histogram messages, if text-based histograms are selected; empty list
    otherwise.
  """
  histogram_messages = []

  if histogram_settings['type'] in ('messageboard', 'both'):
    histogram_messages = MessageboardHistograms(
        flights,
        histogram_settings['histogram'],
        histogram_settings['histogram_history'],
        histogram_settings['histogram_max_screens'],
        histogram_settings.get('histogram_data_summary', False))
  if histogram_settings['type'] in ('images', 'both'):

    # Since Google Chrome seems to ignore all instructions to not cache, we need
    # to make sure we do not reuse file names - hence the epoch_string - and then
    # we need to 1) update the histograms.html file with the correct file links, and
    # 2) delete the images that are now obsolete.
    epoch_string = '%d_' % round(time.time())

    generated_histograms = ImageHistograms(
        flights,
        histogram_settings['histogram'],
        histogram_settings['histogram_history'],
        filename_prefix=HISTOGRAM_IMAGE_PREFIX + epoch_string)
    html_lines = ReadFile(HISTOGRAM_IMAGE_HTML).split('\n')
    replaced_images = []
    for identifier, new_filename in generated_histograms:
      # for each histogram, find the html_line with the matching id
      # Example line: <img id='destination' src='images/histogram_destination.png'><p>
      n, line = None, None  # addresses pylint complaint
      for n, line in enumerate(html_lines):
        if identifier in line:
          break
      start_char = line.find(WEBSERVER_IMAGE_FOLDER) + len(WEBSERVER_IMAGE_FOLDER)
      end_character = (
          line.find(HISTOGRAM_IMAGE_SUFFIX, start_char) + len(HISTOGRAM_IMAGE_SUFFIX))
      old_filename = line[start_char:end_character]
      line = line.replace(old_filename, new_filename)
      html_lines[n] = line
      replaced_images.append(line[start_char:end_character])
    new_html = '\n'.join(html_lines)
    WriteFile(HISTOGRAM_IMAGE_HTML, new_html)

    # Remove those obsoleted files
    for f in old_filename:
      if os.path.exists(f):
        try:
          os.remove(f)
        except PermissionError:
          pass

  return histogram_messages


def SaveFlightsByAltitudeDistanceCSV(
    flights,
    max_days=0,
    filename='flights_by_alt_dist.csv',
    precision=100):
  """Extracts hourly histogram into text file for a variety of altitudes and distances.

  Generates a csv with 26 columns:
  - col#1: altitude (in feet)
  - col#2: distance (in feet)
  - cols#3-26: hour of the day

  The first row is a header row; subsequent rows list the number of flights that have
  occurred in the last max_days with an altitude and min distance less than that identified
  in the first two columns. Each row increments elevation or altitude by precision feet,
  up to the max determined by the max altitude and max distance amongst all the flights.




                            <----SKIPPED LINES---->




      (False).
    subsystem: A tuple describing the system; though that description may have multiple
      attributes, the 0th element is the numeric identifier of that system.  monitoring.py
      depends on other attributes of that tuple being present as well.  Since the
      overall system does not have a tuple defined for it, it gets a default identifier
      of 0.
    failure_message: an (optional) message describing why the system / subsystem is
      being disabled or failing.
  """
  versions = (VERSION_MESSAGEBOARD, VERSION_ARDUINO)
  if subsystem:
    subsystem = subsystem[0]
  PickleObjectToFile(
      (time.time(), subsystem, value, versions, failure_message),
      PICKLE_DASHBOARD, True)


def RemoveFile(file):
  """Removes a file if it exists, returning a boolean indicating if it had existed."""
  if os.path.exists(file):


    os.remove(file)


    return True

  return False


def ConfirmNewFlight(flight, flights):
  """Replaces last-seen flight with new flight if otherwise identical but for identifiers.

  Flights are identified by the radio over time by a tuple of identifiers: flight_number
  and squawk.  Due to unknown communication issues, one or the other may not always
  be transmitted. However, as soon as a new flight is identified that has at least one
  of those identifiers, we report on it and log it to the pickle repository, etc.

  This function checks if the newly identified flight is indeed a duplicate of the
  immediate prior flight by virtue of having the same squawk and/or flight number, and
  further, if the paths overlap.  If the paths do not overlap, then its likely that
  the same flight was seen some minutes apart, and should legitimately be treated as
  a different flight.

  If the new flight is an updated version, then we should replace the prior-pickled-to-
  disk flight and replace the last flight in flights with this new version.





                            <----SKIPPED LINES---->





01234567890123456789012345678901234567890123456789012345678901234567890123456789









239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281








41564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196








442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468








473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780    47814782478347844785478647874788478947904791479247934794479547964797479847994800








57085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754











                            <----SKIPPED LINES---->





#if running on raspberry, then need to prepend path to file names
if RASPBERRY_PI:
  PICKLE_FLIGHTS = MESSAGEBOARD_PATH + PICKLE_FLIGHTS
  PICKLE_DASHBOARD = MESSAGEBOARD_PATH + PICKLE_DASHBOARD
  LOGFILE = MESSAGEBOARD_PATH + LOGFILE
  PICKLE_DUMP_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_DUMP_JSON_FILE
  PICKLE_FA_JSON_FILE = MESSAGEBOARD_PATH + PICKLE_FA_JSON_FILE
  CODE_REPOSITORY = MESSAGEBOARD_PATH

  HISTOGRAM_CONFIG_FILE = WEBSERVER_PATH + HISTOGRAM_CONFIG_FILE
  CONFIG_FILE = WEBSERVER_PATH + CONFIG_FILE
  ROLLING_MESSAGE_FILE = WEBSERVER_PATH + ROLLING_MESSAGE_FILE
  ALL_MESSAGE_FILE = WEBSERVER_PATH + ALL_MESSAGE_FILE
  ROLLING_LOGFILE = WEBSERVER_PATH + ROLLING_LOGFILE
  STDERR_FILE = WEBSERVER_PATH + STDERR_FILE
  BACKUP_FILE = WEBSERVER_PATH + BACKUP_FILE
  SERVICE_VERIFICATION_FILE = WEBSERVER_PATH + SERVICE_VERIFICATION_FILE
  UPTIMES_FILE = WEBSERVER_PATH + UPTIMES_FILE

  WEBSERVER_IMAGE_FOLDER = WEBSERVER_PATH + WEBSERVER_IMAGE_FOLDER
  HISTOGRAM_IMAGE_HTML = WEBSERVER_PATH + HISTOGRAM_IMAGE_HTML
  HOURLY_IMAGE_FILE = WEBSERVER_IMAGE_FOLDER + HOURLY_IMAGE_FILE
  VERSION_REPOSITORY = WEBSERVER_PATH + VERSION_REPOSITORY



TIMEZONE = 'US/Pacific' # timezone of display
TZ = pytz.timezone(TIMEZONE)

KNOWN_AIRPORTS = ('SJC', 'SFO', 'OAK')  # iata codes that we don't need to expand

SPLITFLAP_CHARS_PER_LINE = 22
SPLITFLAP_LINE_COUNT = 6

DIRECTIONS_4 = ['N', 'E', 'S', 'W']
DIRECTIONS_8 = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']
DIRECTIONS_16 = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
                 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']

HOURS = ['12a', ' 1a', ' 2a', ' 3a', ' 4a', ' 5a', ' 6a', ' 7a',
         ' 8a', ' 9a', '10a', '11a', '12p', ' 1p', ' 2p', ' 3p',
         ' 4p', ' 5p', ' 6p', ' 7p', ' 8p', ' 9p', '10p', '11p']




                            <----SKIPPED LINES---->




      max_distance_feet=max_distance_feet,
      max_altitude_feet=max_altitude_feet,
      normalize_factor=normalize_factor,
      exhaustive=exhaustive)
  if position:
    matplotlib.pyplot.subplot(*position)
  matplotlib.pyplot.figure(figsize=figsize_inches)
  values_coordinates = numpy.arange(len(keys))
  matplotlib.pyplot.bar(values_coordinates, values)

  # The filtering may have removed any flight data, or there may be none to start
  if not filtered_data:
    return

  earliest_flight_time = int(filtered_data[0]['now'])
  last_flight_time = int(filtered_data[-1]['now'])
  date_range_string = ' %d flights over last %s hours' % (
      sum(values), SecondsToDdHh(last_flight_time - earliest_flight_time))

  timestamp_string = 'Last updated %s' % EpochDisplayTime(
      time.time(), format_string='%b %-d, %-I:%M%p')

  full_title = '\n'.join([title, date_range_string, timestamp_string])

  matplotlib.pyplot.title(full_title)

  matplotlib.pyplot.subplots_adjust(bottom=0.15, left=0.09, right=0.99, top=0.89)

  matplotlib.pyplot.xticks(
      values_coordinates, keys, rotation='vertical', wrap=True,
      horizontalalignment='right',
      verticalalignment='center')


def HistogramSettingsHours(how_much_history):
  """Extracts the desired history (in hours) from the histogram configuration string.

  Args:
    how_much_history: string from the histogram config file.

  Returns:




                            <----SKIPPED LINES---->




    if this_histogram == 'all':
      this_histogram = histogram['generate']
    (key, sort, title, hours) = HistogramSettingsKeySortTitle(
        this_histogram, hours, flights)

    # if multiple histograms are getting generated, this might take a few seconds;
    # logging a heartbeat with each histogram ensures that monitoring.py does not
    # mistake this pause for a hang.
    if heartbeat:
      Heartbeat()

    CreateSingleHistogramChart(
        flights,
        key,
        sort,
        title,
        truncate=histogram.get('truncate', TRUNCATE),
        hours=hours,
        exhaustive=histogram.get('exhaustive', False))
    filename = filename_prefix + histogram['generate'] + '.' + filename_suffix
    filepath = WEBSERVER_IMAGE_FOLDER + filename
    matplotlib.pyplot.savefig(filepath)
    matplotlib.pyplot.close()
    histograms_generated.append((histogram['generate'], filename))

  return histograms_generated


def MessageboardHistograms(
    flights,
    which_histograms,
    how_much_history,
    max_screens,
    data_summary):
  """Generates multiple split flap screen histograms.

  Args:
    flights: the iterable of the raw data from which the histogram will be generated;
      each element of the iterable is a dictionary, that contains at least the key
      'now', and depending on other parameters, also potentially
      'min_feet' amongst others.
    which_histograms: string paramater indicating which histogram(s) to generate, which




                            <----SKIPPED LINES---->




  Returns:
    List of histogram messages, if text-based histograms are selected; empty list
    otherwise.
  """
  histogram_messages = []

  if histogram_settings['type'] in ('messageboard', 'both'):
    histogram_messages = MessageboardHistograms(
        flights,
        histogram_settings['histogram'],
        histogram_settings['histogram_history'],
        histogram_settings['histogram_max_screens'],
        histogram_settings.get('histogram_data_summary', False))
  if histogram_settings['type'] in ('images', 'both'):

    # Since Google Chrome seems to ignore all instructions to not cache, we need
    # to make sure we do not reuse file names - hence the epoch_string - and then
    # we need to 1) update the histograms.html file with the correct file links, and
    # 2) delete the images that are now obsolete.
    epoch_string = '%d_' % round(time.time())

    generated_histograms = ImageHistograms(
        flights,
        histogram_settings['histogram'],
        histogram_settings['histogram_history'],
        filename_prefix=HISTOGRAM_IMAGE_PREFIX + epoch_string)
    html_lines = ReadFile(HISTOGRAM_IMAGE_HTML).split('\n')
    replaced_images = []
    for identifier, new_filename in generated_histograms:
      # for each histogram, find the html_line with the matching id
      # Example line: <img id='destination' src='images/histogram_destination.png'><p>
      n, line = None, None  # addresses pylint complaint
      for n, line in enumerate(html_lines):
        if identifier in line:
          break
      start_char = line.find(WEBSERVER_IMAGE_FOLDER) + len(WEBSERVER_IMAGE_FOLDER)
      end_character = (
          line.find(HISTOGRAM_IMAGE_SUFFIX, start_char) + len(HISTOGRAM_IMAGE_SUFFIX))
      old_filename = line[start_char:end_character]
      line = line.replace(old_filename, new_filename)
      html_lines[n] = line
      replaced_images.append(line[start_char:end_character])
    new_html = '\n'.join(html_lines)
    WriteFile(HISTOGRAM_IMAGE_HTML, new_html)

    # Remove those obsoleted files
    for f in old_filename:
      RemoveFile(f)





  return histogram_messages


def SaveFlightsByAltitudeDistanceCSV(
    flights,
    max_days=0,
    filename='flights_by_alt_dist.csv',
    precision=100):
  """Extracts hourly histogram into text file for a variety of altitudes and distances.

  Generates a csv with 26 columns:
  - col#1: altitude (in feet)
  - col#2: distance (in feet)
  - cols#3-26: hour of the day

  The first row is a header row; subsequent rows list the number of flights that have
  occurred in the last max_days with an altitude and min distance less than that identified
  in the first two columns. Each row increments elevation or altitude by precision feet,
  up to the max determined by the max altitude and max distance amongst all the flights.




                            <----SKIPPED LINES---->




      (False).
    subsystem: A tuple describing the system; though that description may have multiple
      attributes, the 0th element is the numeric identifier of that system.  monitoring.py
      depends on other attributes of that tuple being present as well.  Since the
      overall system does not have a tuple defined for it, it gets a default identifier
      of 0.
    failure_message: an (optional) message describing why the system / subsystem is
      being disabled or failing.
  """
  versions = (VERSION_MESSAGEBOARD, VERSION_ARDUINO)
  if subsystem:
    subsystem = subsystem[0]
  PickleObjectToFile(
      (time.time(), subsystem, value, versions, failure_message),
      PICKLE_DASHBOARD, True)


def RemoveFile(file):
  """Removes a file if it exists, returning a boolean indicating if it had existed."""
  if os.path.exists(file):

    try:
      os.remove(file)
    except PermissionError:
      return False
    return True

  return False


def ConfirmNewFlight(flight, flights):
  """Replaces last-seen flight with new flight if otherwise identical but for identifiers.

  Flights are identified by the radio over time by a tuple of identifiers: flight_number
  and squawk.  Due to unknown communication issues, one or the other may not always
  be transmitted. However, as soon as a new flight is identified that has at least one
  of those identifiers, we report on it and log it to the pickle repository, etc.

  This function checks if the newly identified flight is indeed a duplicate of the
  immediate prior flight by virtue of having the same squawk and/or flight number, and
  further, if the paths overlap.  If the paths do not overlap, then its likely that
  the same flight was seen some minutes apart, and should legitimately be treated as
  a different flight.

  If the new flight is an updated version, then we should replace the prior-pickled-to-
  disk flight and replace the last flight in flights with this new version.





                            <----SKIPPED LINES---->