LearnRuby.com

Ruby Training and Ruby on Rails Training exclusively

Traffic Grapher

This is a solution to Ruby Quiz #146, written by Gavin Kistner. The quiz begins:

You have been hired to work for a small city government. The city recently bought a vehicle counter, one of those kinds that uses pneumatic rubber hoses stretched across the road. The company that sells the machine also sells software to interpret the raw data. However, the city has asked you to see if you can interpret it instead, saving them some money.

The quiz asked for the processed data to be sliced and diced in various ways for output. Because there were so many variations, I decided to create a domain-specific language (DSL) to describe the graphs. The DSL allows you to specify:

Here is the main.rb file, which is the entry point to the program, to give you a sense of the DSL:


# This is a solution to Ruby Quiz #146 (see http://www.rubyquiz.com/)
# is Copyright 2007 by J. E. Ivancich, all rights reserved.  It is
# released under the Creative Commons Attribution-Share Alike 3.0
# United States License.

# This source code can also be found at:
#   http://learnruby.com/examples/ruby-quiz-146.shtml

# This file is the main entry point to the program.

# It creates 109 different graphs slicing and dicing the data in
# various ways.  90 of the graphs are created through two nested loops
# giving various day/direction combinations.  12 of the graphs are
# created through a single loop (the outer of the two nested loops).
# And the remaining 7 are created outside any loop.

require 'traffic_analyzer'
require 'traffic_grapher'

records = analyze "vehicle_counter.data"

graph records, "Maximum Speed by Hour and Direction" do
  series WestboundFilter
  series EastboundFilter
  bins HourSequence
  combiner MaxSpeedCombiner
end

graph records, "Minimum Speed by Hour and Direction" do
  series WestboundFilter
  series EastboundFilter
  bins HourSequence
  combiner MinSpeedCombiner
end

graph records, "Average Speed by Hour and Direction" do
  series WestboundFilter
  series EastboundFilter
  bins HourSequence
  combiner AverageSpeedCombiner
end

graph records, "Speed Distribution by Direction" do
  series AllAndEachDirectionSequence
  bins SpeedRangeSequence
  combiner VolumeCombiner
end

graph records, "Speed Distribution by AM-PM" do
  series AM_PM_Sequence
  bins SpeedRangeSequence
  combiner VolumeCombiner
end

graph records, "Intra-Car Distance in Feet by Hour and Direction" do
  series WestboundFilter
  series EastboundFilter
  bins HourSequence
  combiner AverageDistanceCombiner
end

graph records, "Intra-Car Distance in Feet by Quarter Hour" do
  series QuarterHourSequence
  bins HourSequence
  combiner AverageDistanceCombiner
end

#
# Here we'll use nested loops to create a series of graphs that are
# slight variations of one another.
#

# make graphs for all days, Monday, Tuesday, ...
AllAndEachDaySequence.each do |day_filter|

  graph records, "%s Volume by Hour and Direction" % day_filter.name do
    filter day_filter
    series WestboundFilter
    series EastboundFilter
    bins HourSequence
    combiner VolumeCombiner
  end

  # note: this one uses multiple combiners and a single (default)
  # series; there can *either* be mulitple series or multiple
  # combiners, but not both
  graph records, "%s Speed by Hour" % day_filter.name do
    filter day_filter
    bins HourSequence
    combiner MinSpeedCombiner
    combiner AverageSpeedCombiner
    combiner MaxSpeedCombiner
  end

  # make graphs for both directions, Eastbound, and Westbound
  AllAndEachDirectionSequence.each do |direction_filter|

    graph(records,
          "%s %s Volume by AM-PM" %
            [day_filter.name, direction_filter.name]) do
      filter day_filter
      series OneSequence
      bins AM_PM_Sequence
      combiner VolumeCombiner
    end

    graph(records,
          "%s %s Volume by Hour" % [day_filter.name, direction_filter.name]) do
      filter day_filter
      series OneSequence
      bins HourSequence
      combiner VolumeCombiner
    end

    graph(records,
          "%s %s Volume by Half Hour" %
            [day_filter.name, direction_filter.name]) do
      filter day_filter
      series FirstHalfHourFilter
      series SecondHalfHourFilter
      bins HourSequence
      combiner VolumeCombiner
    end

    graph(records,
          "%s %s Volume by Third Hour" %
            [day_filter.name, direction_filter.name]) do
      filter day_filter
      series FirstThirdHourFilter
      series SecondThirdHourFilter
      series ThirdThirdHourFilter
      bins HourSequence
      combiner VolumeCombiner
    end

    graph(records,
          "%s %s Volume by Quarter Hour" %
            [day_filter.name, direction_filter.name]) do
      filter day_filter
      series FirstQtrHourFilter
      series SecondQtrHourFilter
      series ThirdQtrHourFilter
      series FourthQtrHourFilter
      bins HourSequence
      combiner VolumeCombiner
    end
  end
end

The source code, spread among five source files, and a data file is available in zip format: traffic_grapher.zip.

Note: you will need to install the scruffy RubyGem, which this code relies on to generate the graphs.