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

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 = 400
Next, 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')