From SVG to Geo Coordinates – A Complete Guide

Why This Task Is Not Trivial?

First of all what do we have? There is a vector shape, which may represent a map, which we’d like to convert into a GEO map. In other words there is a SVG file containing the source shape, that you’d like to convert in geoJSON or whatever collection of geo points. This is not trivial, of course, first of all because there’s no an algorithm or automation that can do this, and because everybody knows that the resulting map will be only approached, but will never be so accurate as the vector shape. This is because in a vector shape you may contain Bézier Curves, which I’ll show a little bit later in this post, that are difficult to represent in geo coordinates.

So the first task will be to find an approaching algorithm. However there are two things that are optimistic:

  1. You can’t effectively represent Bézier curves in geo coordinates, but even if you could do it there’s no practical need, because the collection of geo coordinates will be huge and this will slow down you’re application. Remember that geoJSON is yet again possibly used by your browser and the amount of geo points will be proportionally slowing down the app.
  2. As you may know Google’s visualization map is representing the World’s countries again with quite approached maps. Take a look at the following image – the country borders are quite sharpened:

Google Visualization Map

So far we know that we need an approaching algorithm that will convert vector lines and possibly curves in geo coordinates, but before we proceed we’ve to understand the SVG format.

SVG Format and Possible Approaches

Let me give you an example of a SVG file. By opening such file with a text editor you can see the file format:

M76.484,153.703c-0.86-0.27-3.816,0.199-4.002-1.055
	c-0.049-0.328-1.109-1.799-1.512-1.824c-0.647-0.04-5.182,1.896-5.219,2.013c-0.051,0.161,0.928,0.627,0.319,0.832
	c-0.028-0.003-0.716-0.36-0.816-0.334c-0.13,0.034-0.262,0.834-0.363,0.771c-0.176-0.24-0.179-0.48-0.007-0.719
	c-0.986-0.764-0.345,0.764-0.526,0.764c-0.771,0,0.184-0.849-0.675,0.037c-2.13-0.433-2.267-0.756-4.129-2.754
	c-1.329-1.42,0.383-8.158-1.571-8.535c-2.223-3.153-6.413-3.037-8.806-5.037c0.107-0.278,0.201-0.563,0.282-0.853
	c0.369,0.372-4.136-0.768-4.373-0.862c-1.498-0.604-3.574-0.133-4.656,0.355c-0.028-1.22,0.171-2.329-0.593-3.027
	c0.588-2.548-0.652-4.752-2.98-4.199c-0.443-1.354,0.142-0.868-1.171-1.483c-0.154-2.069,2.703-7.729-2.239-8.185
	c2.009-3.137-6.201-0.985-6.627-3.125c-0.14-0.703-5.574,1.563-5.752,0.192c-0.39-2.987-4.411-2.185-5.371-3.372
	c0.518,0.641-6.067,1.078-5.645,0.85c-0.118,0.063-1.38,5.328-1.307,4.994c-0.457,2.076,3.774,9.01,2.893,9.625
	c-0.495,0.347-3.159,6.972-1.295,8.588c-0.59-0.512,2.894-0.209,3.163-0.129c1.333,0.396,3.091,1.66,3.469,3.176
	c0.52,2.486-0.793,2.359-2.357,3.013c-0.658,0.274-2.289,6.779-2.368,7.461c-0.319,2.747-1.413,4.468-1.264,7.483
	c0.098,1.984-0.368,2.326,0.891,4.287c1.933,3.004,2.018,2.57,2.178,5.813c-0.127,1.556-1.742,3.565-1.568,5.388
	c0.074,0.77,0.485,2.028,0.235,2.795c-0.286,0.875-1.936,1.557-1.573,2.627c3.253-2.051,4.522,3.092,8.168,2.83
	c0.958-0.069,3.996,1.508,4.536,1.938c1.437,1.12,2.39-1.618,3.232-1.618c-1.037,0,3.461,2.315,2.95,1.774
	c0.828,0.873,3.852-0.123,5.026,0.304c2.416,0.881,6.011-1.386,7.227-2.174c1.616-1.048,3.15-1.38,4.396-2.816
	c0.639-0.737,2.375-4.448,3.254-4.366c0.354,0.032,0.59,1.01,1.06,0.652c0.163-0.126-1.075-2.664,0.689-2.664
	c0.935,0,4.283-2.113,3.799-3.344c-1.384-3.521,4.799-1.365,6.683-1.926c0.335-0.1,7.816-8.23,7.865-8.641
	C70.216,157.721,77.5,154.025,76.484,153.703C76.247,153.629,76.721,153.777,76.484,153.703z

