Blog of Appliness

Working with SVG maps

svgMap_zoomin

This article was written by Antanas Marcelionis and originally published on his blog at: http://ammap.com/.

In this post I will try to explain how you can quickly create an interactive and zoomable map from a simple SVG map downloaded from Wikipedia (or anywhere else).

I don’t use any 3rd party libraries in this tutorial and will try to keep it very simple, so even if you are not JavaScript guru, it will be easy to follow.

Part I, Modifying SVG map

To start, let’s save this SVG map of Europe:

http://en.wikipedia.org/wiki/File:Blank_map_europe.svg

Below is the map without any modifications.

If you view the source of this SVG file with a text editor, you will see that it’s XML which you can modify:

1   <?xml version=”1.0” encoding=”UTF-8” standalone=”no”?>
2   <svg
3      xmlns:dc=”http://purl.org/dc/elements/1.1/”
4      xmlns:cc=”http://creativecommons.org/ns#”
5      xmlns:rdf=”http://www.w3.org/1999/02/22-rdf-syntax-ns#”
6      xmlns:svg=”http://www.w3.org/2000/svg”
7      xmlns=”http://www.w3.org/2000/svg”
8      xmlns:sodipodi=”http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd”
9      xmlns:inkscape=”http://www.inkscape.org/namespaces/inkscape”
10     xml:space=”preserve”
11     width=”680”
12     height=”520”
13     viewBox=”1754 161 9938 7945”
14     version=”1.0”
15     id=”svg2”
16     sodipodi:version=”0.32”
17     inkscape:version=”0.46”
18     sodipodi:docname=”Blank_map_europe2.svg”
19     sodipodi:docbase=”C:\Documents and Settings\Botek\Desktop”
20     inkscape:output_extension=”org.inkscape.output.svg.inkscape”><metadata
21       id=”metadata78”><rdf:RDF><cc:Work
22           rdf:about=””><dc:format>image/svg+xml</dc:format><dc:type
23           rdf:resource=”http://purl.org/dc/dcmitype/StillImage” /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
24       inkscape:window-height=”691”
25       inkscape:window-width=”1014”
26       inkscape:pageshadow=”2”
...

 

So let’s try it – let’s make Lithuania (because I live there :) ) green. Without looking into the structure of the document, I searched for Lithuania keyword first, but didn’t find anything. So I searched for “lt” (iso country code of Lithuania), including quotes (otherwise I would find a lot of matches), and bingo – found the match on line 364:

362      style=”fill: rgb(192, 192, 192); stroke: rgb(255, 255, 255); stroke-width: 8; fill-opacity: 1;”
363       transform=”translate(0.0005875, 7.53846e-05)” /><path
364       id=”lt”
365       class=”eu europe”
366       d=”M 7581.781,4394.3979 C 7584.6474,4391.5317 7584.6474,4385.799

I assumed that the XML node starting with <path whose attribute is my match (id=lt) referenced Lithuania’s area on the map. I checked other attributes of “path” and found this one:

style=”fill: rgb(192, 192, 192); stroke: rgb(255, 255, 255); stroke-width: 8; fill-opacity: 1;”

And changed it to:

style=”fill: #00DD00; stroke: rgb(255, 255, 255); stroke-width: 8; fill-opacity: 1;”

(I like HEX color codes more, and guessed that SVG should understand it too). I then saved the file and opened it in browser:

As you see, you can easily modify SVG yourself. Sometimes coloring countries in different colors is all you need. In next part we’ll dynamically change colors.

Part II. Changing SVG map with JavaScript

First, let’s include our SVG map into a basic HTML page. Add the SVG element to the page using <object> tag. I get the “contentDocument” of this object so I can access SVG document later. Note, I do this only after SVG is loaded:

<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE html>  
<html xmlns=”http://www.w3.org/1999/xhtml”>
    <head>
        <title>SVG map tutorial</title>
        <script type=”text/javascript”>
            var svgDoc;
            function colorizeCountries()
            {
                svgDoc = document.getElementById(“map”).contentDocument;
            }
        </script>  
    </head>
    <body>
        <object data=”europe.svg” onload=”colorizeCountries()id=”map” type=”image/svg+xml”></object>
    </body>
