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 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 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 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
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]
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 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]
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])
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])
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 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])
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 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 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 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 __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']]))
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 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 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 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
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))
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()
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