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









102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145








243244245246247248249250251252253254255256257258259260261262263264265266  267268269270271272273274275276277278279280281282283284285286








437143724373437443754376437743784379438043814382438343844385438643874388438943904391  43924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423 442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447 4448444944504451445244534454445544564457445844594460446144624463446444654466446744684469








472547264727472847294730473147324733473447354736473747384739474047414742474347444745      474647474748                           47494750475147524753475447554756475747584759476047614762476347644765476647674768











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





# This web-exposed file is used for non-error messages that might highlight data or
# code logic to check into. It is only cleared out manually.
LOGFILE = 'log.txt'
# Identical to the LOGFILE, except it includes just the most recent n lines. Newest
# lines are at the end.
ROLLING_LOGFILE = 'rolling_log.txt' #file for error messages

ROLLING_LOG_SIZE = 1000  # default number of lines which may be overridden by settings file

# Users can trigger .png histograms analogous to the text ones from the web interface;
# this is the folder (within WEBSERVER_PATH) where those files are placed
WEBSERVER_IMAGE_FOLDER = 'images/'
# Multiple histograms can be generated, i.e. for airline, aircraft, day of week, etc.
# The output files are named by the prefix & suffix, i.e.: prefix + type + . + suffix,
# as in histogram_aircraft.png. These names match up to the names expected by the html
# page that displays the images. Also, note that the suffix is interpreted by matplotlib
# to identify the image format to create.
HISTOGRAM_IMAGE_PREFIX = 'histogram_'
HISTOGRAM_IMAGE_SUFFIX = 'png'
# For those of the approximately ten different types of histograms _not_ generated,
# an empty image is copied into the location expected by the webpage instead; this is
# the location of that "empty" image file.
HISTOGRAM_EMPTY_IMAGE_FILE = 'empty.png'

# This file indicates a pending request for histograms - either png, text-based, or
# both; once it is processed, this file is deleted. The contents are concatenated key-value
# pairs, histogram=all;histogram_history=24h; etc.
HISTOGRAM_CONFIG_FILE = 'secure/histogram.txt'
HISTOGRAM_BOOLEANS = ('histogram_data_summary')
# This contains concatenated key-value configuration attributes in a similar format
# to the HISTOGRAM_CONFIG_FILE that are exposed to the user via the web interface or,
# for a subset of them, through the Arduino interface. They are polled at every iteration
# so that the most current value is always leveraged by the running software.
CONFIG_FILE = 'secure/settings.txt'
CONFIG_BOOLEANS = ('setting_screen_enabled', 'next_flight', 'reset_logs', 'log_jsons')
# A few key settings for the messageboard are its sensitivity to displaying flights -
# though it logs all flights within range, it may not be desirable to display all
# flights to the user. Two key parameters are the maximum altitude, and the furthest
# away we anticipate the flight being at its closest point to HOME. As those two
# parameters are manipulated in the settings, a histogram is displayed with one or
# potentially two series, showing the present and potentially prior-set distribution
# of flights, by hour throughout the day, over the last seven days, normalized to
# flights per day. This allows those parameters to be fine-tuned in a useful way.




                            <----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_EMPTY_IMAGE_FILE = (
      WEBSERVER_PATH + WEBSERVER_IMAGE_FOLDER + HISTOGRAM_EMPTY_IMAGE_FILE)
  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']