</html>

Let’s try to color another country with JavaScript. Since SVG elements represent countries using ISO country codes for the id, let’s get the IDs and change the “fill” style:

function colorizeCountries()
{
     svgDoc = document.getElementById(“map”).contentDocument; 
 
     var lithuania = svgDoc.getElementById(“lt”);
     lithuania.style.fill = “#00CC00”;
 
     var latvia = svgDoc.getElementById(“lv”);
     latvia.style.fill = “#CC0000”;
 
     var estonia = svgDoc.getElementById(“ee”);
     estonia.style.fill = “#0000CC”;                
}

And here is the result:

Handling the click event

Let’s say I want the map to zoom in when one of the colored countries is clicked. To do this, I add click event listeners to each of the countries and create methods to handle these events:

function colorizeCountries() {
      svgDoc = document.getElementById(“map”).contentDocument;
      var lithuania = svgDoc.getElementById(“lt”);
      lithuania.addEventListener(“click”, lithuaniaClicked, false)
      lithuania.style.fill = “#00CC00”;
 
      var latvia = svgDoc.getElementById(“lv”);
      latvia.addEventListener(“click”, latviaClicked, false)
      latvia.style.fill = “#CC0000”;
 
      var estonia = svgDoc.getElementById(“ee”);
      estonia.addEventListener(“click”, estoniaClicked, false)
      estonia.style.fill = “#0000CC”;              
      }
 
      function lithuaniaClicked()
      {
 
      }
 
      function latviaClicked()
      {
 
      }
 
      function estoniaClicked()
      {
 
      }

Zooming the map

There are two ways to zoom in the SVG. One is using the  viewBox attribute of SVG element, another would be to apply scale and translate transformations to the group of all the paths and other shapes in your SVG. We will use the first method.

This may be the most difficult part in this tutorial, but it’s quite simple geometry, so don’t be afraid. First, locate viewBox attribute in the svg:

viewBox=7088 3766 950 917

The value settings for viewbox include:

  • minx—the beginning x coordinate
  • miny—the beginning y coordinate
  • width—width of the view box
  • height—height of the view box

Just above viewBox attribute are the width=”680” and  height=”520” attributes – we’ll need these numbers too. You can modify them to change the size of your map, by the way.

As you can see, the values of viewBox don’t look like pixels, right? Actually we don’t care too much about what they mean. All we want to do is to zoom in the map so that the clicked country is big. I grabbed a screenshot of the HTML page with the map and pasted it to Photoshop. Then, I marked the area to be enlarged (for better results, keep width and height proportional to width and height of your map window). Next, I checked the x coordinate (in pixels) of left and right borders and the y coordinate of the top and bottom borders. You can easily do this by selecting area from top/left corner – the size of selection will be displayed in the “info” panel.

OK, so now I have pixels but I obviously need something else, as these numbers don’t look like the ones from viewBox attribute. My guess is that I need to calculate ratio of original viewBox width and width of the map, same with the height and then use ratio to convert my pixels to new viewBox values. Let’s create a method called getViewBox:

function getViewBox(x1, y1, x2, y2)
{
    // viewBox data (from SVG file)
    var svgMinX = 1754;
    var svgMinY = 161;
    var svgWidth = 9938;
    var svgHeight = 7945;
 
    // actual size (from SVG, but can also be set in HTML)
    var width = 680;
    var height = 520;
 
    // width and height ratio
    var wRatio = svgWidth / width;
    var hRatio = svgHeight / height;
 
    // desired width in pixels 
    var desiredWidth = x2 - x1;
 
    // desired height in pixels
    var desiredHeight = y2 - y1;
 
    // new viewBox values                
    var newWidth = Math.round(desiredWidth * wRatio);
    var newHeight = Math.round(desiredHeight * hRatio);
 
    // initial minX and minY must be added
    var newMinX = svgMinX + Math.round(x1 * wRatio);
    var newMinY = svgMinY + Math.round(y1 * hRatio);
 
    return newMinX + “ “ + newMinY + “ “ + newWidth + “ “ + newHeight;
}

