Creating an image map from SVG

I was asked how I made the map in my examples earlier.

I wrote a small script to do it. (The script is quite limited – I only made it complete enough to handle the SVG files I was using. Others might break it. Also, it requires pyparsing… and hoo-boy is that slow.)

Example!

Wikipedia is good for this, and has provided me with the example file I’ll use, a map of the USA. If you have some GIS data already, I believe that ArcGIS 9.2 has native SVG support, or it looks like you can convert ESRI shapefiles with shp2svg.

My example file is filled with all sort of crud that isn’t a definition of state boundaries, though, so I need to get just that. Perusing it (in a text editor or a SVG editor like Inkscape) reveals that all the state borders are in a group named “States”. Helpful!

So I run my script: svg2imagemap.py Map_of_USA_with_state_names.svg 960 593 States

(The “960 593” is the size of the image I’m creating from the SVG file.)

This creates an html file named [svg name].html, so Map_of_USA_with_state_names.html. It only contains the area tags, so I dump them into an image map in a page set up like the one in the other examples…

And we get: A map of the USA.

Just to disclaim again: That script is unlikely to be immediately useful for any particular SVG image. You would almost certainly need to tweak it significantly to make it work for your purposes. But it’s a good start, at least.

One last time: I make no guarantee of this script working on an arbitrary SVG file. At best it’s an example of an approach to take. If you use it, expect to have to debug how it interacts with your particular file.

Join the Conversation

