Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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 ball_area(self):
     front_left = (self.x + self._catcher_area['front_offset'] + self._catcher_area['height']*1.5, self.y + self._catcher_area['width']/2.0)
     front_right = (self.x + self._catcher_area['front_offset'] + self._catcher_area['height']*1.5, self.y - self._catcher_area['width']/2.0)
     back_left = (self.x - self._catcher_area['front_offset'], self.y + self._catcher_area['width'])
     back_right = (self.x - self._catcher_area['front_offset'], self.y - self._catcher_area['width'])
     area = Polygon((front_left, front_right, back_left, back_right))
     area.rotate(self.angle, self.x, self.y)
     return area
Esempio n. 6
0
 def catcher_area(self):
     front_left = (self.x + self._receiving_area['front_offset'] + self._receiving_area['height'], self.y + self._receiving_area['width'] / 2.0)
     front_right = (self.x + self._receiving_area['front_offset'] + self._receiving_area['height'], self.y - self._receiving_area['width'] / 2.0)
     back_left = (self.x + self._receiving_area['front_offset'], self.y + self._receiving_area['width'] / 2.0)
     back_right = (self.x + self._receiving_area['front_offset'], self.y - self._receiving_area['width'] / 2.0)
     area = Polygon((front_left, front_right, back_left, back_right))
     area.rotate(math.pi / 2 - self.angle, self.x, self.y)
     return area
 def get_generic_polygon(self, width, length):
     '''
     Get polygon drawn around the current object, but with some
     custom width and length:
     '''
     front_left = (self.x + length/2, self.y + width/2)
     front_right = (self.x + length/2, self.y - width/2)
     back_left = (self.x - length/2, self.y + width/2)
     back_right = (self.x - length/2, self.y - width/2)
     poly = Polygon((front_left, front_right, back_left, back_right))
     poly.rotate(self.angle, self.x, self.y)
     return poly[0]
Esempio n. 8
0
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
Esempio n. 9
0
 def get_generic_polygon(self, width, length):
     '''
     Get polygon drawn around the current object, but with some
     custom width and length:
     '''
     front_left = (self.x + length / 2, self.y + width / 2)
     front_right = (self.x + length / 2, self.y - width / 2)
     back_left = (self.x - length / 2, self.y + width / 2)
     back_right = (self.x - length / 2, self.y - width / 2)
     poly = Polygon((front_left, front_right, back_left, back_right))
     poly.rotate(self.angle, self.x, self.y)
     return poly[0]
Esempio n. 10
0
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
Esempio n. 11
0
 def test_generic_polygon(self):
     '''
     Checks if the points returned by the
     generic polygon method are correct
     '''
     angles = ([0, pi/6, pi/4, pi/2, pi/2 + pi/6, pi/2 + pi/4,
                 pi/2 + pi/3, pi, pi + pi/6, pi + pi/4, pi + pi/3,
                 3*pi/2, 3*pi/2 + pi/6, 3*pi/2 + pi/4, 3*pi/2 + pi/3])
     for angle in angles:
         p_object = PitchObject(50, 50, angle, 0, 40, 20, 10)
         poly = Polygon(((60, 70), (60, 30), (40, 70), (40, 30)))
         poly.rotate(angle, 50, 50)
         assert_almost_equal(p_object.get_polygon(), poly[0])
Esempio n. 12
0
 def catcher_area(self):
     front_left = (self.x + self._receiving_area['front_offset'] +
                   self._receiving_area['height'],
                   self.y + self._receiving_area['width'] / 2.0)
     front_right = (self.x + self._receiving_area['front_offset'] +
                    self._receiving_area['height'],
                    self.y - self._receiving_area['width'] / 2.0)
     back_left = (self.x + self._receiving_area['front_offset'],
                  self.y + self._receiving_area['width'] / 2.0)
     back_right = (self.x + self._receiving_area['front_offset'],
                   self.y - self._receiving_area['width'] / 2.0)
     area = Polygon((front_left, front_right, back_left, back_right))
     area.rotate(math.pi / 2 - self.angle, self.x, self.y)
     return area
Esempio n. 13
0
 def test_generic_polygon(self):
     '''
     Checks if the points returned by the
     generic polygon method are correct
     '''
     angles = ([
         0, pi / 6, pi / 4, pi / 2, pi / 2 + pi / 6, pi / 2 + pi / 4,
         pi / 2 + pi / 3, pi, pi + pi / 6, pi + pi / 4, pi + pi / 3,
         3 * pi / 2, 3 * pi / 2 + pi / 6, 3 * pi / 2 + pi / 4,
         3 * pi / 2 + pi / 3
     ])
     for angle in angles:
         p_object = PitchObject(50, 50, angle, 0, 40, 20, 10)
         poly = Polygon(((60, 70), (60, 30), (40, 70), (40, 30)))
         poly.rotate(angle, 50, 50)
         assert_almost_equal(p_object.get_polygon(), poly[0])
