Tuesday, January 15, 2013

Kartograph tutorial III: Symbols


Adding symbols to a map is almost mandatory. Kartograph has this capability using the addSymbols method.
By the way, the documentation page is not well linked in the web.  The right symbols documentation page is here.

The data

As usual, all the data used in the example is available. I have switched to GitHub, so the files are there.I have also created a web page to put all the stuff related to the blog: http://rveciana.github.com/geoexamples/
We will use a map of Morocco for the examples. All the borders and cities are taken from Natural Earth. The pages are at the "1:10m cultural vectors" page, and the layers used are countries and populated places.

Since the web page will need an SVG image to get the map data, generate using the command:

    kartograph -o morocco.svg morocco.json

Then, we will use a csv file to read the data we will put in the map, so ogr2ogr is necessary to convert the files from the original shapefile:

ogr2ogr -clipdst -12 26 0 38 -f csv csv ne_10m_populated_places.shp

Note that the option -clip is used to reduce the output file size, which is quite critical when drawing it with JavaScript.

Drawing the data as bubbles


The basic way to draw point data using Kartograph (or the most used in the examples) is what they call bubbles. A bubble is a circle with a user defined radius that may be used to represent data values.
Let's see the Javascript corresponding to this working example:
function loadMap(){
var map = Kartograph.map('#map', 500, 0);
map.loadMap('morocco.svg', function() {
     
    map.addLayer('world',{'name':'bg',
                'styles': {
                    'stroke': '#aaa',
                    'fill': '#f6f4f2'
                }});
    map.addLayer('world',{'name':'moroccobg'});
    map.addLayer('world',{'name':'moroccofg'});
 
     
    map.getLayer('moroccobg').style('stroke', function(data) {
       return data.country == "Morocco" ? '#d8d6d4' : '#aaa';
    }).style('stroke-width', function(data) {
       return data.country == "Morocco" ? 10 : 1;
    }).style('stroke-linejoin', function(data) {
       return data.country == "Morocco" ? 'round' : null;
    });
     
    map.getLayer('moroccofg').style('stroke', function(data) {
       return data.country == "Morocco" ? '#333' : '#aaa';
    }).style('fill', function(data) {
       return data.country == "Morocco" ? '#fff' : null;
    });

    $.ajax("./data/csv/ne_10m_populated_places.csv").done(
     function(data) {
        var points = [];
        var textLines = data.split(/\r\n|\n/);
        for (var i=1; i<textLines.length; i++) {
            var fields = textLines[i].split(',');
            points.push({lon: parseFloat(fields[22]), lat: parseFloat(fields[21]), name: fields[4], population: parseInt(fields[26])});
        }
         
        $.fn.qtip.defaults.style.classes = 'qtip-light';
        var scale = $K.scale.sqrt(points, 'population').range([0, 40]);

        map.addSymbols({
            type: Kartograph.Bubble,
            data: points,
            location: function(d) { return [d.lon, d.lat]; },
            radius: function(d) { return scale(d.population);},
            tooltip: function(d) {
                return '

'+d.name+'

population: '+d.population; }, sortBy: 'radius desc', style: 'fill:#800; stroke: #fff; fill-opacity: 0.4;' }); }); }, { padding: -2 }) };

Let's see the different parts of this first example:
  •  First the background data is loaded into a map object. Lines 2-3
  • Then, the layers are added with the addLayer method, using alias so the same layer can get more than one style, as explained here
  • After that, the styles are set to each layer, up to the line 26
  • Then, using the jQuery ajax function, the csv file is retrieved. This file contains de big cities location, name and population, that will be used to draw the bubbles.
  • Once the csv file is loaded, the code can draw the bubbles. First, the csv is parsed into an array points containing objects with the necessary info. Lines 30-35.
  • The qtip is inicializad. This will enable the tooltips showing the city name when the mouse is over the bubble. Line 37
  • A scale is defined. This Kartograph function takes all the data (population field from the points array), gets the maximum and minimum values, applies the function we want to set the radius (sqrt, so the circle area is proportional to the population). Then, the range method sets the minimum and maximum radius we want to use. Line 38
  • Then, the bubbles are added using the addSymbols method. In this case, the options used are: Line 40
    • type: The Bubbles type is set here. I haven't found a good list of all the available types.
    • data: The points array.
    • location: Where in every item in the data array the longitude and latitude can be found. In this case, the data is passed in an array, but the object can be also a kartograph.LonLat object.
    • radius: In this case, the scale function defined before is called, but any other possibility is OK if the function returns the number of pixels. The radius can be a fixed number too.
    • tooltip: When filled, this option sets the tool tip shown when the mouse is over the symbol. To do so, the jquery.qtip.js must be included, although is used with this option instead what you will find in the official qtip docs. Any html code can be added here. 
    • order: Sets the order to draw the symbols. Ordering by radius allows the small icons to be over the big ones, so the 
    • style: Finally, the bubble style can be set here
  • The padding is set as the second parameter of the loadMap method.  This clips a little the map, so it the lines closing the polygons are not visible. Check which value is better in every case. Line 56

Clustering 

 

As you can see in the previous example,  if the number of bubbles to draw in a small area is too big, the result is not very attractive. To avoid that, two clustering options are provided:
  • Noverlap: Avoids, as the name suggests, the bubbles overlapping.  A small overlapping can be allowed using the tolerance parameter
  • k-Means: First, a number of bubbles is defined (64 by default). Then, every element is assigned to one of the clusters.
You can compare the results and see the different source codes at this web page. Let's see only the part of the code that changes so the post keeps a reasonable length. Using the noverlap method as in this example web:

map.addSymbols({
            type: Kartograph.Bubble,
            data: points,
            location: function(d) { return [d.lon, d.lat]; },
            radius: function(d) { return scale(d.population);},
            tooltip: function(d) {
                return '

'+d.name+'

population: '+d.population; }, clustering: 'noverlap', aggregate: function(d){ var name = "" var population = 0; $.each(d, function(i, city) { name += " "+city.name; population += city.population; }); return { name: name, population: population }; }, sortBy: 'radius desc', style: 'fill:#800; stroke: #fff; fill-opacity: 0.4;' });

  • clustering: This option appears so be set as noverlap or k-mean. Line 11
  • aggregate: This is the important part. The structure returned here will overwrite the original from the data. The position is already calculated, but the other parameters can be re-calculated.
    In our case, since several cities are added as one single bubble, the most logical is to represent this point with the aggregate population. The parameter d in the function will be an array with all the points, so we use the .each method from jQuery to iterate. Line 15.
    The name is also overwritten to show all the cities included in the new bubble.
If the clustering option is using k-Means, the clustering option will be changed as follows:
clustering: 'k-means',
clusteringOpts: {
    size: 64
},

Changing the size tag to the number of clusters to represent. I think that in this case is important to tune, since the results change a lot.

As a final comment to the Kartograph clustering, there is a thing I don't understand: Some bubbles present when no clustering is applied disappear with the noverlap method. For instance, look at the comparison example to the city of Tindouf, the most southern of all. The city disappears in the noverlap method, and is not aggregated to any new bubble, although no overlap occurs. When using the k-Means, is properly aggregated to other bubbles.

 

Image icons

Is possible to use images as symbols instead of bubbles. This allows to put fixed icons as in the cool La Bella Italia example.
Here, we will see how to represent the geotagged Flickr images on a map (which was the original idea for this post).
There are two differences from the original code.
The first one is that Flickr offers feeds in JSON format. The cool thing is that is possible to get only pictures from a selected country, region or city. So the example could be used at city level.
The code and the working page are here, but the part that changes is, of course the parsing of the csv file, which looks now like this:

$.getJSON("http://www.flickr.com/services/feeds/geo/Morocco?jsoncallback=?",
     {
       format: "json"
     },
     function(data) {
        var points = []
        $.each(data.items, function(i,item){
            points.push([new $K.LonLat(item.longitude,item.latitude),item.title,item.media.m]);
            if ( i == 100 ) return false;
        });

Is much easier than the other, since the JSON file is transformed directly into an object.
The second difference is the addSymbols:
map.addSymbols({
    type: $K.Icon,
    data: points,
    location: function(d) {return d[0]; },
    icon: function(d) { return d[2]; },
    iconsize: [30,30],
    offset: [-15,-15],
    title: function(d) {return d[1]; },
    tooltip: function(d) {return [d[1],''];},
});

Note that:
  • Here, the LonLat object has been used instead of the array to set the location (just to show how to do it).
  •  The type is now Icon
  • The label icon sets the image source, which is part of the Flickr feed in this example.
  • The iconsize label can change the icon size. By default is 10x10 pixels.
  • The offset label centers the icon (I have checked that the location is set at the left top of tie image)
  • Tooltip and title must be set this way to give the result shown. Both interact with the qtip plugin.
Finally, I'm very frustrated with the clustering when using icons. In this case is completly necessary, since many pictures are at the same touristic points, but I haven't found how to do it. Maybe somebody can help.