To be sure it’s correct I simply called this method with values I measured using Photoshop:

getViewBox(365, 236, 430, 296);

and entered the values I’ve got directly in my SVG file:

viewBox=7088 3767 950 917

And the map now looks like this:

Let’s make the same map using JavaScript (do not forget to bring old values back before proceeding).

function lithuaniaClicked()
 
{
 
      var viewBox = getViewBox(365, 236, 430, 296);
 
      svgDoc.getElementById(“svg2”).setAttribute(“viewBox”, viewBox);
 
}

svgDoc.getElementById(“svg2”) gets the first node of the SVG document and sets new viewBox value (“svg2” is the id of this first node in this example, so it might be different in your code). I did the same for other two countries with slightly different values and here is final code:

<?xml version=1.0” encoding=”UTF-8?>
 
<!DOCTYPE html>  
 
<html xmlns=”http://www.w3.org/1999/xhtml”>
 
    <head>
 
        <title>SVG map tutorial</title>
 
        <script type=”text/javascript”>
 
            var svgDoc;
 
            function colorizeCountries()
 
            {
 
                svgDoc = document.getElementById(“map”).contentDocument;
 
                var lithuania = svgDoc.getElementById(“lt”);
 
                lithuania.addEventListener(“click”, lithuaniaClicked, false)
 
                lithuania.style.fill = “#00CC00”;
 
                var latvia = svgDoc.getElementById(“lv”);
 
                latvia.addEventListener(“click”, latviaClicked, false)
 
                latvia.style.fill = “#CC0000”;
 
                var estonia = svgDoc.getElementById(“ee”);
 
                estonia.addEventListener(“click”, estoniaClicked, false)
 
                estonia.style.fill = “#0000CC”;                
 
            }
 
            function lithuaniaClicked()
 
            {
 
                 var viewBox = getViewBox(365, 236, 430, 296);
 
                 svgDoc.getElementById(“svg2”).setAttribute(“viewBox”, viewBox);
 
            }
 
            function latviaClicked()
 
            {
 
                 var viewBox = getViewBox(366, 213, 445, 273);
 
                 svgDoc.getElementById(“svg2”).setAttribute(“viewBox”, viewBox);                
 
            }
 
            function estoniaClicked()
 
            {
 
                 var viewBox = getViewBox(368, 191, 447, 250);
 
                 svgDoc.getElementById(“svg2”).setAttribute(“viewBox”, viewBox);                
 
            }  
 
            function getViewBox(x1, y1, x2, y2)
 
            {
 
                // viewBox data (from SVG file)
 
                var svgMinX = 1754;
 
                var svgMinY = 161;
 
                var svgWidth = 9938;
 
                var svgHeight = 7945;
 
                // actual size (from SVG, but can also be set in HTML)
 
                var width = 680;
 
                var height = 520;
 
                // width and height ratio
 
                var wRatio = svgWidth / width;
 
                var hRatio = svgHeight / height;
 
                // desired width in pixels 
 
                var desiredWidth = x2 - x1;
 
                // desired height in pixels
 
                var desiredHeight = y2 - y1;
 
                // new viewBox values                
 
                var newWidth = Math.round(desiredWidth * wRatio);
 
                var newHeight = Math.round(desiredHeight * hRatio);
 
                // initial minX and minY must be added
 
                var newMinX = svgMinX + Math.round(x1 * wRatio);
 
                var newMinY = svgMinY + Math.round(y1 * hRatio);
 
                return newMinX + “ “ + newMinY + “ “ + newWidth + “ “ + newHeight;
 
            }                      
 
        </script>  
 
    </head>
 
    <body>
 
        <object data=”europe.svgonload=”colorizeCountries()” id=”map” type=”image/svg+xml”></object>
 
    </body>
 
</html>

Open the page and click on one of the three countries – the map should zoom in. Now try to extend this example and do the zoom with animation :)

And this is it for this tutorial. I hope you enjoyed it and you will get some use from it.

Comments

comments

Powered by Facebook Comments

One Comment

  1. Mike Johnson
    November 14, 2012

    This is a great tutorial. Thank you very much for sharing.

    Reply

Leave a Reply