eCourier RSS Feed eCourier News RSS Feed

At eCourier we move fast. Keep up to date with the latest eCourier news by subscribing to our RSS feed.
(What's this?)

In the news

eCourier News Stop the press and read some happy articles all about yours truly.
Technology Overview Aiba API

Map animations

As well as making a map, we can also animate the couriers driving:
This is a simple animation, but much more detail can be added as in this example. The code is very similar to the map example so we won't discuss it in detail but will point out the differences:
#!/usr/bin/ruby

require 'xml/libxml'
require 'RMagick'
The view location is a little different:
lat_min = 51.44
lat_max = 51.59

lon_min = -0.2
lon_max = 0

WIDTH = 400
HEIGHT = 400

class Mercator
  include Math
  
  def initialize(lat, lon, degrees_per_pixel, width, height)
    @clat = lat
    @clon = lon
    @degrees_per_pixel = degrees_per_pixel
    @width = width
    @height = height
    @dlon = width / 2 * degrees_per_pixel
    @dlat = height / 2 * degrees_per_pixel  * cos(@clat * PI / 180)
    
    @tx = xsheet(@clon - @dlon)
    @ty = ysheet(@clat - @dlat)
    
    @bx = xsheet(@clon + @dlon)
    @by = ysheet(@clat + @dlat)
    
  end
  
  def kilometerinpixels
    return 40008.0  / 360.0 * @degrees_per_pixel
  end
  
  def ysheet(lat)
    log(tan(PI / 4 +  (lat  * PI / 180 / 2)))
  end

  def xsheet(lon)
    lon
  end
  
  def y(lat)
    return @height - ((ysheet(lat) - @ty) / (@by - @ty) * @height)
  end

  def x(lon)
    return  ((xsheet(lon) - @tx) / (@bx - @tx) * @width)
  end
end

@proj = Mercator.new((lat_min + lat_max) / 2, (lon_max + lon_min) / 2, (lat_max - lat_min) / WIDTH, HEIGHT, HEIGHT)

@canvas = Magick::Image.new(WIDTH, HEIGHT)
@gc = Magick::Draw.new
@gc.stroke('black')
@gc.stroke_width(1)

@positions = Hash.new

@count = 0
@mcount = 0
@oldgc = nil


def draw()
  puts "writing frame #{@mcount}"
  @gc.draw(@canvas)

  fnum = @mcount.to_s
  fnum += '0' if fnum.length == 1

  @canvas.write("courier_map-#{fnum}.jpg")

  @gc = Magick::Draw.new
  @mcount += 1
  @count = 0
end

def plot(doc)
  doc.find('courier').each do |courier|
    y = @proj.y(courier['latitude'].to_f)
    x = @proj.x(courier['longitude'].to_f)
    if @positions[courier['type'] + courier['id']]
      oldx = @positions[courier['type']+courier['id']][0]
      oldy = @positions[courier['type']+courier['id']][1]
      @gc.line(oldx,oldy,x,y) unless ((x-oldx)*(x-oldx))+((y-oldy)*(y-oldy)) > 100
    else
      @gc.line(x,y,x,y)
    end
    @positions[courier['type'] + courier['id']] = [x,y]
    @count += 1
    if @count > 100
      draw()
    end
  end
end

2.times do |i|
  xml = `curl -s 'http://api.ecourier.co.uk/xmlapi/0.1/API_KEY/PASSWORD/courier/time_range/2002-02-22%2010:00/2007-02-22%2012:00?page=#{i}'`
  p = XML::Parser.new
  p.string = xml
  doc = p.parse
  plot(doc)
end

Lastly, once the frames are written out they need to be converted in to an animation. This can be done with imagemagicks 'convert' utility like this:
  convert courier*jpg animation.gif
Though it creates a rather large gif file. This can be optimised in the gimp with filters -> animation -> optimise.