This text represent this shape:

SVG Map

First thing to notice is there are things quite different from the HTML markup. Of course this things are noticeable. There is a M, c and z where the first two are followed by some numbers. What are these characters? Well lets see at W3.org. There are lots of SVG specific commands, but we need to understand only few of them.

1. M (absolute) m (relative) – moveto

Start a new sub-path at the given (x,y) coordinate. M (uppercase) indicates that absolute coordinates will follow; m (lowercase) indicates that relative coordinates will follow. If a moveto is followed by multiple pairs of coordinates, the subsequent pairs are treated as implicit lineto commands. Hence, implicit lineto commands will be relative if the moveto is relative, and absolute if the moveto is absolute. If a relative moveto (m) appears as the first element of the path, then it is treated as a pair of absolute coordinates. In this case, subsequent pairs of coordinates are treated as relative even though the initial moveto is interpreted as an absolute moveto.

2. Z or z – closepath

Close the current subpath by drawing a straight line from the current point to current subpath’s initial point. Since the Z and z commands take no parameters, they have an identical effect.

3. C (absolute) c (relative) – curveto (x1 y1 x2 y2 x y)+

Draws a cubic Bézier curve from the current point to (x,y) using (x1,y1) as the control point at the beginning of the curve and (x2,y2) as the control point at the end of the curve. C (uppercase) indicates that absolute coordinates will follow; c (lowercase) indicates that relative coordinates will follow. Multiple sets of coordinates may be specified to draw a polybézier. At the end of the command, the new current point becomes the final (x,y) coordinate pair used in the polybézier.

4. L (absolute) l (relative) – lineto (x y)+

Draw a line from the current point to the given (x,y) coordinate which becomes the new current point. L (uppercase) indicates that absolute coordinates will follow; l (lowercase) indicates that relative coordinates will follow. A number of coordinates pairs may be specified to draw a polyline. At the end of the command, the new current point is set to the final set of coordinates provided.

1. Bézier Curves

So far we know that the especially the c command draws a curve – a Bézier curve. But what is a Bézier curve anyway? Here’s a little explanation you can find at Wikipedia. Here’s an example of a cubic Bézier curve:

Cubic Bezier CurveFirst thing we see is that here there are four points – p0, p1, p2, p3. The curve between p0 and p3 is defined by them and the two control points – p1 and p2. To be more precise let me show you an animation of how this curve is constructed:

Cubic Bezier Curve

This is the way the red line is constructed with the help of the green and blue lines, and of course the points p1 and p2. The result is a smooth line that’s often used in animation and shapes. This is our case. As you’ve seen the example above is constructed only with curves.

Before we proceed let me say that there are lots of types of Bézier curves not only cubic. Here they are:

1.1. Linear Bézier curve

Linear Bezier Curve

1.2. Quadratic Bézier curve

Quadratic Bezier Curve1.3. And event Higher-order curves:

Bezier Curves

2. What You Need To Know About The SVG Format

However the most important thing to know is the heart of SVG curve format. First you MOVE to a point with the M command. Lets see how does it look in our case:

M76.484,153.703 – This simply means: move to the point with coordinates (76.484, 153.703)

The next step is to draw a curve. We’ve seen that a cubic curve is defined by four points – p0, p1, p2 and p3, but what we have in the SVG format:

c-0.86-0.27-3.816,0.199-4.002-1.055