Esempio n. 14
0
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]
Esempio n. 15
0
 def get_pass_path(self, target):
     '''
     Gets a path represented by a Polygon for the area for passing ball between two robots
     '''
     robot_poly = self.get_polygon()
     target_poly = target.get_polygon()
     return Polygon(robot_poly[0], robot_poly[1], target_poly[0],
                    target_poly[1])
Esempio n. 16
0
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
Esempio n. 17
0
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
Esempio n. 18
0
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)))
Esempio n. 19
0
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
Esempio n. 20
0
    def __init__(self, pitch_num):
        config_json = tools.get_croppings(pitch=pitch_num)

        self._width = max([point[0]
                           for point in config_json['outline']]) - min(
                               [point[0] for point in config_json['outline']])
        self._height = max([point[1]
                            for point in config_json['outline']]) - min(
                                [point[1] for point in config_json['outline']])
        # Getting the zones:
        self._zones = []
        self._zones.append(
            Polygon([(x, self._height - y)
                     for (x, y) in config_json['Zone_0']]))
        self._zones.append(
            Polygon([(x, self._height - y)
                     for (x, y) in config_json['Zone_1']]))
        self._zones.append(
            Polygon([(x, self._height - y)
                     for (x, y) in config_json['Zone_2']]))
        self._zones.append(
            Polygon([(x, self._height - y)
                     for (x, y) in config_json['Zone_3']]))
Esempio n. 21
0
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
Esempio n. 22
0
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)
Esempio n. 23
0
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)
Esempio n. 24
0
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])
Esempio n. 25
0
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 = list(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 ('red', 'green', 'blue', 'yellow'))
    fill_opacity = __RingBuffer(fill_opacity or (1.0,))
    stroke_color = __RingBuffer(stroke_color or ('black',))
    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:%s;fill-opacity:%s;fill-rule:evenodd;stroke:%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
Esempio n. 26
0
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 = tuple(xs.items())[0]
            countx *= 3
        else:
            spx, countx = _find_split(xs)
        if len(ys) < 2:
            spy, county = tuple(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))
Esempio n. 27
0
    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 = list(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()
Esempio n. 28
0
def writeSVG(ofile,
             polylist,
             width=None,
             height=None,
             fill_color=None,
             fill_opacity=None,
             stroke_color=None,
             stroke_width=None,
             labels=None,
             labels_coords=None,
             labels_centered=False):
    """
    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.
    Optional labels can be placed at the polygons.

    :Arguments:
        - ofile: see above
        - polylist: sequence of Polygons
        - optional width: float
        - optional height: height
        - optional fill_color: sequence of colors (3-tuples of integers [0,255]: RGB)
        - optional fill_opacity: sequence of colors
        - optional stroke_color: sequence of colors
        - optional stroke_width: sequence of floats
        - optional labels: sequence of strings (with same length as polylist)
        - optional labels_coords: sequence of x,y coordinates
        - optional labels_centered: if true, then the x,y coordinates specify the middle of the text's bounding box
    :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 = list(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)
    str_dy = ''
    str_anchor = ''
    if labels:
        nlabels = len(labels)
        if nlabels == 0:
            labels = None
        else:
            if nlabels != npoly:
                raise Error(
                    "The number of labels must either be zero, or equal to the number of polygons"
                )
            if labels_coords and len(labels_coords) != nlabels:
                raise Error(
                    "The number of label coordinates must be equal to the number of labels"
                )
            if labels_centered:
                str_dy = 'dy="0.3em" '
                str_anchor = 'text-anchor:middle;text-align:center'
    default_colors = ((27, 158, 119), (217, 95, 2), (117, 112, 179),
                      (231, 41, 138), (102, 166, 30), (230, 171, 2),
                      (166, 118, 29), (102, 102, 102))
    fill_color = __RingBuffer(fill_color or default_colors)
    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('"/>')
        if labels:
            if labels_coords:
                coord_x = width * (labels_coords[i][0] - minx) / xdim
                coord_y = height * (labels_coords[i][1] - miny) / ydim
            else:
                center = p.center()
                coord_x = center[0]
                coord_y = center[1]
            s.append(
                '<g>'
            )  # Group polygon and label, to allow user to find out which label belongs to which group
            s.append(''.join(subl))
            label_svg = '<text x="{0}" y="{1}" {2}style="font-size:10px;fill:black;font-family:Sans;{3}">{4}</text>'.format(
                coord_x, coord_y, str_dy, str_anchor, labels[i])
            s.append(label_svg)
            s.append('</g>')
        else:
            s.append(''.join(subl))
    s.append('</svg>')
    f.write('\n'.join(s))
    if cl: f.close()
    return f