Part 2a of 2: Creating Charts From Synthetic Trajectories.

Note this an optional process that uses the output files from part 1a

Imports and Constants

This script utilizes the following libraries:

  • matplotlib 2.2.2 : to create charts
  • Pandas 0.23 : to easily format and manipulate the data
  • encyclopedia 0.29 : for creating the visualizations as kml files.

Constants definition

KML_DIR: Location output files will be written to
CV_FILE: Location of part 1 BSM synthetic trajectories file

In [1]:
%matplotlib inline
# Standard
from datetime import datetime, timedelta
import math

# External
import matplotlib.pyplot as plt
import matplotlib.patheffects as PathEffects
import pandas as pd
from bits.time import unix2str
In [2]:
# Constants
KML_DIR = 'examples/'
CV_FILE = 'VehicleCountByTime.csv'
RSU_FILE = 'VehicleCountInRSURange.csv'

Global Variables

The following variables are used to assist in creating the charts:

  • xs_time : stores cumulative timesetps, to be used as x-axis values
  • y_all : stores cumulative total number of vehivles at a given time, to be used as y-axis values
  • y_rsu_all : stores cumulative total number of vehicles in rsu coverage at a given time, to be used as y-axis values
  • y_not_rsu_all : stores cumulative total number of out of rsu coverage at a given time, to be used as y-axis values
  • kml_body : stores kml content
In [3]:
# global variables for the x-axis and y-axis of matplotlib charts
xs_time = []
y_all = []
y_rsu_all = []
y_not_rsu_all = []

# global variable for kml body
kml_body = ""

def writer():

Creates charts as png images and a kml linking to them
  • plots : number of plots to be drawn
  • gx_time : number of timestamps in current time window, to be used as x-axis
  • y_current : average number of vehicles in current time window, to be used as y-axis
  • min_local : minimum time value of current time window
  • max_local : maximum time value of current time window
  • abs_min : earliest time value in entire dataset
  • min_label : earliest time value in current time window
  • max_label : latest time value in current time window
  • abs_min_label : different format of abs_min
  • png_filename : name of png file
  • kml_min : minimum time of current time window in kml format
  • kml_max : maximum time of current time window in kml format
  • y_current_in_rsu : average number of vehicles within rsu coverage in current time window, to be used as x-axis
  • y_current_not_in_rsu : average number of vehicles not within rsu coverage in current time window, to be used as x-axis

***Note the kml and png files must be in the same directory

