Skip to content
Snippets Groups Projects
DelaunayVoronoi.py 35.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • # -*- coding: utf-8 -*-
    
    # Voronoi diagram calculator/ Delaunay triangulator
    #
    # - Voronoi Diagram Sweepline algorithm and C code by Steven Fortune,
    #   1987, http://ect.bell-labs.com/who/sjf/
    # - Python translation to file voronoi.py by Bill Simons, 2005, http://www.oxfish.com/
    # - Additional changes for QGIS by Carson Farmer added November 2010
    # - 2012 Ported to Python 3 and additional clip functions by domlysz at gmail.com
    #
    # Calculate Delaunay triangulation or the Voronoi polygons for a set of
    # 2D input points.
    #
    # Derived from code bearing the following notice:
    #
    #  The author of this software is Steven Fortune.  Copyright (c) 1994 by AT&T
    #  Bell Laboratories.
    #  Permission to use, copy, modify, and distribute this software for any
    #  purpose without fee is hereby granted, provided that this entire notice
    #  is included in all copies of any software which is or includes a copy
    #  or modification of this software and in all copies of the supporting
    #  documentation for such software.
    #  THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
    #  WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
    #  REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
    #  OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
    #
    # Comments were incorporated from Shane O'Sullivan's translation of the
    # original code into C++ (http://mapviewer.skynet.ie/voronoi.html)
    #
    # Steve Fortune's homepage: http://netlib.bell-labs.com/cm/cs/who/sjf/index.html
    #
    # For programmatic use, two functions are available:
    #
    #   computeVoronoiDiagram(points, xBuff, yBuff, polygonsOutput=False, formatOutput=False):
    #   Takes :
    #       - a list of point objects (which must have x and y fields).
    #       - x and y buffer values which are the expansion percentages of the
    #         bounding box rectangle including all input points.
    #       Returns :
    #       - With default options :
    #         A list of 2-tuples, representing the two points of each Voronoi diagram edge.
    #         Each point contains 2-tuples which are the x,y coordinates of point.
    #         if formatOutput is True, returns :
    #               - a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
    #               - and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram.
    #                 v1 and v2 are the indices of the vertices at the end of the edge.
    #       - If polygonsOutput option is True, returns :
    #         A dictionary of polygons, keys are the indices of the input points,
    #         values contains n-tuples representing the n points of each Voronoi diagram polygon.
    #         Each point contains 2-tuples which are the x,y coordinates of point.
    #         if formatOutput is True, returns :
    #               - A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
    #               - and a dictionary of input points indices. Values contains n-tuples representing
    #                 the n points of each Voronoi diagram polygon.
    #                 Each tuple contains the vertex indices of the polygon vertices.
    #
    #   computeDelaunayTriangulation(points):
    #       Takes a list of point objects (which must have x and y fields).
    #       Returns a list of 3-tuples: the indices of the points that form a Delaunay triangle.
    
    import bpy
    import math
    
    TOLERANCE = 1e-9
    BIG_FLOAT = 1e38
    
    
    class Context(object):
    
        def __init__(self):
            self.doPrint = 0
            self.debug = 0
    
            # tuple (xmin, xmax, ymin, ymax)
            self.extent = ()
            self.triangulate = False
            # list of vertex 2-tuples: (x,y)
            self.vertices = []
            # equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c
            self.lines = []
    
            # edge 3-tuple: (line index, vertex 1 index, vertex 2 index)
            # if either vertex index is -1, the edge extends to infinity
            self.edges = []
            # 3-tuple of vertex indices
            self.triangles = []
            # a dict of site:[edges] pairs
            self.polygons = {}
    
    
    # Clip functions #
        def getClipEdges(self):
            xmin, xmax, ymin, ymax = self.extent
            clipEdges = []
            for edge in self.edges:
                equation = self.lines[edge[0]]       # line equation
                if edge[1] != -1 and edge[2] != -1:  # finite line
                    x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
                    x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
                    pt1, pt2 = (x1, y1), (x2, y2)
                    inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2)
                    if inExtentP1 and inExtentP2:
                        clipEdges.append((pt1, pt2))
                    elif inExtentP1 and not inExtentP2:
                        pt2 = self.clipLine(x1, y1, equation, leftDir=False)
                        clipEdges.append((pt1, pt2))
                    elif not inExtentP1 and inExtentP2:
                        pt1 = self.clipLine(x2, y2, equation, leftDir=True)
                        clipEdges.append((pt1, pt2))
                else:  # infinite line
                    if edge[1] != -1:
                        x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
                        leftDir = False
                    else:
                        x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
                        leftDir = True
                    if self.inExtent(x1, y1):
                        pt1 = (x1, y1)
                        pt2 = self.clipLine(x1, y1, equation, leftDir)
                        clipEdges.append((pt1, pt2))
            return clipEdges
    
        def getClipPolygons(self, closePoly):
            xmin, xmax, ymin, ymax = self.extent
            poly = {}
            for inPtsIdx, edges in self.polygons.items():
                clipEdges = []
                for edge in edges:
                    equation = self.lines[edge[0]]       # line equation
                    if edge[1] != -1 and edge[2] != -1:  # finite line
                        x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
                        x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
                        pt1, pt2 = (x1, y1), (x2, y2)
                        inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2)
                        if inExtentP1 and inExtentP2:
                            clipEdges.append((pt1, pt2))
                        elif inExtentP1 and not inExtentP2:
                            pt2 = self.clipLine(x1, y1, equation, leftDir=False)
                            clipEdges.append((pt1, pt2))
                        elif not inExtentP1 and inExtentP2:
                            pt1 = self.clipLine(x2, y2, equation, leftDir=True)
                            clipEdges.append((pt1, pt2))
                    else:  # infinite line
                        if edge[1] != -1:
                            x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
                            leftDir = False
                        else:
                            x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
                            leftDir = True
                        if self.inExtent(x1, y1):
                            pt1 = (x1, y1)
                            pt2 = self.clipLine(x1, y1, equation, leftDir)
                            clipEdges.append((pt1, pt2))
                # create polygon definition from edges and check if polygon is completely closed
                polyPts, complete = self.orderPts(clipEdges)
                if not complete:
                    startPt = polyPts[0]
                    endPt = polyPts[-1]
                    # if start & end points are collinear then they are along an extent border
                    if startPt[0] == endPt[0] or startPt[1] == endPt[1]:
                        polyPts.append(polyPts[0])  # simple close
                    else:  # close at extent corner
                        # upper left
                        if (startPt[0] == xmin and endPt[1] == ymax) or (endPt[0] == xmin and startPt[1] == ymax):
                            polyPts.append((xmin, ymax))  # corner point
                            polyPts.append(polyPts[0])    # close polygon
                        # upper right
                        if (startPt[0] == xmax and endPt[1] == ymax) or (endPt[0] == xmax and startPt[1] == ymax):
                            polyPts.append((xmax, ymax))
                            polyPts.append(polyPts[0])
                        # bottom right
                        if (startPt[0] == xmax and endPt[1] == ymin) or (endPt[0] == xmax and startPt[1] == ymin):
                            polyPts.append((xmax, ymin))
                            polyPts.append(polyPts[0])
                        # bottom left
                        if (startPt[0] == xmin and endPt[1] == ymin) or (endPt[0] == xmin and startPt[1] == ymin):
                            polyPts.append((xmin, ymin))
                            polyPts.append(polyPts[0])
                if not closePoly:  # unclose polygon
                    polyPts = polyPts[:-1]
                poly[inPtsIdx] = polyPts
            return poly
    
        def clipLine(self, x1, y1, equation, leftDir):
            xmin, xmax, ymin, ymax = self.extent
            a, b, c = equation
            if b == 0:       # vertical line
                if leftDir:  # left is bottom of vertical line
                    return (x1, ymax)
                else:
                    return (x1, ymin)
            elif a == 0:     # horizontal line
                if leftDir:
                    return (xmin, y1)
                else:
                    return (xmax, y1)
            else:
                y2_at_xmin = (c - a * xmin) / b
                y2_at_xmax = (c - a * xmax) / b
                x2_at_ymin = (c - b * ymin) / a
                x2_at_ymax = (c - b * ymax) / a
                intersectPts = []
                if ymin <= y2_at_xmin <= ymax:  # valid intersect point
                    intersectPts.append((xmin, y2_at_xmin))
                if ymin <= y2_at_xmax <= ymax:
                    intersectPts.append((xmax, y2_at_xmax))
                if xmin <= x2_at_ymin <= xmax:
                    intersectPts.append((x2_at_ymin, ymin))
                if xmin <= x2_at_ymax <= xmax:
                    intersectPts.append((x2_at_ymax, ymax))
                # delete duplicate (happens if intersect point is at extent corner)
                intersectPts = set(intersectPts)
                # choose target intersect point
                if leftDir:
                    pt = min(intersectPts)  # smaller x value
                else:
                    pt = max(intersectPts)
                return pt
    
        def inExtent(self, x, y):
            xmin, xmax, ymin, ymax = self.extent
            return x >= xmin and x <= xmax and y >= ymin and y <= ymax
    
        def orderPts(self, edges):
            poly = []  # returned polygon points list [pt1, pt2, pt3, pt4 ....]
            pts = []
            # get points list
            for edge in edges:
                pts.extend([pt for pt in edge])
            # try to get start & end point
            try:
                startPt, endPt = [pt for pt in pts if pts.count(pt) < 2]  # start and end point aren't duplicate
            except:  # all points are duplicate --> polygon is complete --> append some or other edge points
                complete = True
                firstIdx = 0
                poly.append(edges[0][0])
                poly.append(edges[0][1])
            else:  # incomplete --> append the first edge points
                complete = False
                # search first edge
                for i, edge in enumerate(edges):
                    if startPt in edge:  # find
                        firstIdx = i
                        break
                poly.append(edges[firstIdx][0])
                poly.append(edges[firstIdx][1])
                if poly[0] != startPt:
                    poly.reverse()
            # append next points in list
            del edges[firstIdx]
            while edges:  # all points will be treated when edges list will be empty
                currentPt = poly[-1]  # last item
                for i, edge in enumerate(edges):
                    if currentPt == edge[0]:
                        poly.append(edge[1])
                        break
                    elif currentPt == edge[1]:
                        poly.append(edge[0])
                        break
                del edges[i]
            return poly, complete
    
        def setClipBuffer(self, xpourcent, ypourcent):
            xmin, xmax, ymin, ymax = self.extent
            witdh = xmax - xmin
            height = ymax - ymin
            xmin = xmin - witdh * xpourcent / 100
            xmax = xmax + witdh * xpourcent / 100
            ymin = ymin - height * ypourcent / 100
            ymax = ymax + height * ypourcent / 100
            self.extent = xmin, xmax, ymin, ymax
    
        # End clip functions #
    
        def outSite(self, s):
            if(self.debug):
                print("site (%d) at %f %f" % (s.sitenum, s.x, s.y))
            elif(self.triangulate):
                pass
            elif(self.doPrint):
                print("s %f %f" % (s.x, s.y))
    
        def outVertex(self, s):
            self.vertices.append((s.x, s.y))
            if(self.debug):
                print("vertex(%d) at %f %f" % (s.sitenum, s.x, s.y))
            elif(self.triangulate):
                pass
            elif(self.doPrint):
                print("v %f %f" % (s.x, s.y))
    
        def outTriple(self, s1, s2, s3):
            self.triangles.append((s1.sitenum, s2.sitenum, s3.sitenum))
    
                print("circle through left=%d right=%d bottom=%d" % (s1.sitenum, s2.sitenum, s3.sitenum))
    
            elif (self.triangulate and self.doPrint):
    
                print("%d %d %d" % (s1.sitenum, s2.sitenum, s3.sitenum))
    
        def outBisector(self, edge):
            self.lines.append((edge.a, edge.b, edge.c))
    
                print("line(%d) %gx+%gy=%g, bisecting %d %d" % (edge.edgenum, edge.a, edge.b,
                                                                edge.c, edge.reg[0].sitenum,
                                                                edge.reg[1].sitenum)
                    )
            elif(self.doPrint):
                print("l %f %f %f" % (edge.a, edge.b, edge.c))
    
        def outEdge(self, edge):
            sitenumL = -1
            if edge.ep[Edge.LE] is not None:
                sitenumL = edge.ep[Edge.LE].sitenum
            sitenumR = -1
            if edge.ep[Edge.RE] is not None:
                sitenumR = edge.ep[Edge.RE].sitenum
    
            # polygons dict add by CF
            if edge.reg[0].sitenum not in self.polygons:
                self.polygons[edge.reg[0].sitenum] = []
            if edge.reg[1].sitenum not in self.polygons:
                self.polygons[edge.reg[1].sitenum] = []
            self.polygons[edge.reg[0].sitenum].append((edge.edgenum, sitenumL, sitenumR))
            self.polygons[edge.reg[1].sitenum].append((edge.edgenum, sitenumL, sitenumR))
    
            self.edges.append((edge.edgenum, sitenumL, sitenumR))
    
    
            if (not self.triangulate):
                if (self.doPrint):
    
    331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998
                    print("e %d" % edge.edgenum)
                    print(" %d " % sitenumL)
                    print("%d" % sitenumR)
    
    
    def voronoi(siteList, context):
        context.extent = siteList.extent
        edgeList = EdgeList(siteList.xmin, siteList.xmax, len(siteList))
        priorityQ = PriorityQueue(siteList.ymin, siteList.ymax, len(siteList))
        siteIter = siteList.iterator()
    
        bottomsite = siteIter.next()
        context.outSite(bottomsite)
        newsite = siteIter.next()
        minpt = Site(-BIG_FLOAT, -BIG_FLOAT)
        while True:
            if not priorityQ.isEmpty():
                minpt = priorityQ.getMinPt()
    
            if (newsite and (priorityQ.isEmpty() or newsite < minpt)):
                # newsite is smallest -  this is a site event
                context.outSite(newsite)
    
                # get first Halfedge to the LEFT and RIGHT of the new site
                lbnd = edgeList.leftbnd(newsite)
                rbnd = lbnd.right
    
                # if this halfedge has no edge, bot = bottom site (whatever that is)
                # create a new edge that bisects
                bot = lbnd.rightreg(bottomsite)
                edge = Edge.bisect(bot, newsite)
                context.outBisector(edge)
    
                # create a new Halfedge, setting its pm field to 0 and insert
                # this new bisector edge between the left and right vectors in
                # a linked list
                bisector = Halfedge(edge, Edge.LE)
                edgeList.insert(lbnd, bisector)
    
                # if the new bisector intersects with the left edge, remove
                # the left edge's vertex, and put in the new one
                p = lbnd.intersect(bisector)
                if p is not None:
                    priorityQ.delete(lbnd)
                    priorityQ.insert(lbnd, p, newsite.distance(p))
    
                # create a new Halfedge, setting its pm field to 1
                # insert the new Halfedge to the right of the original bisector
                lbnd = bisector
                bisector = Halfedge(edge, Edge.RE)
                edgeList.insert(lbnd, bisector)
    
                # if this new bisector intersects with the right Halfedge
                p = bisector.intersect(rbnd)
                if p is not None:
                    # push the Halfedge into the ordered linked list of vertices
                    priorityQ.insert(bisector, p, newsite.distance(p))
    
                newsite = siteIter.next()
    
            elif not priorityQ.isEmpty():
                # intersection is smallest - this is a vector (circle) event
                # pop the Halfedge with the lowest vector off the ordered list of
                # vectors.  Get the Halfedge to the left and right of the above HE
                # and also the Halfedge to the right of the right HE
                lbnd = priorityQ.popMinHalfedge()
                llbnd = lbnd.left
                rbnd = lbnd.right
                rrbnd = rbnd.right
    
                # get the Site to the left of the left HE and to the right of
                # the right HE which it bisects
                bot = lbnd.leftreg(bottomsite)
                top = rbnd.rightreg(bottomsite)
    
                # output the triple of sites, stating that a circle goes through them
                mid = lbnd.rightreg(bottomsite)
                context.outTriple(bot, top, mid)
    
                # get the vertex that caused this event and set the vertex number
                # couldn't do this earlier since we didn't know when it would be processed
                v = lbnd.vertex
                siteList.setSiteNumber(v)
                context.outVertex(v)
    
                # set the endpoint of the left and right Halfedge to be this vector
                if lbnd.edge.setEndpoint(lbnd.pm, v):
                    context.outEdge(lbnd.edge)
    
                if rbnd.edge.setEndpoint(rbnd.pm, v):
                    context.outEdge(rbnd.edge)
    
                # delete the lowest HE, remove all vertex events to do with the
                # right HE and delete the right HE
                edgeList.delete(lbnd)
                priorityQ.delete(rbnd)
                edgeList.delete(rbnd)
    
                # if the site to the left of the event is higher than the Site
                # to the right of it, then swap them and set 'pm' to RIGHT
                pm = Edge.LE
                if bot.y > top.y:
                    bot, top = top, bot
                    pm = Edge.RE
    
                # Create an Edge (or line) that is between the two Sites.  This
                # creates the formula of the line, and assigns a line number to it
                edge = Edge.bisect(bot, top)
                context.outBisector(edge)
    
                # create a HE from the edge
                bisector = Halfedge(edge, pm)
    
                # insert the new bisector to the right of the left HE
                # set one endpoint to the new edge to be the vector point 'v'
                # If the site to the left of this bisector is higher than the right
                # Site, then this endpoint is put in position 0; otherwise in pos 1
                edgeList.insert(llbnd, bisector)
                if edge.setEndpoint(Edge.RE - pm, v):
                    context.outEdge(edge)
    
                # if left HE and the new bisector don't intersect, then delete
                # the left HE, and reinsert it
                p = llbnd.intersect(bisector)
                if p is not None:
                    priorityQ.delete(llbnd)
                    priorityQ.insert(llbnd, p, bot.distance(p))
    
                # if right HE and the new bisector don't intersect, then reinsert it
                p = bisector.intersect(rrbnd)
                if p is not None:
                    priorityQ.insert(bisector, p, bot.distance(p))
            else:
                break
    
        he = edgeList.leftend.right
        while he is not edgeList.rightend:
            context.outEdge(he.edge)
            he = he.right
        Edge.EDGE_NUM = 0  # CF
    
    
    def isEqual(a, b, relativeError=TOLERANCE):
        # is nearly equal to within the allowed relative error
        norm = max(abs(a), abs(b))
        return (norm < relativeError) or (abs(a - b) < (relativeError * norm))
    
    
    class Site(object):
    
        def __init__(self, x=0.0, y=0.0, sitenum=0):
            self.x = x
            self.y = y
            self.sitenum = sitenum
    
        def dump(self):
            print("Site #%d (%g, %g)" % (self.sitenum, self.x, self.y))
    
        def __lt__(self, other):
            if self.y < other.y:
                return True
            elif self.y > other.y:
                return False
            elif self.x < other.x:
                return True
            elif self.x > other.x:
                return False
            else:
                return False
    
        def __eq__(self, other):
            if self.y == other.y and self.x == other.x:
                return True
    
        def distance(self, other):
            dx = self.x - other.x
            dy = self.y - other.y
            return math.sqrt(dx * dx + dy * dy)
    
    
    class Edge(object):
        LE = 0  # left end indice --> edge.ep[Edge.LE]
        RE = 1  # right end indice
        EDGE_NUM = 0
        DELETED = {}  # marker value
    
        def __init__(self):
            self.a = 0.0  # equation of the line a*x+b*y = c
            self.b = 0.0
            self.c = 0.0
            self.ep = [None, None]  # end point (2 tuples of site)
            self.reg = [None, None]
            self.edgenum = 0
    
        def dump(self):
            print("(#%d a=%g, b=%g, c=%g)" % (self.edgenum, self.a, self.b, self.c))
            print("ep", self.ep)
            print("reg", self.reg)
    
        def setEndpoint(self, lrFlag, site):
            self.ep[lrFlag] = site
            if self.ep[Edge.RE - lrFlag] is None:
                return False
            return True
    
        @staticmethod
        def bisect(s1, s2):
            newedge = Edge()
            newedge.reg[0] = s1  # store the sites that this edge is bisecting
            newedge.reg[1] = s2
    
            # to begin with, there are no endpoints on the bisector - it goes to infinity
            # ep[0] and ep[1] are None
    
            # get the difference in x dist between the sites
            dx = float(s2.x - s1.x)
            dy = float(s2.y - s1.y)
            adx = abs(dx)  # make sure that the difference in positive
            ady = abs(dy)
    
            # get the slope of the line
            newedge.c = float(s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * 0.5)
            if adx > ady:
                # set formula of line, with x fixed to 1
                newedge.a = 1.0
                newedge.b = dy / dx
                newedge.c /= dx
            else:
                # set formula of line, with y fixed to 1
                newedge.b = 1.0
                newedge.a = dx / dy
                newedge.c /= dy
    
            newedge.edgenum = Edge.EDGE_NUM
            Edge.EDGE_NUM += 1
            return newedge
    
    
    class Halfedge(object):
    
        def __init__(self, edge=None, pm=Edge.LE):
            self.left = None    # left Halfedge in the edge list
            self.right = None   # right Halfedge in the edge list
            self.qnext = None   # priority queue linked list pointer
            self.edge = edge    # edge list Edge
            self.pm = pm
            self.vertex = None  # Site()
            self.ystar = BIG_FLOAT
    
        def dump(self):
            print("Halfedge--------------------------")
            print("left: ", self.left)
            print("right: ", self.right)
            print("edge: ", self.edge)
            print("pm: ", self.pm)
            print("vertex: "),
            if self.vertex:
                self.vertex.dump()
            else:
                print("None")
            print("ystar: ", self.ystar)
    
        def __lt__(self, other):
            if self.ystar < other.ystar:
                return True
            elif self.ystar > other.ystar:
                return False
            elif self.vertex.x < other.vertex.x:
                return True
            elif self.vertex.x > other.vertex.x:
                return False
            else:
                return False
    
        def __eq__(self, other):
            if self.ystar == other.ystar and self.vertex.x == other.vertex.x:
                return True
    
        def leftreg(self, default):
            if not self.edge:
                return default
            elif self.pm == Edge.LE:
                return self.edge.reg[Edge.LE]
            else:
                return self.edge.reg[Edge.RE]
    
        def rightreg(self, default):
            if not self.edge:
                return default
            elif self.pm == Edge.LE:
                return self.edge.reg[Edge.RE]
            else:
                return self.edge.reg[Edge.LE]
    
        # returns True if p is to right of halfedge self
        def isPointRightOf(self, pt):
            e = self.edge
            topsite = e.reg[1]
            right_of_site = pt.x > topsite.x
    
            if(right_of_site and self.pm == Edge.LE):
                return True
    
            if(not right_of_site and self.pm == Edge.RE):
                return False
    
            if(e.a == 1.0):
                dyp = pt.y - topsite.y
                dxp = pt.x - topsite.x
                fast = 0
                if ((not right_of_site and e.b < 0.0) or (right_of_site and e.b >= 0.0)):
                    above = dyp >= e.b * dxp
                    fast = above
                else:
                    above = pt.x + pt.y * e.b > e.c
                    if(e.b < 0.0):
                        above = not above
                    if (not above):
                        fast = 1
                if (not fast):
                    dxs = topsite.x - (e.reg[0]).x
                    above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b)
                    if(e.b < 0.0):
                        above = not above
            else:  # e.b == 1.0
                yl = e.c - e.a * pt.x
                t1 = pt.y - yl
                t2 = pt.x - topsite.x
                t3 = yl - topsite.y
                above = t1 * t1 > t2 * t2 + t3 * t3
    
            if(self.pm == Edge.LE):
                return above
            else:
                return not above
    
        # create a new site where the Halfedges el1 and el2 intersect
        def intersect(self, other):
            e1 = self.edge
            e2 = other.edge
            if (e1 is None) or (e2 is None):
                return None
    
            # if the two edges bisect the same parent return None
            if e1.reg[1] is e2.reg[1]:
                return None
    
            d = e1.a * e2.b - e1.b * e2.a
            if isEqual(d, 0.0):
                return None
    
            xint = (e1.c * e2.b - e2.c * e1.b) / d
            yint = (e2.c * e1.a - e1.c * e2.a) / d
            if e1.reg[1] < e2.reg[1]:
                he = self
                e = e1
            else:
                he = other
                e = e2
    
            rightOfSite = xint >= e.reg[1].x
            if((rightOfSite and he.pm == Edge.LE) or
                    (not rightOfSite and he.pm == Edge.RE)):
                return None
    
            # create a new site at the point of intersection - this is a new
            # vector event waiting to happen
            return Site(xint, yint)
    
    
    class EdgeList(object):
    
        def __init__(self, xmin, xmax, nsites):
            if xmin > xmax:
                xmin, xmax = xmax, xmin
            self.hashsize = int(2 * math.sqrt(nsites + 4))
    
            self.xmin = xmin
            self.deltax = float(xmax - xmin)
            self.hash = [None] * self.hashsize
    
            self.leftend = Halfedge()
            self.rightend = Halfedge()
            self.leftend.right = self.rightend
            self.rightend.left = self.leftend
            self.hash[0] = self.leftend
            self.hash[-1] = self.rightend
    
        def insert(self, left, he):
            he.left = left
            he.right = left.right
            left.right.left = he
            left.right = he
    
        def delete(self, he):
            he.left.right = he.right
            he.right.left = he.left
            he.edge = Edge.DELETED
    
        # Get entry from hash table, pruning any deleted nodes
        def gethash(self, b):
            if(b < 0 or b >= self.hashsize):
                return None
            he = self.hash[b]
            if he is None or he.edge is not Edge.DELETED:
                return he
    
            #  Hash table points to deleted half edge.  Patch as necessary.
            self.hash[b] = None
            return None
    
        def leftbnd(self, pt):
            # Use hash table to get close to desired halfedge
            bucket = int(((pt.x - self.xmin) / self.deltax * self.hashsize))
    
            if(bucket < 0):
                bucket = 0
    
            if(bucket >= self.hashsize):
                bucket = self.hashsize - 1
    
            he = self.gethash(bucket)
            if(he is None):
                i = 1
                while True:
                    he = self.gethash(bucket - i)
                    if (he is not None):
                        break
                    he = self.gethash(bucket + i)
                    if (he is not None):
                        break
                    i += 1
    
            # Now search linear list of halfedges for the corect one
            if (he is self.leftend) or (he is not self.rightend and he.isPointRightOf(pt)):
                he = he.right
                while he is not self.rightend and he.isPointRightOf(pt):
                    he = he.right
                he = he.left
            else:
                he = he.left
                while (he is not self.leftend and not he.isPointRightOf(pt)):
                    he = he.left
    
            # Update hash table and reference counts
            if(bucket > 0 and bucket < self.hashsize - 1):
                self.hash[bucket] = he
            return he
    
    
    class PriorityQueue(object):
    
        def __init__(self, ymin, ymax, nsites):
            self.ymin = ymin
            self.deltay = ymax - ymin
            self.hashsize = int(4 * math.sqrt(nsites))
            self.count = 0
            self.minidx = 0
            self.hash = []
            for i in range(self.hashsize):
                self.hash.append(Halfedge())
    
        def __len__(self):
            return self.count
    
        def isEmpty(self):
            return self.count == 0
    
        def insert(self, he, site, offset):
            he.vertex = site
            he.ystar = site.y + offset
            last = self.hash[self.getBucket(he)]
            next = last.qnext
            while((next is not None) and he > next):
                last = next
                next = last.qnext
            he.qnext = last.qnext
            last.qnext = he
            self.count += 1
    
        def delete(self, he):
            if (he.vertex is not None):
                last = self.hash[self.getBucket(he)]
                while last.qnext is not he:
                    last = last.qnext
                last.qnext = he.qnext
                self.count -= 1
                he.vertex = None
    
        def getBucket(self, he):
            bucket = int(((he.ystar - self.ymin) / self.deltay) * self.hashsize)
            if bucket < 0:
                bucket = 0
            if bucket >= self.hashsize:
                bucket = self.hashsize - 1
            if bucket < self.minidx:
                self.minidx = bucket
            return bucket
    
        def getMinPt(self):
            while(self.hash[self.minidx].qnext is None):
                self.minidx += 1
            he = self.hash[self.minidx].qnext
            x = he.vertex.x
            y = he.ystar
            return Site(x, y)
    
        def popMinHalfedge(self):
            curr = self.hash[self.minidx].qnext
            self.hash[self.minidx].qnext = curr.qnext
            self.count -= 1
            return curr
    
    
    class SiteList(object):
    
        def __init__(self, pointList):
            self.__sites = []
            self.__sitenum = 0
    
            self.__xmin = min([pt.x for pt in pointList])
            self.__ymin = min([pt.y for pt in pointList])
            self.__xmax = max([pt.x for pt in pointList])
            self.__ymax = max([pt.y for pt in pointList])
            self.__extent = (self.__xmin, self.__xmax, self.__ymin, self.__ymax)
    
            for i, pt in enumerate(pointList):
                self.__sites.append(Site(pt.x, pt.y, i))
            self.__sites.sort()
    
        def setSiteNumber(self, site):
            site.sitenum = self.__sitenum
            self.__sitenum += 1
    
        class Iterator(object):
    
            def __init__(this, lst):
                this.generator = (s for s in lst)
    
            def __iter__(this):
                return this
    
            def next(this):
                try:
                    # Note: Blender is Python 3.x so no need for 2.x checks
                    return this.generator.__next__()
                except StopIteration:
                    return None
    
        def iterator(self):
            return SiteList.Iterator(self.__sites)
    
        def __iter__(self):
            return SiteList.Iterator(self.__sites)
    
        def __len__(self):
            return len(self.__sites)
    
        def _getxmin(self):
            return self.__xmin
    
        def _getymin(self):
            return self.__ymin
    
        def _getxmax(self):
            return self.__xmax
    
        def _getymax(self):
            return self.__ymax
    
        def _getextent(self):
            return self.__extent
    
        xmin = property(_getxmin)
        ymin = property(_getymin)
        xmax = property(_getxmax)
        ymax = property(_getymax)
        extent = property(_getextent)
    
    
    def computeVoronoiDiagram(points, xBuff=0, yBuff=0, polygonsOutput=False,
                              formatOutput=False, closePoly=True):
        """
        Takes :
        - a list of point objects (which must have x and y fields).
        - x and y buffer values which are the expansion percentages of the bounding box
            rectangle including all input points.
        Returns :
        - With default options :
          A list of 2-tuples, representing the two points of each Voronoi diagram edge.
          Each point contains 2-tuples which are the x,y coordinates of point.
          if formatOutput is True, returns :
                        - a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
                        - and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram.
                          v1 and v2 are the indices of the vertices at the end of the edge.
        - If polygonsOutput option is True, returns :
          A dictionary of polygons, keys are the indices of the input points,
          values contains n-tuples representing the n points of each Voronoi diagram polygon.
          Each point contains 2-tuples which are the x,y coordinates of point.
          if formatOutput is True, returns :
                        - A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
                        - and a dictionary of input points indices. Values contains n-tuples representing
                          the n points of each Voronoi diagram polygon.
                          Each tuple contains the vertex indices of the polygon vertices.
        - if closePoly is True then, in the list of points of a polygon, last point will be the same of first point
        """
        siteList = SiteList(points)
        context = Context()
        voronoi(siteList, context)
        context.setClipBuffer(xBuff, yBuff)
        if not polygonsOutput:
            clipEdges = context.getClipEdges()
            if formatOutput:
                vertices, edgesIdx = formatEdgesOutput(clipEdges)
                return vertices, edgesIdx
            else:
                return clipEdges
        else:
            clipPolygons = context.getClipPolygons(closePoly)
            if formatOutput:
                vertices, polyIdx = formatPolygonsOutput(clipPolygons)
                return vertices, polyIdx
            else:
                return clipPolygons
    
    
    def formatEdgesOutput(edges):
        # get list of points
        pts = []
        for edge in edges:
            pts.extend(edge)
        # get unique values
        pts = set(pts)  # unique values (tuples are hashable)
        # get dict {values:index}
        valuesIdxDict = dict(zip(pts, range(len(pts))))
        # get edges index reference
        edgesIdx = []
        for edge in edges:
            edgesIdx.append([valuesIdxDict[pt] for pt in edge])
        return list(pts), edgesIdx
    
    
    def formatPolygonsOutput(polygons):
        # get list of points
        pts = []
        for poly in polygons.values():
            pts.extend(poly)
        # get unique values
        pts = set(pts)  # unique values (tuples are hashable)
        # get dict {values:index}
        valuesIdxDict = dict(zip(pts, range(len(pts))))
        # get polygons index reference
        polygonsIdx = {}
        for inPtsIdx, poly in polygons.items():
            polygonsIdx[inPtsIdx] = [valuesIdxDict[pt] for pt in poly]
        return list(pts), polygonsIdx
    
    
    def computeDelaunayTriangulation(points):
        """ Takes a list of point objects (which must have x and y fields).
                Returns a list of 3-tuples: the indices of the points that form a
                Delaunay triangle.
        """
        siteList = SiteList(points)
        context = Context()
        context.triangulate = True
        voronoi(siteList, context)
        return context.triangles