Here there are only three points (-0.86, -0.27), (-3.816, 0.199), (-4.002, -1.055) – not four. Well these are only the last three points, the first one is the one defined by the M command. Next thing to know is that the M command defines an absolute point in the canvas of the SVG file, while the c (shouldn’t be mistaken with C) is a relative curve. This means that if the p0 is the point (76.484, 153.703), the p1 point is relative to it with small difference in coordinates – (-0.86, -0.27).

However the most important thing to know is that after drawing the curve:

At the end of the command, the new current point becomes the final (x,y) coordinate pair used in the polybézier.

This is extremely important! This means that the last point of this curve becomes the p0 of the next curve, and the three points of the second curve, in our case:

c-0.049-0.328-1.109-1.799-1.512-1.824

are relative to the last (x, y) of the previous curve – (-4.002, -1.055). Without this you cannot convert any SVG shape into geo coordinates.

3. Lines Instead of Curves

OK, I said about an approaching algorithm, that is only approaching the curved vector shape. This means that I can simply replace the curves with lines. Thus I’ve to change to SVG file format from using the c command to the l command, as l is also relative. The curve:

M76.484,153.703 c-0.86-0.27-3.816,0.199-4.002-1.055

will become

M76.484,153.703 l-4.002-1.055

and so forth. This will result into the more sharpened shape:SVG Line Map

However this is again a SVG file and no Geo coordinates are present. To end up with this there’s a tiny PHP script that collects all the last pairs from the c commands and converts them to Geo Coordinates relative to the World’s center – (Lat, Lon) = (0, 0).

<?php
 
$str = '-0.86-0.27-3.816,0.199-4.002-1.055
c-0.049-0.328-1.109-1.799-1.512-1.824c-0.647-0.04-5.182,1.896-5.219,2.013
c-0.051,0.161,0.928,0.627,0.319,0.832c-0.028-0.003-0.716-0.36-0.816-0.334
c-0.13,0.034-0.262,0.834-0.363,0.771c-0.176-0.24-0.179-0.48-0.007-0.719
c-0.986-0.764-0.345,0.764-0.526,0.764c-0.771,0,0.184-0.849-0.675,0.037
c-2.13-0.433-2.267-0.756-4.129-2.754c-1.329-1.42,0.383-8.158-1.571-8.535
c-2.223-3.153-6.413-3.037-8.806-5.037c0.107-0.278,0.201-0.563,0.282-0.853
c0.369,0.372-4.136-0.768-4.373-0.862c-1.498-0.604-3.574-0.133-4.656,0.355
c-0.028-1.22,0.171-2.329-0.593-3.027c0.588-2.548-0.652-4.752-2.98-4.199
c-0.443-1.354,0.142-0.868-1.171-1.483c-0.154-2.069,2.703-7.729-2.239-8.185
c2.009-3.137-6.201-0.985-6.627-3.125c-0.14-0.703-5.574,1.563-5.752,0.192
c-0.39-2.987-4.411-2.185-5.371-3.372c0.518,0.641-6.067,1.078-5.645,0.85
c-0.118,0.063-1.38,5.328-1.307,4.994c-0.457,2.076,3.774,9.01,2.893,9.625
c-0.495,0.347-3.159,6.972-1.295,8.588c-0.59-0.512,2.894-0.209,3.163-0.129
c1.333,0.396,3.091,1.66,3.469,3.176c0.52,2.486-0.793,2.359-2.357,3.013
c-0.658,0.274-2.289,6.779-2.368,7.461c-0.319,2.747-1.413,4.468-1.264,7.483
c0.098,1.984-0.368,2.326,0.891,4.287c1.933,3.004,2.018,2.57,2.178,5.813
c-0.127,1.556-1.742,3.565-1.568,5.388c0.074,0.77,0.485,2.028,0.235,2.795
c-0.286,0.875-1.936,1.557-1.573,2.627c3.253-2.051,4.522,3.092,8.168,2.83
c0.958-0.069,3.996,1.508,4.536,1.938c1.437,1.12,2.39-1.618,3.232-1.618
c-1.037,0,3.461,2.315,2.95,1.774c0.828,0.873,3.852-0.123,5.026,0.304
c2.416,0.881,6.011-1.386,7.227-2.174c1.616-1.048,3.15-1.38,4.396-2.816
c0.639-0.737,2.375-4.448,3.254-4.366c0.354,0.032,0.59,1.01,1.06,0.652
c0.163-0.126-1.075-2.664,0.689-2.664c0.935,0,4.283-2.113,3.799-3.344
c-1.384-3.521,4.799-1.365,6.683-1.926c0.335-0.1,7.816-8.23,7.865-8.641';
 
 
$matches = explode('c', $str);
 
