4

AFAIK, you need to provide a list of coordinates to Shapely in order to create a shape but that's not how SVG <path> element works. Is there a way to create a Shapely shape using an SVG <path> element?

1 Answer 1

7

You can use the possibility to transform matplotlib geometries into shapely geometries as in Converting Matplotlib contour objects to Shapely objects.

For that, it is necessary to convert first the SVG paths into matplotlib Path objects, then this matplotlib path into a shapely geometry.

1) With the svg_parse() function in the firefox.py script for example

import re
import string
import matplotlib
import numpy as np
from matplotlib.path import Path
svgpath = "M10 10 C 20 20, 40 20, 50 10Z"
def svg_parse(path):
      commands = { 'M' : (Path.MOVETO,),   'L' : (Path.LINETO,),
                   'Q' : (Path.CURVE3,)*2, 'C' : (Path.CURVE4,)*3,
                   'Z' : (Path.CLOSEPOLY,) }
      path_re = re.compile(r'([MLHVCSQTAZ])([^MLHVCSQTAZ]+)', re.IGNORECASE)
      float_re = re.compile(r'(?:[\s,]*)([+-]?\d+(?:\.\d+)?)')
      vertices = []
      codes = []
      last = (0,0)
      for cmd, values in path_re.findall(path):
          points = [float(v) for v in float_re.findall(values)]
          points = np.array(points).reshape((len(points)//2,2))
          if cmd in string.lowercase:
              points += last
          cmd = string.capitalize(cmd)
          last = points[-1]
          codes.extend( commands[cmd] )
          vertices.extend( points.tolist() )
      return codes, vertices
codes, verts = svg_parse(svgpath)
mpl_path = Path(verts, codes)
type(mpl_path)
<class 'matplotlib.path.Path'>
mpl_path.vertices
array([[ 10.,  10.],
       [ 20.,  20.],
       [ 40.,  20.],
       [ 50.,  10.]])
 mpl_path.to_polygons()
[array([[ 10.       ,  10.       ],
       [ 11.953125 ,  11.796875 ],
       [ 16.5234375,  14.609375 ],
       [ 21.6796875,  16.484375 ],
       [ 27.1875   ,  17.421875 ],
       [ 32.8125   ,  17.421875 ],
       [ 38.3203125,  16.484375 ],
       [ 43.4765625,  14.609375 ],
       [ 48.046875 ,  11.796875 ],
       [ 50.       ,  10.       ]])]

from shapely.geometry import Polygon
coords = mpl_path.to_polygons()
from shapely.geometry import Polygon
Polygon(coords[0]).wkt
'POLYGON ((10 -10, 11.953125 -11.796875, 16.5234375 -14.609375, 21.6796875 -16.484375, 27.1875 -17.421875, 32.8125 -17.421875, 38.3203125 -16.484375, 43.4765625 -14.609375, 48.046875 -11.796875, 50 -10, 10 -10))'

2) With svgpath2mpl that does the same thing directly

from svgpath2mpl import parse_path
svgpath = 'M10 10 C 20 20, 40 20, 50 10Z'
mpl_path = parse_path(svgpath)
coords = mpl_path.to_polygons()
from shapely.geometry import Polygon, LineString
Polygon(coords[0]).wkt
'POLYGON ((10 -10, 11.953125 -11.796875, 16.5234375 -14.609375, 21.6796875 -16.484375, 27.1875 -17.421875, 32.8125 -17.421875, 38.3203125 -16.484375, 43.4765625 -14.609375, 48.046875 -11.796875, 50 -10, 10 -10))'
LineString(coords[0]).wkt
'LINESTRING (10 10, 11.953125 11.796875, 16.5234375 14.609375, 21.6796875 16.484375, 27.1875 17.421875, 32.8125 17.421875, 38.3203125 16.484375, 43.4765625 14.609375, 48.046875 11.796875, 50 10, 10 10, 10 10)'

3) If you have to take into account the multiple levels of transformation that are embedded in the SVG path (transform=..) as well as parent groups you can use parse_svg_path.py (with the drawing.svg example here, see Understanding and parsing SVG paths)

from parse_svg_path import *
svg_paths= parse_svg_path('drawing.svg')
coortrans is (0.28222222115604934, 0, 0, 0.28222222115604934, 0.0, 0.0)
have trans [0.29893589, 0.0, 0.0, 0.13478681, 50.825779, 127.66697]
need to multiple [1.5568383, 0.0, 0.0, 4.682378, -27.754271, 177.53645] and [0.29893589, 0.0, 0.0, 0.13478681, 50.825779, 127.66697]
need to multiple (1, 0, 0, 1, 0.0, -698.0315) and [0.46539484279658694, 0.0, 0.0, 0.63112279383418, 51.37324837453569, 775.32146165466]
using transform [0.13134476624862318, 0.0, 0.0, 0.17811687669809367, 14.498672264262863, 21.812944651244006]
have trans (1, 0, 0, 1, 0.0, -698.0315)
using transform [0.28222222115604934, 0.0, 0.0, 0.28222222115604934, 0.0, -197.00000036688886]
path= svg_paths[0]
from shapely.geometry import Polygon
Polygon(path.polys[0]).wkt
'POLYGON ((48.84738024352663 48.09492517619944, 48.84738024352663 63.73581341125079, 47.58575334511613 59.91604177422839, 45.76642212128969 58.65148141525384, 42.92609155116321 62.83270563015968, 42.27424192419559 69.40841735942483, 39.16993989665328 68.12576745045283, 40.34306406467012 59.34829422975322, 38.66970021991877 59....'
2
  • Trying coords = mpl_path.to_polygons(), I get AttributeError: 'Path' object has no attribute 'to_polgygons' Commented Mar 16, 2021 at 8:38
  • This is an excellent answer and the second option using svgpath2mpl is working for me. Note that it is more complicated if the shapes have holes in them (like rendered text). Also svgpath2mpl doesn't have an API for controlling the detail level of the linear approximation to curves, although I suspect one could easily be added.
    – Nelson
    Commented Oct 3, 2021 at 16:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.