123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- #!/usr/bin/python
- import sys
- import json,copy,re
- from xml.etree import ElementTree as ET
- import numpy as np
- from more_itertools import peekable
- np.cosd = lambda x: np.cos(x*np.pi/180)
- np.sind = lambda x: np.sin(x*np.pi/180)
- xscale = 0.01
- yscale = 0.01
- geodata = { "type": "FeatureCollection", "features": [] }
- featuretemplate = {
- "type": "Feature",
- "properties": { "tags": { "name": "default room name" },
- "relations": [ { "reltags": { "height": "4", "level": "0", "type": "level" } } ],
- "meta": {}
- },
- "geometry": {
- "type": "Polygon",
- "coordinates": [[]]
- }
- }
- def SVGTransforms(s,pts):
- pts = [ np.reshape(p+[1],(3,1)) for p in pts ]
- transforms = re.findall("[a-zA-Z]+\([^\)]+\)",s.get('transform') or '')
- # reverse order because SVG transforms apply right-to-left
- transforms.reverse()
- mat = np.identity(3)
- for t in transforms:
- if t.startswith("matrix"):
- t = re.sub("[a-zA-Z]+\(([^\)]+)\)","\\1",t)
- mat = matrixTransform( [ float(n) for n in re.split("[ ,]",t) ], mat )
- # TODO: translate, skewX, skewY transforms
- elif t.startswith("translate"):
- pass
- elif t.startswith("skew"):
- pass
- elif t.startswith("scale"):
- t = re.sub("[a-zA-Z]+\(([^\)]+)\)","\\1",t)
- n = re.split("[ ,]",t)
- sx = float(n[0])
- sy = float(n[1]) if len(n)>1 else sx # y scale is optional
- mat = matrixTransform( [ sx,0,0,sy,0,0 ], mat )
- elif t.startswith("rotate"):
- # TODO: rotate about a given point using translate() and this code
- t = re.sub("[a-zA-Z]+\(([^\)]+)\)","\\1",t)
- n = re.split("[ ,]",t)
- a = float(n[0])
- x = float(n[1]) if len(n)>1 else None
- y = float(n[2]) if len(n)>1 else None
- mat = matrixTransform( [ np.cosd(a), np.sind(a), -np.sind(a), np.cosd(a) ], mat )
- # this is gross
- return [ np.dot(mat,p)[0:2].transpose().tolist()[0] for p in pts ]
- def transformPoint(pt):
- # Inkscape uses +y up coordinates internally
- # this doesn't invert the y-axis for now, but might at some point...
- return np.add(np.multiply(pt,[1*xscale,1*yscale]),[0,0]).tolist()
- def matrixTransform(L,R):
- # L is given in SVG order, abcdef, zero-padded if 6 elements not given
- L += ([0]*(6-len(L)) if len(L) < 6 else [])
- L = np.vstack( [ np.reshape( L, (3,2) ).transpose(), [0,0,1] ] )
- return np.dot( L, R )
- def getCoord(iterable):
- return float(iterable.next())
- def main():
- ns="http://www.w3.org/2000/svg"
- if len(sys.argv) > 1 :
- try:
- xmltree = ET.parse(sys.argv[1])
- except IOError:
- print("Error, not a valid file")
- sys.exit(1)
- except:
- print("Unknown error")
- sys.exit(1)
- else:
- print("No SVG to parse specified!")
- print("Usage: python svgRectToGeoJSON.py file.svg")
- sys.exit(1)
- shapes = []
- shapes += xmltree.getroot().findall("./{%s}g/{%s}rect"%(ns,ns))
- shapes += xmltree.getroot().findall("./{%s}g/{%s}path"%(ns,ns))
- for s in shapes:
- shapeid = s.get('id')
- if ( s.tag == '{%s}rect'%ns ):
- x = float(s.get('x'))
- y = float(s.get('y'))
- w = float(s.get('width'))
- h = float(s.get('height'))
- pts = [ [x,y],[x+w,y],[x+w,y+h],[x,y+h],[x,y] ]
- pts = SVGTransforms(s,pts)
- geomObject(pts,name=shapeid)
- elif ( s.tag == '{%s}path'%ns ):
- pathdata = re.split("([a-zA-Z, ])",s.get('d'))
- # split ops and their arguments, discard whitespace and commas
- # peekable() is used instead of iter() so we can peek() at next value
- ops = peekable( filter( (lambda str: not str.isspace() and str not in ['',',']), pathdata))
- spot = [0,0]
- pts = []
- for o in ops:
- if( o.isalpha() ):
- if ( o == 'Z' or o == 'z' ): # close path command
- pts.append( pts[0] )
- break
- elif ( o == 'M' or o == 'm' ):
- pts = []
- while not ops.peek().isalpha():
- if ( o == 'M' ):
- spot = [getCoord(ops),getCoord(ops)]
- elif ( o == 'm' ):
- spot[0] += getCoord(ops)
- spot[1] += getCoord(ops)
- pts.append(spot)
- print >> sys.stderr, "Path pts %s"%pts
- pts = SVGTransforms(s,pts)
- geomObject(pts,name=shapeid)
- elif ( o == 'H' ): # horizontal line command
- spot[0] = getCoord(ops)
- elif ( o == 'h' ):
- spot[0] += getCoord(ops)
- elif ( o == 'V' ): # vertical line command
- spot[1] = getCoord(ops)
- elif ( o == 'v' ):
- spot[1] += getCoord(ops)
- elif ( o == 'L' ): # line command
- spot[0] = getCoord(ops)
- spot[1] = getCoord(ops)
- elif ( o == 'l' ):
- spot[0] += getCoord(ops)
- spot[1] += getCoord(ops)
- # the only non-drawing directives M and Z will skip this bit
- pts.append(spot)
- else:
- print >> sys.stderr, "Unexpected <path> operation %s, ignoring..." % o
- geomObject(pts,name=shapeid)
- else:
- print >> sys.stderr, "Unhandled tag %s encountered, skipping..." % s.tag
- print(json.dumps(geodata, indent=3))
- def geomObject(pts,name="default room"):
- f = copy.deepcopy(featuretemplate)
- for pt in pts:
- f['geometry']['coordinates'][0].append( transformPoint(pt) )
- f['properties']['tags']['name'] = name
- geodata['features'].append(f)
- if __name__ == "__main__":
- main()
|