Skip to content

Instantly share code, notes, and snippets.

@sighmin
Last active July 11, 2017 15:03
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sighmin/5628306 to your computer and use it in GitHub Desktop.
Save sighmin/5628306 to your computer and use it in GitHub Desktop.
Traffic widget and job for dashing dashboard framework.

Description

Simple Dashing widget and job to display driving times of a route. Uses TomTom for free traffic information.

We at platform45 use it to display our driving times home so we know when to leave work to get home to our loved ones in time.

##Dependencies

Uses json, uri and net/http net/https libraries.

##Usage

To use this widget, copy traffic.html, traffic.coffee, and traffic.scss into the /widgets/traffic directory. Put the traffic.rb file in your /jobs folder.

To include the widget in a dashboard, add the following snippet to the dashboard layout file:

<li class="purple" data-row="1" data-col="1" data-sizex="1" data-sizey="2">
  <div class="purple" data-id="tomtom" data-view="Traffic" data-title="Home Time"></div>
</li>

##Settings

You'll need to grab your office location and everyone's home address location from tomtom's maps here, and add them as shown to office_location and the location array.

You'll also need to generate a tomtom developer api key, this is free, it just takes 24 hours to activate. Get one here.

##Preview

Live Demo Here

class Dashing.Traffic extends Dashing.Widget
ready: ->
# This is fired when the widget is done being rendered
onData: (data) ->
# Handle incoming data
# You can access the html node of this widget with `@node`
# Example: $(@node).fadeOut().fadeIn() will make the node flash each time data comes in.
<div class="non-semantic-protector">
<!-- ribbons and other content in here -->
<h1 class="ribbon">
<strong class="ribbon-content">
<h1 class="title" data-bind="title"></h1>
</strong>
</h1>
</div>
<br />
<ol>
<li data-foreach-result="results">
<span data-bind="result.name"></span> at <span class="drive-time" data-bind="result.time"></span>
<span class="drive-road" data-bind="result.road"></span>
</li>
</ol>
<p class="more-info" data-bind="moreinfo | raw"></p>
<p class="updated-at" data-bind="updatedAtMessage"></p>
require 'net/http'
require 'net/https'
require 'uri'
require 'json'
office_location = URI::encode('-26.116525,28.031015')
key = URI::encode('TOMTOM_APP_API_KEY')
locations = []
locations << { name: "Sam", location: URI::encode('-25.764803,28.34625') } # example location format
SCHEDULER.every '10m', :first_in => '15s' do |job|
routes = []
# pull data
locations.each do |location|
uri = URI.parse("https://api.tomtom.com/lbs/services/route/3/#{office_location}:#{location[:location]}/Quickest/json?avoidTraffic=true&includeTraffic=true&language=en&day=today&time=now&iqRoutes=2&avoidTolls=false&includeInstructions=true&projection=EPSG4326&key=#{key}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
routes << { name: location[:name], location: location[:location], route: JSON.parse(response.body)["route"] }
end
# find winner
if routes
routes.sort! { |route1, route2| route2[:route]["summary"]["totalTimeSeconds"] <=> route1[:route]["summary"]["totalTimeSeconds"] }
routes.map! do |r|
{ name: r[:name],
time: seconds_in_words(r[:route]["summary"]["totalTimeSeconds"]),
road: delay(r[:route]["summary"]["totalDelaySeconds"]) + longest_leg(r[:route]["instructions"]["instruction"]) }
end
end
# send event
send_event('tomtom', { results: routes } )
end
def seconds_in_words(secs)
m, s = secs.divmod(60)
h, m = m.divmod(60)
plural_hours = if h > 1 then "s" else "" end
plural_minutes = if m > 1 then "s" else "" end
if secs >= 3600
"#{h} hour#{plural_hours}, #{m} min#{plural_minutes}"
else
"#{m} min#{plural_minutes}"
end
end
def longest_leg(instructions)
instructions.sort! { |leg1, leg2| leg2["travelTimeSeconds"] <=> leg1["travelTimeSeconds"] }
if instructions[0]["roadNumber"].empty?
instructions[0]["roadName"]
elsif instructions[0]["roadName"].empty?
instructions[0]["roadNumber"]
else
"#{instructions[0]["roadName"]}, #{instructions[0]["roadNumber"]}"
end
end
def delay(delay_seconds)
m, s = delay_seconds.divmod(60)
h, m = m.divmod(60)
if delay_seconds >= 60
"#{m} min delay on "
elsif delay_seconds == 0
""
else
"#{s} sec delay on "
end
end
@ljunkie
Copy link

ljunkie commented May 31, 2013

Where is the traffic.scss?

@ridemx
Copy link

ridemx commented Jun 10, 2013

I was able to find the .scss from looking at the live demo, viewing source, and trying to pull out the applicable code. I may have missed come code, or included extra, but this worked for me.

.p45-colour {
  background-color: #d71717; }
.widget-traffic {
  background-color: #ec663c; }
  .widget-traffic .title {
    color: rgba(255, 255, 255, 0.7); }
  .widget-traffic .ribbon {
    font-size: 16px !important;
    /* This ribbon is based on a 16px font side and a 24px vertical rhythm. I've used em's to position each element for scalability. If you want to use a different font size you may have to play with the position of the ribbon elements */
    width: 65%;
    position: relative;
    background: #ba89b6;
    color: #fff;
    text-align: center;
    padding: 0.5em 0.6em 0.1em 0.6em;
    /* Adjust to suit */
    margin: 2em auto 3em;
    /* Based on 24px vertical rhythm. 48px bottom margin - normally 24 but the ribbon 'graphics' take up 24px themselves so we double it. */
    margin-top: 0px;
    margin-bottom: 25px; }
  .widget-traffic .ribbon:before, .widget-traffic .ribbon:after {
    content: "";
    position: absolute;
    display: block;
    bottom: -1em;
    border: 1.5em solid #986794;
    z-index: -1; }
  .widget-traffic .ribbon:before {
    left: -2em;
    border-right-width: 1.5em;
    border-left-color: transparent; }
  .widget-traffic .ribbon:after {
    right: -2em;
    border-left-width: 1.5em;
    border-right-color: transparent; }
  .widget-traffic .ribbon .ribbon-content:before, .widget-traffic .ribbon .ribbon-content:after {
    content: "";
    position: absolute;
    display: block;
    border-style: solid;
    border-color: #804f7c transparent transparent transparent;
    bottom: -1em; }
  .widget-traffic .ribbon .ribbon-content:before {
    left: 0;
    border-width: 1em 0 0 1em; }
  .widget-traffic .ribbon .ribbon-content:after {
    right: 0;
    border-width: 1em 1em 0 0; }
  .widget-traffic .non-semantic-protector {
    position: relative;
    z-index: 1; }
  .widget-traffic ol, .widget-traffic ul {
    margin: 0 15px;
    text-align: left; }
  .widget-traffic ol {
    list-style-position: inside; }
  .widget-traffic li:first-child {
    font-weight: bold; }
  .widget-traffic li {
    margin-bottom: 12px;
    font-size: 18px; }
  .widget-traffic .drive-time {
    color: rgba(255, 255, 255, 0.7); }
  .widget-traffic .drive-road {
    position: relative;
    left: 35px;
    font-size: 10px;
    display: block; }
  .widget-traffic .more-info {
    color: rgba(255, 255, 255, 0.7); }
  .widget-traffic .updated-at {
    color: rgba(255, 255, 255, 0.7); }

.purple {
  background-color: #b91be0; }

.widget.widget-traffic {
  vertical-align: top; }

@MrZaph
Copy link

MrZaph commented Oct 2, 2014

This widget is great. Really love it!

Just trying to work out how to also display the data in reverse on the same dashboard.

So for example how long its likely to take to get to work so you can see whos going to be late in.

@michaelgra
Copy link

Does this still work? I get the same blank box as on the live demo.

@sighmin
Copy link
Author

sighmin commented Apr 8, 2015

Michaelgra, the dashboard has been decommissioned and it's been so long since I worked on it so you'll have to salvage what you can from the files above. I hope you come right 😄

@jbenner
Copy link

jbenner commented Apr 14, 2015

Here's an updated traffic.rb that is compatible with TomTom's current API:

require 'net/http'
require 'net/https'
require 'uri'
require 'json'

office_location = URI::encode('-26.116525,28.031015')
key             = URI::encode('[TOMTOM_ROUTING_API_KEY]')
locations       = []
locations << { name: "Sam", location: URI::encode('-25.764803,28.34625') }

SCHEDULER.every '10m', :first_in => '15s' do |job|
    routes = []

    # pull data
    locations.each do |location|
        uri = URI.parse("https://api.tomtom.com/routing/1/calculateRoute/#{office_location}:#{location[:location]}/json?routeType=fastest&traffic=true&travelMode=car&key=#{key}")
        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = true
        http.verify_mode = OpenSSL::SSL::VERIFY_NONE

        request = Net::HTTP::Get.new(uri.request_uri)
        response = http.request(request)
        routes << { name: location[:name], location: location[:location], route: JSON.parse(response.body)["routes"][0] }
    end

    # find winner
    if routes
        routes.sort! { |route1, route2| route2[:route]["summary"]["travelTimeInSeconds"] <=> route1[:route]["summary"]["travelTimeInSeconds"] }
        routes.map! do |r|
            { name: r[:name],
                time: seconds_in_words(r[:route]["summary"]["travelTimeInSeconds"].to_i),
                road: delay(r[:route]["summary"]["trafficDelayInSeconds"])}
        end
    end

    # send event
  send_event('tomtom', { results: routes } )
end

def seconds_in_words(secs)
    m, s = secs.divmod(60)
    h, m = m.divmod(60)

    plural_hours = if h > 1 then "s" else "" end
    plural_minutes = if m > 1 then "s" else "" end

    if secs >= 3600
        "#{h} hour#{plural_hours}, #{m} min#{plural_minutes}"
    else
        "#{m} min#{plural_minutes}"
    end
end

def delay(delay_seconds)
    m, s = delay_seconds.divmod(60)
    h, m = m.divmod(60)

    if delay_seconds >= 60
        "#{m} min delay"
    elsif delay_seconds == 0
        ""
    else
        "#{s} sec delay"
    end
end

I removed the longest_leg functionality as this API route no longer returns street information (roadName, etc.)

Hope this helps, thanks to @sighmin for the original!

@jonoeoo
Copy link

jonoeoo commented Jul 9, 2015

Thanks @jbenner this looks great!
I'm new to ruby but how can you add more locations to the location array?

locations << { name: "Sam", location: URI::encode('-25.764803,28.34625') }
locations << { name: "John", location: URI::encode('-25.764803,28.34625') }

doesn't seem to work...

@jonoeoo
Copy link

jonoeoo commented Jul 15, 2015

Found a solution.
Easiest way is to add locations when defining the array

locations = [ { name: "Sam", location: URI::encode('-25.764803,28.34625') }, { name: "John", location: URI::encode('-25.764803,28.34625') }]

@mika4president
Copy link

mika4president commented Sep 26, 2016

Hi @jonoeoo,

Sorry to bother you but do you happen to know how to define this array? I'm new at ruby and am having trouble to implement this widget with more then one address, similiair like you.

I now have

locations       = []
locations << { name: "Same, location: URI::encode('52.37829,4.89983') }

Which works, but when I change that to

locations       = []
locations = [ { name: "Sam", location: URI::encode('-25.764803,28.34625') }, { name: "John", location: URI::encode('-25.764803,28.34625') }]

The widget doesn't show any traffic info anymore.
Could you help me with the proper syntax maybe?

Kind regards,

Michel

@karlashi
Copy link

karlashi commented Nov 2, 2016

@mika4president
maybe you already figured it out. This is the format I have it:
locations << { name: "xxx", location: URI::encode('45.7685623,-72.9380278') }
locations << { name: "yyy", location: URI::encode('45.728491,-72.859983') }

Each line is one person.

@titusece
Copy link

titusece commented Apr 4, 2017

Hello,

I'm also trying to work.
Its display empty.
Can you please help ?

require 'net/http'
require 'net/https'
require 'uri'
require 'json'

office_location = URI::encode('-26.116525,28.031015')
key = URI::encode('wD9DZdZbclVBtMMY')
#key = URI::encode('u3oMj4stA2SPyH4quz4Ahq5Q8VREK7mo')

#locations = []
#locations << { name: "Sam", location: URI::encode('-25.764803,28.34625') }

#locations << { name: "xxx", location: URI::encode('45.7685623,-72.9380278') }
#locations << { name: "yyy", location: URI::encode('45.728491,-72.859983') }

locations = []
locations << { name: "Same, location: URI::encode('52.37829,4.89983') }

SCHEDULER.every '10m', :first_in => '15s' do |job|
routes = []

# pull data
locations.each do |location|
    uri = URI.parse("https://api.tomtom.com/routing/1/calculateRoute/#{office_location}:#{location[:location]}/json?routeType=fastest&traffic=true&travelMode=car&key=#{key}")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE

    request = Net::HTTP::Get.new(uri.request_uri)
    response = http.request(request)
    routes << { name: location[:name], location: location[:location], route: JSON.parse(response.body)["routes"][0] }
end

# find winner
if routes
    routes.sort! { |route1, route2| route2[:route]["summary"]["travelTimeInSeconds"] <=> route1[:route]["summary"]["travelTimeInSeconds"] }
    routes.map! do |r|
        { name: r[:name],
            time: seconds_in_words(r[:route]["summary"]["travelTimeInSeconds"].to_i),
            road: delay(r[:route]["summary"]["trafficDelayInSeconds"])}
    end
end

# send event

send_event('tomtom', { results: routes } )
end

def seconds_in_words(secs)
m, s = secs.divmod(60)
h, m = m.divmod(60)

plural_hours = if h > 1 then "s" else "" end
plural_minutes = if m > 1 then "s" else "" end

if secs >= 3600
    "#{h} hour#{plural_hours}, #{m} min#{plural_minutes}"
else
    "#{m} min#{plural_minutes}"
end

end

def delay(delay_seconds)
m, s = delay_seconds.divmod(60)
h, m = m.divmod(60)

if delay_seconds >= 60
    "#{m} min delay"
elsif delay_seconds == 0
    ""
else
    "#{s} sec delay"
end

end

Regards,
Titus S.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment