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.