In [4]:
def writer(plots, gx_time, y_current, min_local, max_local, abs_min, min_label, max_label, abs_min_label,
           png_filename, kml_min, kml_max, y_current_in_rsu=None, y_current_not_in_rsu=None):

    global xs_time, y_all, y_rsu_all, y_not_rsu_all, kml_body

    # Create pyplot figure
    plt.rcParams['axes.labelweight'] = 'bold' # make all axes text bold

    if plots == 4:
        fig1, (ax1, ax2, ax3, ax4) = plt.subplots(nrows=1, ncols=4, sharex=False, sharey=False)
    else:
        fig1, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, sharex=False, sharey=False)
    fig1.set_figwidth(15)
    fig1.set_figheight(3)

    # Round up to nearest 10
    y_max = max(y_all)
    y_max = int(math.ceil(y_max / 10.0)) * 10

    # Plots are created in the code below
    # Total number of vehicles in 10 min. window
    ax1.plot(gx_time, y_current, linewidth=3.5, color='green', path_effects=[PathEffects.SimpleLineShadow(foreground='w'), PathEffects.Normal()])
    ax1.axis([min_local, max_local, 0, y_max])
    ax1.set_ylabel("Total Active Vehicles", color='black', fontsize=14)
    ax1.set_xlabel(min_label + " to " + max_label, color='black', fontsize=14)
    ax1.spines['bottom'].set_color('black')
    ax1.spines['bottom'].set_linewidth(3)
    ax1.spines['top'].set_color('black')
    ax1.spines['top'].set_linewidth(3)
    ax1.spines['left'].set_color('black')
    ax1.spines['left'].set_linewidth(3)
    ax1.spines['right'].set_color('black')
    ax1.spines['right'].set_linewidth(3)
    ax1.tick_params(axis='x', colors='black')
    ax1.tick_params(axis='y', colors='black', labelsize=14)
    ax1.set_facecolor('white')

    # Cumulative total number of vehicles
    ax2.plot(xs_time, y_all, linewidth=2.5, color='green', path_effects=[PathEffects.SimpleLineShadow(foreground='w'), PathEffects.Normal()])
    ax2.axis([abs_min, max_local, 0, y_max])
    ax2.set_ylabel("Total Vehicles", color='black', fontsize=14)
    ax2.set_xlabel(abs_min_label + " to " + max_label, color='black', fontsize=14)
    ax2.spines['bottom'].set_color('black')
    ax2.spines['bottom'].set_linewidth(3)
    ax2.spines['top'].set_color('black')
    ax2.spines['top'].set_linewidth(3)
    ax2.spines['left'].set_color('black')
    ax2.spines['left'].set_linewidth(3)
    ax2.spines['right'].set_color('black')
    ax2.spines['right'].set_linewidth(3)
    ax2.tick_params(axis='x', colors='black')
    ax2.tick_params(axis='y', colors='black', labelsize=14)
    ax2.set_facecolor('white')

    # hide all the x labels
    ax1.set_xticklabels([])
    ax2.set_xticklabels([])

    if plots == 4:
        # Number of vehicles in coverage of rsu in 10 min. window
        ax3.plot(gx_time, y_current_in_rsu, linewidth=2.5, color='blue', path_effects=[PathEffects.SimpleLineShadow(foreground='w'), PathEffects.Normal()], label='In Coverage')
        ax3.plot(gx_time, y_current_not_in_rsu, linewidth=2.5, color='red', path_effects=[PathEffects.SimpleLineShadow(foreground='w'), PathEffects.Normal()], label='Out of Coverage')
        ax3.axis([min_local, max_local, 0, y_max])
        ax3.set_ylabel("Active RSU Coverage", color='black', fontsize=14)
        ax3.set_xlabel(abs_min_label + " to " + max_label, color='black', fontsize=14)
        ax3.spines['bottom'].set_color('black')
        ax3.spines['bottom'].set_linewidth(3)
        ax3.spines['top'].set_color('black')
        ax3.spines['top'].set_linewidth(3)
        ax3.spines['left'].set_color('black')
        ax3.spines['left'].set_linewidth(3)
        ax3.spines['right'].set_color('black')
        ax3.spines['right'].set_linewidth(3)
        ax3.tick_params(axis='x', colors='black')
        ax3.tick_params(axis='y', colors='black', labelsize=14)
        ax3.set_facecolor('white')
        ax3.legend(loc='upper left')

        # Cumulative number of vehicles in coverage of rsu
        ax4.plot(xs_time, y_rsu_all, linewidth=2.5, color='blue', path_effects=[PathEffects.SimpleLineShadow(foreground='w'), PathEffects.Normal()], label='In Coverage')
        ax4.plot(xs_time, y_not_rsu_all, linewidth=2.5, color='red', path_effects=[PathEffects.SimpleLineShadow(foreground='w'), PathEffects.Normal()], label='Out of Coverage')
        ax4.axis([abs_min, max_local, 0, y_max])
        ax4.set_ylabel("Total RSU Coverage", color='black', fontsize=14)
        ax4.set_xlabel(abs_min_label + " to " + max_label, color='black', fontsize=14)
        ax4.spines['bottom'].set_color('black')
        ax4.spines['bottom'].set_linewidth(3)
        ax4.spines['top'].set_color('black')
        ax4.spines['top'].set_linewidth(3)
        ax4.spines['left'].set_color('black')
        ax4.spines['left'].set_linewidth(3)
        ax4.spines['right'].set_color('black')
        ax4.spines['right'].set_linewidth(3)
        ax4.tick_params(axis='x', colors='black')
        ax4.tick_params(axis='y', colors='black', labelsize=14)
        ax4.set_facecolor('white')
        ax4.legend(loc='upper left')

        # hide all the x labels
        ax3.set_xticklabels([])
        ax4.set_xticklabels([])

    # use tight_layout() to avoid overlapping text
    fig1.tight_layout()

    # save figure
    fig1.savefig(KML_DIR + png_filename + '.png', bbox_inches='tight', dpi=65, transparent=False)

    # clear & close everything to avoid memory leaks
    plt.close()
    fig1.clf()
    plt.clf()

    kml_body += '''<ScreenOverlay>
    <name>HUD Histogram</name>
    <TimeSpan>
    <begin>''' + kml_min + '''</begin>
    <end>''' + kml_max + '''</end>
    </TimeSpan>
    <Icon> <href>''' + png_filename + '''.png</href>
    </Icon>
    <overlayXY x="0.05" y="0.05" xunits="fraction" yunits="fraction"/>
    <screenXY x="0.05" y="0.05" xunits="fraction" yunits="fraction"/>
    <rotationXY x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
    <size x="0" y="0" xunits="fraction" yunits="fraction"/>
    </ScreenOverlay>
        '''