38 Comments

  1. Hi David (not the director),

    Great tutorial !

    What group name did you use for countries in the world wikipedia svg map ?

    Thanks in advance !

  2. The world map was very complicated to do — part of the reason I chose the USA map for the example was that it didn’t involve getting bogged down in painful meddling in the internals of an SVG file.

    Firstly, the link on the world map demo page is going to the core map — I actually used this variant: http://en.wikipedia.org/wiki/I

    I edited the SVG file in Inkscape and removed a lot of the tiny islands. I also had to repeatedly change the scale of the image here, in order to make the output line up with the final image. This was very trial-and-error.

    Then I had to use an extremely long command line, because there is no single group id that includes all countries. Instead, each country has its two-letter ISO code as an id. So it looked something like:

    python svg2imagemap.py BlankMap-World6-Equirectangular.svg 1000 507 se id ye mg …[etc]… jp au th ve

    In retrospect it might have been easier to edit the SVG file to just remove everything that wasn’t a country.

    Finally, I hand-edited the output to include better titles than, say, “path5325”.

    Complicated and painful. In all honesty, if you need a world map imagemap, it would probably be a lot easier to just rescale the one used on my demo. (It’s far simpler to take the already-rewritten map and just scale all the coordinates than it is to deal with the SVG again.)

  3. David –

    I looked at your code a bit, I think you might be spending a lot of time in CaselessPreservingLiteral. Since you only use this class in Command, and all command args are single characters, could you try this?

    def Command(char):
    “”” Case insensitive but case preserving”””
    # return CaselessPreservingLiteral(char)
    return oneOf([char.upper(),char.lower()])

    You might also get some speed boost from these Regex forms of floating and non-neg numbers:
    floatingPointConstant = Regex(r”[-+]?d.?d([Ee][-+]?d)?”)
    nonnegativeNumber = Regex(r”d
    .?d([Ee][-+]?d)?”)

    Otherwise, without some real in-depth profiling, I don’t see any likely places for improving performance.

    Nice utility!
    — Paul

  4. Hello David,

    I just found this post googling for a way to create an imagemap out of an SVG file. Unfortunately I have 0 programming skill. I have no clue how to run a python script (just downloaded python, but have found no way to run your script with the correct command line). Do you plan on creating an application (.exe) for this? Or could you explain to a dummy like me how to use your script?

    I’m working on a map (currently a large .psd file) of a Fantasy world which I’d like to turn into several navigable client side imagemaps (.png’s of world, continents, regions, countries, counties). From the low scale world map you could navigate to a large scale county map and get more details etc.

  5. Think I finally got it to work, was pretty simple too:-(. At least I have a test map that’s working.

    Thanks a lot for this script.

  6. Hi, I’m tryng your python script, but I’m experiencing some problems.
    When I first run the script I got this error:
    C:Python25>python.exe svg2imagemap.py Italia.svg 1000 1000 regioni
    Traceback (most recent call last):
    File “svg2imagemap.py“, line 49, in
    raw_width = float(svg.getAttribute(‘width’))
    ValueError: invalid literal for float(): 553.423px

    I have a tryed editing line 49 e 50 from:

    raw_width = float(svg.getAttribute(‘width’))

    to

    raw_width = float(svg.getAttribute(‘width’).replace(‘px’, ”))

    But now no errors are displayed, but the result file is 0 byte.

    If can be useful for debug the doctype of the file is:

    Any help appreciated, thank you

  7. Hi,

    I tried using your svg2imagemap.py script to rescale the Wikipedia BlankMap-World6-Equirectangular.svg map, but the script doesn’t seem to work. Did you use it in your example? The width_ratio and height_ratio formulas don’t seem to do anything to my image, and the path coordinates come out way higher than the image size that I gave on the command line. I’m trying to get an image that is 630px wide.

    Thanks for any help / comments.

  8. David –

    I noticed that your image map of the world contains many doubled coordinates, that is, consecutive coordinates that are the same x-y pair (probably due to float-to-integer roundoff).

    Here (http://pyparsing.pastebin.com/… is a pyparsing script to post-process your generated image map – on your map of the world, it removes over 7700 duplicate coords. The maximum number of duplicates was 14 on line 108.

    — Paul

  9. Interestingly, I want to do the same thing for my django-worldmap application – http://lurkingideas.net/worldm….

    Having spent the several hours looking for a solution, it seems the only way to do it is to parse the svg and draw the map again somehow – like you have done.

    Thanks, at least I know there is probably no other way 🙂

  10. Giovanni, if you are using Inkscape to draw the image, try to save it as Plain SVG instead of Inkscape SVG.

    I had the same problem and solved this way.

    Best regards!

  11. One may also use the Batik framework [1] for that. There’s a patch which is directed towards creating an image map which, although with some limitations, can be a serious helper! 😉
    Unfortunately, to use the patch, some development experience is needed (one needs to user the source version, not a binary release).

    Hope this helps,
    Helder Magalhães

    [1] http://xmlgraphics.apache.org/
    [2] https://issues.apache.org/bugz

  12. HI THERE- awesome script. wondering how to scale the coordinates so that the script works with an image that is 600 px wide by 247 high (i already made the image)

  13. woohoo ! very cool! liked that!

    thanks very much!
    keep on publishing 😉

  14. One more tool to mention. There’s http://sourceforge.net/project… which only works sometimes, for instance with the sample provided. I think the problem is excessive error checking. Somebody should go into the Java code and make it more tolerant of various svg files. Inkscapemap would then me the most turnkey solution for creating imagemaps from svg files.

  15. That is some pretty useful information for saving images. Thanks for your research. I shall put it to work immediately.

  16. I’m wondering if implementing interacting graphics using image maps yields better performance than inserting SVG into the browser’s DOM using javascript?

    Sourced with the SVG of an athletic shoe, I have been able to convert the SVG XML into paths that the Raphael javascript library can use to insert SVG into the dom and make each path interactive (i.e. react on hover).

    From initial tests, it seems to me like image maps do perform better, but I would like to figure out a good way to benchmark this. Any ideas?

  17. Open up your vector-based map in Adobe illustrator and Save for web & devices — select HTML & Images and it churns out the and coords.

  18. Outstanding jquery plugin! I wonder if there is a way to disable the highlighting from taking place when hovering over the map itself and only trigger it from other text links? This would be similar to your example where the link text makes the star highlight except that when the user hovers their mouse over the star it wouldn’t highlight.

  19. Hi David,
    I am trying to run the below script to generate a smaller size of th png without success.
    svg2imagemap.py Map_of_USA_with_state_names.svg 800 553 States

    Traceback (most recent call last):
    File “svg2imagemap.py“, line 33, in ?
    import parse_path
    File “parse_path.py“, line 8
    from pyparsing import (ParserElement, Literal, Word, CaselessLiteral,
    ^
    SyntaxError: invalid syntax

  20. @bmishra: That would imply that you’re using a version of Python before 2.4, which is when the parenthesis in the from ... import statement were added to the syntax.

  21. Thank you David for your quick response.
    I installed python 2.4.4 then ran. HTML file is created with 0 bytes.
    C:map>svg2imagemap.py Map_of_USA_with_state_names.svg 960 593 States
    States matrix(1.00433, 0, 0, 1.00906, -19.9329, -136.405)

  22. Hi,
    Any idea why is the output HTML file is 0 bytes after running the script? If anyone has HTML for 500px png file that would also help. Thank you

  23. To resize existing map, you can use this code, it will recalculate the coords by a new ratio – the one you used to resize the png image:
    def resize_map_area():
    output = []

    def compute_new_size(old_size):<br>        old_size = old_size.groups()[0]<br>#        print old_size<br>        return str(int(round(int(old_size) * 0.9375)))
    
    coords = []<br>    for l in f.readlines():<br>        match = re.search(re_coords,l)<br>        if match:<br>            coords_line = match.groups()<br>#            print coords_line<br>            finds = re.sub(re_coord,compute_new_size,coords_line[1])<br>#            print finds<br>            print l<br>            replaced_coords = re.sub(re_coords,finds,l)<br>            print coords_line[0] + replaced_coords + coords_line[2]<br>            output.append(coords_line[0] + replaced_coords + coords_line[2])<br>        else:<br>            output.append(l)
    
  24. Thank you Vladimir,
    Is there way a to show or hide a small grahics (for ex, a small plus sign)in each state? I have to show one of four different types of small graphics in each state.

  25. for all those who are getting 0 byte html file, edit parse_path.py, change line 153 into:

        if command[0] == 'M' or command[0] == 'm':<br>line 157 into:
    
        elif command[0] == 'L' or command[0] == 'l':<br>and finally line 159 into:
    
        elif command[0] == 'C' or command[0] == 'c':
    

    (making it case insensitive to these directives).

  26. I keep getting this error:
    File “F:htmlmsvg2imagemap.py“, line 73
    print e.getAttribute(‘id’), e.getAttribute(‘transform’)
    ^

    SyntaxError: invalid syntax

    is your script bugged, or what is going on? here?

  27. And after downgrading from Python 3.2.2 to 2.7.2, the error changes to:
    File “F:htmlmsvg2imagemap.py”, line 49, in
    raw_width = float(svg.getAttribute(‘width’))
    ValueError: invalid literal for float(): 1000px

  28. This was very helpful for me. I used it to generate an image map from an Inkscape SVG. Had to change the settings to export absolute positioning instead of relative (the default). Many thanks.

  29. This is the only useful result for a google search on the matter, so I thought I might as well add a few tips here.

    How to convert any sgv to image map:

    this script works for sgv files created with gimp, and gimp in turn can read any sgv file. So you can effectively convert any sgv to an image map with this script. 

    Gimp’s UI is godawful, but when opening an sgv you have a pop-up giving you the option to import paths. Do that, and afterwards, in the “layers window” there is a tab called paths. From there you can right-click on the imported path and export it as svg.
    You need to edit the exported svg a bit:

    • the width and height attributes of the svg need to be in pixels and without the unit of measure, so “100” is OK, but “100px” or “1.25in” are not;
      – you need to wrap the tags in a tag.

    Then you can feed it to the script and you’ll get an image map.

  30. Thanks so much for making this available, I used it to make an imagemap of Europe from an svg and it worked really well.

Leave a comment

Your email address will not be published. Required fields are marked *