def readXML(ifile): """ Read a list of Polygons from a XML file which was written with writeXML(). :Arguments: - ofile: see above :Returns: list of Polygon objects """ f, cl = getReadableObject(ifile) d = parseString(f.read()) if cl: f.close() plist = [] for pn in d.getElementsByTagName('polygon'): p = Polygon() plist.append(p) for sn in pn.childNodes: if not sn.nodeType == Node.ELEMENT_NODE: continue assert sn.tagName == 'contour' polist = [] for pon in sn.childNodes: if not pon.nodeType == Node.ELEMENT_NODE: continue polist.append((float(pon.getAttribute('x')), float(pon.getAttribute('y')))) assert int(sn.getAttribute('points')) == len(polist) p.addContour(polist, int(sn.getAttribute('isHole'))) assert int(pn.getAttribute('contours')) == len(p) return plist
def tile(poly, x=[], y=[], bb=None): """ Returns a list of polygons which are tiles of p splitted at the border values specified in x and y. If you already know the bounding box of p, you may give it as argument bb (4-tuple) to speed up the calculation. :Arguments: - p: Polygon - x: list of floats - y: list of floats - optional bb: tuple of 4 floats :Returns: list of new Polygons """ if not (x or y): return [poly] # nothin' to do bb = bb or poly.boundingBox() x = [bb[0]] + [i for i in x if bb[0] < i < bb[1]] + [bb[1]] y = [bb[2]] + [j for j in y if bb[2] < j < bb[3]] + [bb[3]] x.sort() y.sort() cutpoly = [] for i in range(len(x)-1): for j in range(len(y)-1): cutpoly.append(Polygon(((x[i],y[j]),(x[i],y[j+1]),(x[i+1],y[j+1]),(x[i+1],y[j])))) tmp = [c & poly for c in cutpoly] return [p for p in tmp if p]
def prunePoints(poly): """ Returns a new Polygon which has exactly the same shape as p, but unneeded points are removed. The new Polygon has no double points or points that are exactly on a straight line. :Arguments: - p: Polygon :Returns: new Polygon """ np = Polygon() for x in range(len(poly)): # loop over contours c = list(poly[x]) c.insert(0, c[-1]) c.append(c[1]) # remove double points i = 1 while (i < (len(c))): if c[i] == c[i-1]: del c[i] else: i += 1 # remove points that are on a straight line n = [] for i in range(1, len(c)-1): if __linVal(c[i-1:i+2]) != 0.0: n.append(c[i]) if len(n) > 2: np.addContour(n, poly.isHole(x)) return np
def Rectangle(xl=1.0, yl=None): """ Create a rectangular shape. If yl is not set, a square is created. :Arguments: - optional xl: float - optional yl: float :Returns: new Polygon """ if yl is None: yl = xl return Polygon(((0.0, 0.0), (xl, 0.0), (xl, yl), (0.0, yl)))
def fillHoles(poly): """ Returns the polygon p without any holes. :Arguments: - p: Polygon :Returns: new Polygon """ n = Polygon() [n.addContour(poly[i]) for i in range(len(poly)) if poly.isSolid(i)] return n
def Circle(radius=1.0, center=(0.0, 0.0), points=32): """ Create a polygonal approximation of a circle. :Arguments: - optional radius: float - optional center: point (2-tuple of float) - optional points: integer :Returns: new Polygon """ p = [] for i in range(points): a = 2.0 * pi * float(i) / points p.append((center[0] + radius * sin(a), center[1] + radius * cos(a))) return Polygon(p)
def Star(radius=1.0, center=(0.0, 0.0), beams=16, iradius=0.5): """ Create a star shape, iradius is the inner and radius the outer radius. :Arguments: - optional radius: float - optional center: point (2-tuple of float) - optional beams: integer - optional iradius: float :Returns: new Polygon """ p = [] for i in range(beams): a = 2.0 * pi * float(i) / beams p.append((center[0] + radius * sin(a), center[1] + radius * cos(a))) b = 2.0 * pi * (float(i) + 0.5) / beams p.append((center[0] + iradius * sin(b), center[1] + iradius * cos(b))) return Polygon(p)
def cloneGrid(poly, con, xl, yl, xstep, ystep): """ Create a single new polygon with contours that are made from contour con from polygon poly arranged in a xl-yl-grid with spacing xstep and ystep. :Arguments: - poly: Polygon - con: integer - xl: integer - yl: integer - xstep: float - ystep: float :Returns: new Polygon """ p = Polygon(poly[con]) for xi in range(xl): for yi in range(yl): p.cloneContour(0, xi*xstep, yi*ystep) return p
def decodeBinary(bin): """ Create Polygon from a binary string created with encodeBinary(). If the string is not valid, the whole thing may break! :Arguments: - s: string :Returns: new Polygon """ nC, b = __unpack('!I', bin) p = Polygon() for i in range(nC[0]): x, b = __unpack('!l', b) if x[0] < 0: isHole = 1 s = -2 * x[0] else: isHole = 0 s = 2 * x[0] flat, b = __unpack('!%dd' % s, b) p.addContour(tuple(__couples(flat)), isHole) return p
def convexHull(poly): """ Returns a polygon which is the convex hull of p. :Arguments: - p: Polygon :Returns: new Polygon """ points = list(pointList(poly, 0)) points.sort() u = [points[0], points[1]] for p in points[2:]: u.append(p) while len(u) > 2 and __left(u[-3:]): del u[-2] points.reverse() l = [points[0], points[1]] for p in points[2:]: l.append(p) while len(l) > 2 and __left(l[-3:]): del l[-2] return Polygon(u+l[1:-1])
def writePDF(ofile, polylist, pagesize=None, linewidth=0, fill_color=None): """ *This function is only available if the reportlab package is installed!* Write a the Polygons in polylist to a PDF file. :Arguments: - ofile: see above - polylist: sequence of Polygons - optional pagesize: 2-tuple of floats - optional linewidth: float - optional fill_color: color :Returns: ofile object """ from reportlab.pdfgen import canvas from reportlab.lib.colors import red, green, blue, yellow, black, white if not pagesize: from reportlab.lib.pagesizes import A4 pagesize = A4 can = canvas.Canvas(ofile, pagesize=pagesize) can.setLineWidth(linewidth) pp = [Polygon(p) for p in polylist] # use clones only bbs = [p.boundingBox() for p in pp] bbs2 = zip(*bbs) minx = min(bbs2[0]) maxx = max(bbs2[1]) miny = min(bbs2[2]) maxy = max(bbs2[3]) xdim = maxx - minx ydim = maxy - miny if not (xdim or ydim): raise Error("Polygons have no extent in one direction!") a = ydim / xdim width, height = pagesize if a > (height / width): width = height / a else: height = width * a npoly = len(pp) fill_color = __RingBuffer(fill_color or (red, green, blue, yellow)) for i in range(npoly): p = pp[i] bb = bbs[i] p.warpToBox(width * (bb[0] - minx) / xdim, width * (bb[1] - minx) / xdim, height * (bb[2] - miny) / ydim, height * (bb[3] - miny) / ydim) for poly in pp: solids = [poly[i] for i in range(len(poly)) if poly.isSolid(i)] can.setFillColor(fill_color()) for c in solids: p = can.beginPath() p.moveTo(c[0][0], c[0][1]) for i in range(1, len(c)): p.lineTo(c[i][0], c[i][1]) p.close() can.drawPath(p, stroke=1, fill=1) holes = [poly[i] for i in range(len(poly)) if poly.isHole(i)] can.setFillColor(white) for c in holes: p = can.beginPath() p.moveTo(c[0][0], c[0][1]) for i in range(1, len(c)): p.lineTo(c[i][0], c[i][1]) p.close() can.drawPath(p, stroke=1, fill=1) can.showPage() can.save()
def writeSVG(ofile, polylist, width=None, height=None, fill_color=None, fill_opacity=None, stroke_color=None, stroke_width=None): """ Write a SVG representation of the Polygons in polylist, width and/or height will be adapted if not given. fill_color, fill_opacity, stroke_color and stroke_width can be sequences of the corresponding SVG style attributes to use. :Arguments: - ofile: see above - polylist: sequence of Polygons - optional width: float - optional height: height - optional fill_color: sequence of colors (3-tuples of floats: RGB) - optional fill_opacity: sequence of colors - optional stroke_color: sequence of colors - optional stroke_width: sequence of floats :Returns: ofile object """ f, cl = getWritableObject(ofile) pp = [Polygon(p) for p in polylist] # use clones only [p.flop(0.0) for p in pp] # adopt to the SVG coordinate system bbs = [p.boundingBox() for p in pp] bbs2 = zip(*bbs) minx = min(bbs2[0]) maxx = max(bbs2[1]) miny = min(bbs2[2]) maxy = max(bbs2[3]) xdim = maxx - minx ydim = maxy - miny if not (xdim or ydim): raise Error("Polygons have no extent in one direction!") a = ydim / xdim if not width and not height: if a < 1.0: width = 300 else: height = 300 if width and not height: height = width * a if height and not width: width = height / a npoly = len(pp) fill_color = __RingBuffer(fill_color or ((255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0))) fill_opacity = __RingBuffer(fill_opacity or (1.0, )) stroke_color = __RingBuffer(stroke_color or ((0, 0, 0), )) stroke_width = __RingBuffer(stroke_width or (1.0, )) s = [ '<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>', '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">', '<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d">' % (width, height) ] for i in range(npoly): p = pp[i] bb = bbs[i] p.warpToBox(width * (bb[0] - minx) / xdim, width * (bb[1] - minx) / xdim, height * (bb[2] - miny) / ydim, height * (bb[3] - miny) / ydim) subl = [ '<path style="fill:rgb%s;fill-opacity:%s;fill-rule:evenodd;stroke:rgb%s;stroke-width:%s;" d="' % (fill_color(), fill_opacity(), stroke_color(), stroke_width()) ] for c in p: subl.append('M %g, %g %s z ' % (c[0][0], c[0][1], ' '.join([("L %g, %g" % (a, b)) for a, b in c[1:]]))) subl.append('"/>') s.append(''.join(subl)) s.append('</svg>') f.write('\n'.join(s)) if cl: f.close() return f
def tileBSP(p): """ This generator function returns tiles of a polygon. It will be much more efficient for larger polygons and a large number of tiles than the original tile() function. For a discussion see: http://dr-josiah.blogspot.com/2010/08/binary-space-partitions-and-you.html :Arguments: - p: Polygon :Returns: tiles of the Polygon p on the integer grid """ _int = int _floor = floor work = [p] while work: # we'll use an explicit stack to ensure that degenerate polygons don't # blow the system recursion limit polygon = work.pop() # find out how many points are in each row/column of the grid xs = defaultdict(_int) ys = defaultdict(_int) for poly in polygon: for x,y in poly: xs[_int(_floor(x))] += 1 ys[_int(_floor(y))] += 1 # handle empty polygons gracefully if not xs: continue # handle top and right-edge border points mvx = max(max(x for x,y in poly) for poly in polygon) vx = _int(_floor(mvx)) if len(xs) > 1 and mvx == vx: xs[vx-1] += xs.pop(vx, 0) mvy = max(max(y for x,y in poly) for poly in polygon) vy = _int(_floor(mvy)) if len(ys) > 1 and mvy == vy: ys[vy-1] += ys.pop(vy, 0) # we've got a single grid, yield it if len(xs) == len(ys) == 1: yield polygon continue # find the split if len(xs) < 2: spx, countx = xs.items()[0] countx *= 3 else: spx, countx = _find_split(xs) if len(ys) < 2: spy, county = ys.items()[0] county *= 3 else: spy, county = _find_split(ys) # get the grid bounds for the split minx = min(xs) maxx = max(xs) miny = min(ys) maxy = max(ys) # actually split the polygon and put the results back on the work # stack if (countx < county and not _single_poly(polygon, 0, minx + 1.0)) or _single_poly(polygon, 1, miny + 1.0): work.append(polygon & Polygon([(minx, miny), (minx, maxy+1), (spx, maxy+1), (spx, miny)])) work.append(polygon & Polygon([(spx, miny), (spx, maxy+1), (maxx+1, maxy+1), (maxx+1, miny)])) else: work.append(polygon & Polygon([(minx, miny), (minx, spy), (maxx+1, spy), (maxx+1, miny)])) work.append(polygon & Polygon([(minx, spy), (minx, maxy+1), (maxx+1, maxy+1), (maxx+1, spy)])) # Always recurse on the smallest set, which is a trick to ensure that # the stack size is O(log n) . if work[-2].nPoints() < work[-1].nPoints(): work.append(work.pop(-2))