def plotter(df_hour, min_time, max_time, abs_min_time, png_filename, rsu_df_hour=None):

Calculates values to be used in charts
  • df_hour : dataframe of current time window
  • min_time : minimum time of dataframe
  • max_time : maximum time of datafram
  • abs_min_time : minimum time of entire dataset
  • png_filename : png file naming convention
  • rsu_df_hour : dataframe of current time window for rsu trajectory data
In [5]:
def plotter(df_hour, min_time, max_time, abs_min_time, png_filename, rsu_df_hour=None):
    """
    Draws charts as png files and creates a kml file to display them
    """

    # reference variables for x and y axes for total tracks graph and kml body
    global xs_time, y_all, y_rsu_all, y_not_rsu_all, kml_body

    # variables for x and y axes for total active tracks graph
    gx_time = []
    y_current = []
    y_current_in_rsu = []
    y_current_not_in_rsu = []

    # groups min and max time
    max_group_time = max_time
    current_min_time = min_time
    current_max_time = current_min_time + 600 # Further divide by every minute for points

    # x axis range values. Converted from utc tick to datetime format
    min_local = unix2str(min_time/10)
    max_local = unix2str(max_time/10)

    # x axis min for total tracks graph
    abs_min = unix2str(abs_min_time/10)

    # x axis labels. Converted from tick to est datetime format
    abs_min_label = str(datetime.strptime(unix2str(abs_min_time/10, format='%H:%M:%S'), '%H:%M:%S') - timedelta(hours=5))
    abs_min_label = abs_min_label.split()[1]
    min_label = str(datetime.strptime(unix2str(min_time/10, format='%H:%M:%S'), '%H:%M:%S') - timedelta(hours=5))
    min_label = min_label.split()[1]
    max_label = str(datetime.strptime(unix2str(max_time/10, format='%H:%M:%S'), '%H:%M:%S') - timedelta(hours=5))
    max_label = max_label.split()[1]

    # kml begin and end times. Dictates when the graphs are displayed. KML time format
    kml_min = unix2str(min_time/10, format='%Y-%m-%dT%H:%M:%SZ')
    kml_max = str(datetime.strptime(unix2str(max_time/10, format='%Y-%m-%dT%H:%M:%SZ'), '%Y-%m-%dT%H:%M:%SZ') - timedelta(minutes=1))
    kml_max = kml_max.split()[0] + 'T' + kml_max.split()[1] + 'Z'

    # Plot point every minute
    while current_min_time <= max_group_time:
        df_minute = df_hour[(df_hour['tick'] >= current_min_time) & (df_hour['tick'] <= current_max_time)]

        # Compute point values
        num_total_current = df_minute['Number of Vehicles'].mean()

        # Replace nan instances with 0
        if math.isnan(num_total_current):
            num_total_current = 0

        if rsu_df_hour is not None:
            rsu_df_minute = rsu_df_hour[(rsu_df_hour['tick'] >= current_min_time) & (rsu_df_hour['tick'] <= current_max_time)]
            num_total_rsu_current = rsu_df_minute['True'].mean()
            num_total_not_in_rsu_current = rsu_df_minute['False'].mean()
            if math.isnan(num_total_rsu_current):
                num_total_current = 0

            if math.isnan(num_total_not_in_rsu_current):
                num_total_current = 0

        # Append data onto the x & y axes to be displayed
        gx_time.append(unix2str(current_min_time/10)) # Used for active total
        xs_time.append(unix2str(current_min_time/10)) # Used for total

        y_current.append(num_total_current) # Used for active total
        y_all.append(num_total_current) # Used for total
        if rsu_df_hour is not None:
            y_current_in_rsu.append(num_total_rsu_current) # Used for active number of vehicles in RSU
            y_current_not_in_rsu.append(num_total_not_in_rsu_current) # Used for active number of vehicles out of RSU
            y_rsu_all.append(num_total_rsu_current) # Used for total number of vehicles in RSU
            y_not_rsu_all.append(num_total_not_in_rsu_current) # Used for total number of vehicles out of RSU

        current_min_time = current_max_time
        current_max_time += 600

    if rsu_df_hour is not None:
        plots = 4
        writer(plots, gx_time, y_current, min_local, max_local, abs_min, min_label, max_label, abs_min_label,
               png_filename, kml_min, kml_max, y_current_in_rsu, y_current_not_in_rsu)
    else:
        plots = 2
        writer(plots, gx_time, y_current, min_local, max_local, abs_min, min_label, max_label, abs_min_label,
               png_filename, kml_min, kml_max)