SECONDS_IN_MINUTE = 60
MINUTES_IN_HOUR = 60




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




  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
      can be either the special string 'all', or a string linked to a specific
      histogram.
    how_much_history: string parameter taking a value among ['today', '24h', '7d', '30d].
    filename_prefix: this string indicates the file path and name prefix for the images
      that are created. File names are created in the form [prefix]name.[suffix], i.e.:
      if the prefix is histogram_ and the suffix is png, then the file name might be
      histogram_aircraft.png.
    filename_suffix: see above; also interpreted by savefig to generate the correct
      format.
    heartbeat: boolean indicating whether we should log heartbeats between each histogram
      to make sure monitoring does not mistake this slow procedure for being hung; this
      should be set to false if this is called from outside of messageboard.main.

  Returns:
    List of the names of the histograms generated.


  """
  hours = HistogramSettingsHours(how_much_history)

  histograms_to_generate = []
  if which_histograms in ['destination', 'all']:
    histograms_to_generate.append({'generate': 'destination'})
  if which_histograms in ['origin', 'all']:
    histograms_to_generate.append({'generate': 'origin'})
  if which_histograms in ['hour', 'all']:
    histograms_to_generate.append({'generate': 'hour'})
  if which_histograms in ['airline', 'all']:
    histograms_to_generate.append({'generate': 'airline', 'truncate': int(TRUNCATE/2)})
  if which_histograms in ['aircraft', 'all']:
    histograms_to_generate.append({'generate': 'aircraft'})
  if which_histograms in ['altitude', 'all']:
    histograms_to_generate.append({'generate': 'altitude', 'exhaustive': True})
  if which_histograms in ['bearing', 'all']:
    histograms_to_generate.append({'generate': 'bearing'})
  if which_histograms in ['distance', 'all']:
    histograms_to_generate.append({'generate': 'distance', 'exhaustive': True})
  if which_histograms in ['day_of_week', 'all']:
    histograms_to_generate.append({'generate': 'day_of_week'})
  if which_histograms in ['day_of_month', 'all']:
    histograms_to_generate.append({'generate': 'day_of_month'})

  if which_histograms in ['speed', 'all']:
    histograms_to_generate.append({'generate': 'speed', 'exhaustive': True})
  if which_histograms in ['aircraft_length', 'all']:
    histograms_to_generate.append({'generate': 'aircraft_length', 'exhaustive': True})
  if which_histograms in ['vert_rate', 'all']:
    histograms_to_generate.append({'generate': 'vert_rate', 'exhaustive': True})


  for histogram in histograms_to_generate:
    this_histogram = which_histograms
    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 = [h['generate'] for h in histograms_to_generate]
  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
      can be either the special string 'all', or a string linked to a specific
      histogram.
    how_much_history: string parameter taking a value among ['today', '24h', '7d', '30d].




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




  created so that broken image links are not displayed in the webpage.

  Args:
    flights: List of flight attribute dictionaries.
    histogram_settings: Dictionary of histogram parameters.

  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'):
    ImageHistograms(






        flights,
        histogram_settings['histogram'],
        histogram_settings['histogram_history'])




























  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---->





01234567890123456789012345678901234567890123456789012345678901234567890123456789









102103104105106107108109110111112113114115116117118119120121122   123124125126127128129130131132133134135136137138139140141142








240241242243244245246247248249250251252253254255256257258259260 261262263264265266267268269270271272273274275276277278279280281282283284








4369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450 44514452445344544455445644574458445944604461446244634464446544664467446844694470








47264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802











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





# This web-exposed file is used for non-error messages that might highlight data or
# code logic to check into. It is only cleared out manually.
LOGFILE = 'log.txt'
# Identical to the LOGFILE, except it includes just the most recent n lines. Newest
# lines are at the end.
ROLLING_LOGFILE = 'rolling_log.txt' #file for error messages

ROLLING_LOG_SIZE = 1000  # default number of lines which may be overridden by settings file

# Users can trigger .png histograms analogous to the text ones from the web interface;
# this is the folder (within WEBSERVER_PATH) where those files are placed
WEBSERVER_IMAGE_FOLDER = 'images/'
# Multiple histograms can be generated, i.e. for airline, aircraft, day of week, etc.
# The output files are named by the prefix & suffix, i.e.: prefix + type + . + suffix,
# as in histogram_aircraft.png. These names match up to the names expected by the html
# page that displays the images. Also, note that the suffix is interpreted by matplotlib
# to identify the image format to create.
HISTOGRAM_IMAGE_PREFIX = 'histogram_'
HISTOGRAM_IMAGE_SUFFIX = 'png'
HISTOGRAM_IMAGE_HTML = 'histograms.html'




# This file indicates a pending request for histograms - either png, text-based, or
# both; once it is processed, this file is deleted. The contents are concatenated key-value
# pairs, histogram=all;histogram_history=24h; etc.
HISTOGRAM_CONFIG_FILE = 'secure/histogram.txt'
HISTOGRAM_BOOLEANS = ('histogram_data_summary')
# This contains concatenated key-value configuration attributes in a similar format
# to the HISTOGRAM_CONFIG_FILE that are exposed to the user via the web interface or,
# for a subset of them, through the Arduino interface. They are polled at every iteration
# so that the most current value is always leveraged by the running software.
CONFIG_FILE = 'secure/settings.txt'
CONFIG_BOOLEANS = ('setting_screen_enabled', 'next_flight', 'reset_logs', 'log_jsons')
# A few key settings for the messageboard are its sensitivity to displaying flights -
# though it logs all flights within range, it may not be desirable to display all
# flights to the user. Two key parameters are the maximum altitude, and the furthest
# away we anticipate the flight being at its closest point to HOME. As those two
# parameters are manipulated in the settings, a histogram is displayed with one or
# potentially two series, showing the present and potentially prior-set distribution
# of flights, by hour throughout the day, over the last seven days, normalized to
# flights per day. This allows those parameters to be fine-tuned in a useful way.




                            <----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']

SECONDS_IN_MINUTE = 60
MINUTES_IN_HOUR = 60




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




  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
      can be either the special string 'all', or a string linked to a specific
      histogram.
    how_much_history: string parameter taking a value among ['today', '24h', '7d', '30d].
    filename_prefix: this string indicates the file path and name prefix for the images
      that are created. File names are created in the form [prefix]name.[suffix], i.e.:
      if the prefix is histogram_ and the suffix is png, then the file name might be
      histogram_aircraft.png.
    filename_suffix: see above; also interpreted by savefig to generate the correct
      format.
    heartbeat: boolean indicating whether we should log heartbeats between each histogram
      to make sure monitoring does not mistake this slow procedure for being hung; this
      should be set to false if this is called from outside of messageboard.main.

  Returns:
    List of 2-tuples of histograms generated, where the first element is the histogram
    identifier (i.e.: destination), and the second element is the filename (i.e.:
    histogram_destination.png).
  """
  hours = HistogramSettingsHours(how_much_history)

  histograms_to_generate = []
  if which_histograms in ['destination', 'all']:
    histograms_to_generate.append({'generate': 'destination'})
  if which_histograms in ['origin', 'all']:
    histograms_to_generate.append({'generate': 'origin'})
  if which_histograms in ['hour', 'all']:
    histograms_to_generate.append({'generate': 'hour'})
  if which_histograms in ['airline', 'all']:
    histograms_to_generate.append({'generate': 'airline', 'truncate': int(TRUNCATE/2)})
  if which_histograms in ['aircraft', 'all']:
    histograms_to_generate.append({'generate': 'aircraft'})
  if which_histograms in ['altitude', 'all']:
    histograms_to_generate.append({'generate': 'altitude', 'exhaustive': True})
  if which_histograms in ['bearing', 'all']:
    histograms_to_generate.append({'generate': 'bearing'})
  if which_histograms in ['distance', 'all']:
    histograms_to_generate.append({'generate': 'distance', 'exhaustive': True})
  if which_histograms in ['day_of_week', 'all']:
    histograms_to_generate.append({'generate': 'day_of_week'})
  if which_histograms in ['day_of_month', 'all']:
    histograms_to_generate.append({'generate': 'day_of_month'})

  if which_histograms in ['speed', 'all']:
    histograms_to_generate.append({'generate': 'speed', 'exhaustive': True})
  if which_histograms in ['aircraft_length', 'all']:
    histograms_to_generate.append({'generate': 'aircraft_length', 'exhaustive': True})
  if which_histograms in ['vert_rate', 'all']:
    histograms_to_generate.append({'generate': 'vert_rate', 'exhaustive': True})

  histograms_generated = []
  for histogram in histograms_to_generate:
    this_histogram = which_histograms
    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
      can be either the special string 'all', or a string linked to a specific
      histogram.
    how_much_history: string parameter taking a value among ['today', '24h', '7d', '30d].




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




  created so that broken image links are not displayed in the webpage.

  Args:
    flights: List of flight attribute dictionaries.
    histogram_settings: Dictionary of histogram parameters.

  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---->