This time we would like to show an example of making world maps with countries painted according to some values. To make it possible we’ll use simple nevertheless very efficient gem Worldize.
When it comes to visualizing statistics on your website, heavy numbers won’t be your friend. People aren’t very likely to read an essay, or look at a table of statistics therefore you don’t want to drag your followers into analyzing bunch of data. However, there are always few ways to diversify: infographics, diagrams, charts etc.
But moving on from old school technics, we would like to introduce you our way to make your data appealing, while creating thematic map in Ruby.
In all statistical languages and packages there is an option to build a world map, where each country would be colored, depending on some quantity (e.g. life expectancy, panda’s population per capita, flat prices etc.). You could certainly make a map of basically anything you want.
Such maps are also called choropleth maps.
Let’s ask what Google is saying in such case – and “voila!”:
require 'worldize' Worldize::Countries.new. draw_gradient( '#D4F6C8', # gradient starts from this value... '#247209', # until this # some numeric value for different countries: {'Argentina' => 1, 'Bolivia' => 2, 'Chile' => 3 .... } )
Result:
Process:
- find data in open access “country name – country polygon coordinates”;
- learn drawing illustration with such polygons painted in different colours;
- create simple API around all of it.
GeoData and maps
We used open data access from Natural Earth, and then found its comfortable conversion – in one amazing repository. Comfortable formatting – is GeoJSON, which, compared to other cartographic formats, is easily readable and usable without any additional tools and libraries.
But this is only half the work. Geo coordinates received from such datasets are still needed to be converted in beautiful polygons on surface. We need some projection. Again Google says that the most popular and recognized projection is Web Mercator, used for modern online maps. After some work with formulas from Wikipedia, we receive this Ruby-code:
# longitude for x coordinate of the image: # just change from one range to another # max_x — width of the expected image x = lng.rescale(-180..180, 0..max_x) # Ruby supports Unicode variable names starting with version 1.9. (2007) include Math π = PI φ = -lat * π / 180 # convert value from degrees to radians # latitude for y coordinate of the image: # Formula with logarithms and tangents gives value from -π to π # and convert it out of this range in the range of "from 0 to the height of the image" y = log(tan(π / 4 + φ / 2)).rescale(-π..π, 0..max_y)
Useful method rescale that converts number from one diapason to another one, in our library is defined, at that using feature from Ruby 2 – refine:
module Refinements refine Range do def distance self.end - self.begin end end refine Numeric do def rescale(from, to) (self - from.begin).to_f / from.distance * to.distance + to.begin end end end # now, in any class or module ... module TryMe # ... where we write "using Refinements" ... using Refinements # ... all numeric variables will have a "rescale" method p 8.rescale(0..10, 0..100) # => 80 end # ...but in other places this method will not be available p 8.rescale(0..10, 0..100) # undefined method `rescale' for 8:Fixnum (NoMethodError)
By the way, it is used not only here, but also for coloring gradient calculations (check below).
Colors, gradients and drawing
First and main choice of every rubist who wants to draw something – is library RMagick. This cover has not very good reputation, non-understandable API, questionable productivity, and, besides, is inclined to memory leakage … – but if you need to draw something quickly, you just do it:
require 'rmagick' img = Magick::Image.new(width, height) # image object canvas = Magick::Draw.new # helper object for drawing on the image # ... canvas.polygon(*points) # draw :) # ... canvas.draw(img) # set objects to images img.write('test.png')
Getting used to RMagick consents, to fulfill the task becomes not that difficult.
There is only the last algorithmic task: to calculate color for every country – gradient from first color to the last one. There is an incredible gem color, using which, the code for choosing the color for each country with its numerical value becomes super simple:
# this function takes such arguments: # * the beginning and end of the gradient as CSS-colors: "#FF0000" or "white" # * hash dictionary with "country-numeric" value # * some other options (you can read about them on README) def draw_gradient(from_color, to_color, value_by_country, **options) min = value_by_country.values.min max = value_by_country.values.max from = ::Color::RGB.by_css(from_color) to = ::Color::RGB.by_css(to_color) values = value_by_country. map{|country, value| [country, value.rescale(min..max, 0..100)]}. # convert each country mapped value in the range of 0-100% map{|country, value| [country, from.mix_with(to, 100 - value).html]}. # calculate the color which corresponding to each percentage to_h # now we have a hash dictionary with "country-color" value
And here it is! Aside from simply making geographical and statistical data from your website easy to read, there are lots of ways to let your creativity flow and build various interactive maps. E.g. you could create city map of your retail coffee stands, country maps of dairy production households, maps of your servers loading in real time, well, actually everything you’re up to.
Source: mkdev
Tags: gems, geodata, interactive maps