def kml_writer(rsu=False)

Ingests, formats, and time slices data, creates kml file and calls chart methods

  • rsu: set to true if rsu trajectory file is being used
In [6]:
def kml_writer(rsu=False):

    global kml_body

    # Create kml file
    kml_file = KML_DIR + 'HUD.kml'
    with open(kml_file, "w") as f:

        # Add header to file
        kmlhead = '<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"' + \
        ' xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">\n' + '<Document>\n'

        # Load data
        dfFromRecords = pd.read_csv(CV_FILE) # Number of vehicles at a given time
        if rsu:
            dfFromRSU = pd.read_csv(RSU_FILE) # Number of vehicles in and out of range of RSU at given time

        counter = 0

        # Rename time column
        dfFromRecords.rename(columns={'time_received': 'tick'}, inplace=True)
        if rsu:
            dfFromRSU.rename(columns={'time_received': 'tick'}, inplace=True)

        # Every tick is by decisecond
        min_time = dfFromRecords['tick'].min()
        max_time = dfFromRecords['tick'].max()
        current_min_time = min_time
        current_max_time = current_min_time + 6000 # Every 10 minutes

        # Divide dataframe every 10 minutes and draw a new graph
        while current_min_time < max_time:
            df = dfFromRecords[(dfFromRecords['tick'] >= current_min_time) & (dfFromRecords['tick'] <= current_max_time)]
            if rsu:
                rsu_df = dfFromRSU[(dfFromRSU['tick'] >= current_min_time) & (dfFromRSU['tick'] <= current_max_time)]

            counter += 1
            png_filename = "HUD" + str(counter)

            if rsu:
                plotter(df, current_min_time, current_max_time, min_time, png_filename, rsu_df) # draw the HUD
            else:
                plotter(df, current_min_time, current_max_time, min_time, png_filename) # draw the HUD

            current_min_time = current_max_time
            current_max_time += 6000

        kml = kmlhead  + kml_body + '</Document>\n </kml>'

        f.write(kml)

Run kml_writer()

In [7]:
kml_writer(rsu=True)
<matplotlib.figure.Figure at 0x7a0eff0>