Make a map of London
We can use successive courier locations to plot their path and make a crude geometrical map of where they go as an aggregate of all couriers. This is very similar to how openstreetmap makes its maps. It looks like this:
We use a simple ruby script to generate the image. First, we set up ruby and define some constants that center on London:
#!/usr/bin/ruby require 'xml/libxml' require 'RMagick' lat_min = 51.46 lat_max = 51.57 lon_min = -0.2 lon_max = 0 width = 400 height = 400Next, we define a class which can convert between latitude and longitude to our screen coordinates:
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
#the following two functions will give you the x/y on the entire sheet
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
Now we create a projection object for our viewpoint. We then create some image magick objects we use to draw our lines on to and set them to be one pixel wide black lines.
@proj = Mercator.new((lat_min + lat_max) / 2, (lon_max + lon_min) / 2, (lat_max - lat_min) / width, width, height)
canvas = Magick::Image.new(width, height)
@gc = Magick::Draw.new
@gc.stroke('black')
@gc.stroke_width(1)
We use a hash to store the 'old' positions so we can iterate through the data and be able to find the previous location to draw the line from. The hash key is the courier type + id. We make sure not to draw lines longer than 100 pixels as these may be cases where id's have been switched with distant couriers.
@positions = Hash.new
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']][0]
oldy = @positions[courier['type']][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]
end
end
Next is the central loop of the code. It gets five pages of API data and plots each, using libxml to parse the data. Be sure to substitue your own api key and password here:
5.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
Finally when all lines are drawn we draw the data to a JPEG file:
@gc.draw(canvas)
canvas.write('courier_map.jpg')