$temp = array();
foreach ($matches as $k => $v) {
    // get the new relative point
    $s = $v;
 
    // a really dummy regex to find the last pair
    preg_match('/(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?/', $s, $m);
 
 
    // if the array is not empty
    if (!empty($temp)) {
        $m[5] = $temp[0]+$m[5];
        $m[6] = $temp[1]+$m[6];
    }
 
    echo '[', ($m[5]), ', ', ($m[6]), '],<br />';
 
    // save the last point
    $temp = array($m[5], $m[6]);
}

Final Adjustments

I guess you don’t need any shape in the World’s center as you may not need it in the same scale. There are two things to do. First you can scale the map by multiplying by a factor every point:

// a really dummy regex to find the last pair
    preg_match('/(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?(-?\d{1,2}\.?\d{0,3}),?/', $s, $m);
 
    // multiply by some factor
    $m[5] *= 0.05;
    $m[6] *= -0.05;
 
    // if the array is not empty
    if (!empty($temp)) {
        $m[5] = $temp[0]+$m[5];
        $m[6] = $temp[1]+$m[6];
    }

and than you can move the shape all over the world by adding some offset to the resulting point:

echo '[', ($m[5]-38.9), ', ', ($m[6]+10.7), '],<br />';

Further Researches

Here we just substitute the curves with lines, which is the most primitive approach. What about substituting each curve with corresponding two lines, or three lines. The resulting shape will be far more accurate in compare with the source “curved” shape. So there are still lots of things to be done. However this may help you do the job.

3 thoughts on “From SVG to Geo Coordinates – A Complete Guide

  1. Superb research and explanation. This is exactly what I’m trying to accomplish in one of my recent projects. This will help me to get me on the right track.

  2. In most cases, to save only the last point change dramatically the shape of the curve.
    My first option was to use the two control points as new points and connect them with lines, but it was not good either.

    Then I thought that maybe the best option is to ass points that are on the curve.

    What you need is:
    – 4 points of a Bezier curve
    – 2 equations to yield values for x and y
    – number of points you want to “rasterize” your curve

    Take this example :
    M0,0
    c35,0,50-50,100-50
    The four points are 0,0; 35,0; 50,-50 ; 100,-50; (0,0 because it’s relative coordinates).

    This link http://www.moshplant.com/direct-or/bezier/math.html gives you the explanation to calculate 6 values and create the two equations (one for x one for y).

    Because the Bezier curve is defined in [0,1], it’s easy to pick up values:
    – to have one extra point, take 1/2
    – to have 3 extra points, take 1/4, 1/2 and 3/4

    In my example, I want to add 3 points. The equations give me this:
    v=0.25
    x=23.359375
    y=-7.8125

    v=0.5
    x=44.375
    y=-25

    v=0.75
    x=68.203125
    y=-42.1875

    Now, you have to change your path.
    It was: c35,0,50-50,100-50
    Meaning: a curve from 0,0 (current point) to 100,-50

    Now you have to draw 4 lines.
    You need to calculate relative coordinates for each new point and the new coordinates of the final point (100,-50).

    you start at 0,0
    then 23.359375,-7.8125
    then you need to subtract the x and the y with the previous

    Then you create 4 lines:

    l23.359375-7.8125
    l21.015625,-17.1875
    l23.828125,-17.1875
    l31.796875,-7.8125

    And voilà! You have 4 segments instead of a Bezier curve.

    Tip: add all x and all y and you find the final point.

Leave a Reply

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