Пример #1
0
    def _remove(self, site, points, triangles):
        '''
        Update the triangulation by removing the surrounding triangles 
        and then filling this cavity with new Delaunay triangles.
        Challenge: Maintain neighborhood control.
        Ref: Mir Abolfazl Mostafavi, Christopher Gold, and Maciej Dakowicz. 
             2003. Delete and insert operations in Voronoi/Delaunay methods 
             and applications. Comput. Geosci. 29, 4 (May 2003), 523-530. 
             DOI=10.1016/S0098-3004(03)00017-7 
             http://dx.doi.org/10.1016/S0098-3004(03)00017-7
        '''
        # verify compatibility of surrounding points and old triangles
        if len(points) != len(triangles):
            raise Exception("Triangulation has different sizes.")
        # verify is the triangulation is empty (or insufficient?)
        if len(points) < 3:
            return
        # initialize new triangulation controls
        i = -1
        new_triangles = {}
        # while there are points to form more than the last triangle
        while len(points) > 3:
            # checks the possible triangles considering 
            # all three consecutive points (i, i1, i2 in a cycle) 
            # of surrounding points of the site
            i += 1
            npoints = len(points)
            if (i >= npoints):
                raise Exception("Inexists a valid ear? Is it possible?")
            i1 = (i + 1) % npoints
            i2 = (i1 + 1) % npoints
            # verify if points represent a valid triangle to site,
            # like a ear listen to the site:
            # 1: gets triangle orientation (CW or CCW)
            o_ear = Triangle.orientation(points[i], points[i1], points[i2])
            # 2: gets direction of triangle to the site (CW or CCW)
            o_ear_site = Triangle.orientation(points[i], points[i2], site)
            # 3: if points are collinear, try another edge as a reference
            # ??why don't take this edge at first place??
            if o_ear_site == 0:
                o_ear_site = Triangle.orientation(points[i], points[i1], site)
            # 4: the directions is the same?
            if (o_ear * o_ear_site) > 0:
                # if so, this a valid ear (possible triangulation)
                valid_ear = Triangle(points[i], points[i1], points[i2])
                # verify if this ear is a Delaunay Triangulation
                ear_is_delaunay = True
                # 1. for all other surrounding points
                for p in points:
                    # 1.1: is this other point (not in ear)?
                    if not valid_ear.contains(p):
                        # verify if ear won't circumcircle it
                        if valid_ear.circumscribe(p):
                            # if circumcircle, ear is not a Delaunay triangle
                            ear_is_delaunay = False
                            break
                # if it is a Delaunay triangle...
                if ear_is_delaunay:
                    # include to new triangle control
                    new_triangles[valid_ear] = None
                    # include to neighborhood control
                    self.neighborhood[valid_ear] = {}
                    # link to the opposite triangles from the removed vertices
                    self._link_ear(site, valid_ear, triangles[i], 
                                   new_triangles)
                    self._link_ear(site, valid_ear, triangles[i1], 
                                   new_triangles)
                    # change triangle related to vertex by the new one
                    # remove old triangle by switching the diagonal
                    triangles[i] = valid_ear
                    # remove middle point (leave the corners) 
                    del points[i1]
                    del triangles[i1]
                    # restart cycle of surrounding points
                    i = -1
        # if has only three neighbours remaining in the surrounding points, 
        # merged these three points (triangles) into last triangulation
        last_ear = Triangle(points[0], points[1], points[2])
        self.neighborhood[last_ear] = {}
        new_triangles[last_ear] = None
        # last triangle closes the triangulation and 
        # needs update the link with all sides (neighborhood)
        self._link_ear(site, last_ear, triangles[0], new_triangles)
        self._link_ear(site, last_ear, triangles[1], new_triangles)
        self._link_ear(site, last_ear, triangles[2], new_triangles)

        if self.main_site is not None:
            if self.main_site == site:
                self.main_site = None
        self._new_version()
        if self.check_triangulation:
            if not self.valid():
                print "### Removing failure"
Пример #2
0
class Delaunay(object):
    '''
    Delaunay Triangulation
    ref: http://www.cs.cornell.edu/home/chew/Delaunay.html
    '''

    # INT => MAX=+(pow(2,31) - 1)(2147483647) & MIN=-pow(2,31)(-2147483648)
    # inf = 1000000 was empirically limited due to pygame simulator
    # inf = 2^16 was empirically limited due to float point precision

    def __init__(self, inf = 65536, test = False):
        self._initTriangle = Triangle(Point2D(-inf, -inf), 
                                      Point2D(inf, -inf), 
                                      Point2D(0, inf))
        self.neighborhood = {self._initTriangle: {}}
        self.lock = threading.RLock()
        self.main_site = None
        self.fail_site = None
        self.fail_triangle = None
        self.fail_witness = None
        self.version = 0
        self.check_triangulation = test

    def __repr__(self):
        return 'Triangulation with ' + str(len(self.neighborhood)) + \
            ' triangles'

    def __str__(self):
        return self.__repr__() 

    def _new_version(self):
        self.version += 1
        # cicle version
        if self.version > 2147483640:
            self.version = 0

    def updated_since(self, version):
        return (version != self.version)

    def has_error(self):
        return self.fail_site is not None

    def include(self, site):
        '''
        Add a site to triangulation
        '''
        if self.fail_site is not None:
            return False
        with self.lock:
            triangle = self._find_triangle_circumscribe(site)
            return self._add(site, triangle)
    
    def remove_far_sites(self, from_site):
        '''
        Remove all sites that do not affect a "from site" cell
        Returns the number of removed sites
        '''
        if self.fail_site is not None:
            return False
        with self.lock:
            from_triangle = self._find_triangle_by_vertex(from_site)
            if (from_triangle is None):
                return 0
            triangles, points = \
                    self.surrounding_triangles(from_site, from_triangle)
            # search irrlevant sites
            dispensables = []
            for triangle in self.neighborhood.keys():
                if (not triangle in triangles):
                    dispensable = True
                    for vertex in triangle:
                        if not vertex in points:
                            dispensables.append(vertex)
            # remove irrlevant sites
            removed = 0
            for site in dispensables:
                if not self._initTriangle.contains(site):
                    self.remove(site)
                    removed += 1
            return removed

    def include_near(self, site, from_site):
        '''
        Add a site only if it will affect a "from site" cell
        '''
        if self.fail_site is not None:
            return False
        with self.lock:
            triangle = self._find_triangle_by_vertex(from_site)
            if (triangle is None):
                raise Exception("'From site' don't found.")
            triangles, points = \
                    self.surrounding_triangles(from_site, triangle)
            # verify site influence
            for triangle in triangles:
                if triangle.circumscribe(site):
                    return self._add(site, triangle)
            return False

    def remove(self, site):
        '''
        Remove a site from triagulation
        '''
        if self.fail_site is not None:
            return False
        with self.lock:
            triangle = self._find_triangle_by_vertex(site)
            if triangle is not None:
                triangles, points = self.surrounding_triangles(site, triangle)
                self._remove(site, points, triangles)
                return True
            return False

    def _find_triangle_circumscribe(self, site):
        '''
        Search the triangle that circumcircle the point
        '''
        for triangle in self.neighborhood:
            if triangle.circumscribe(site) or triangle.contains(site):
                return triangle
        raise Exception("Site out of the valid area.")
    
    def _find_triangle_by_vertex(self, site):
        '''
        Search the triangle that contains the point as a vertex
        '''
        for triangle in self.neighborhood.keys():
            if triangle.contains(site):
                return triangle
        return None
    
    def _cavity(self, site, triangle):
        '''
        Determine the cavity caused by site.
        '''
        encroached = {}
        toBeChecked = [triangle]
        marked = {triangle: None}
        while (len(toBeChecked) > 0):
            triangle = toBeChecked.pop()
            # Site outside triangle => triangle not in cavity
            if not triangle.circumscribe(site):
                continue
            encroached[triangle] = None
            # Check the neighborhood
            for neighbor in self.neighborhood[triangle]:
                if neighbor not in marked:
                    marked[neighbor] = None
                    toBeChecked.append(neighbor)
        return encroached

    def _add(self, site, triangle):
        '''
        Add a site to triangulation with specific triangle
        '''
        if not triangle.contains(site):
            cavity = self._cavity(site, triangle)
            self._insert(site, cavity)
            return True
        return False

    def _add_triangle(self, t1, t2):
        '''
        Add a triangle to neighborhood control
        '''
        if t1 not in self.neighborhood:
            self.neighborhood[t1] = {t2}
        else:
            self.neighborhood[t1][t2] = None

    def _del_triangle(self, triangle):
        '''
        Remove a triangle from neighborhood control
        '''
        del self.neighborhood[triangle]
        for neighbor in self.neighborhood.values():
            if triangle in neighbor:
                del neighbor[triangle]
        
    def _link_triangles(self, t1, t2):
        '''
        Include mutual neighbor triangles
        '''
        self._add_triangle(t1, t2)
        self._add_triangle(t2, t1)

    def _insert(self, site, cavity):
        '''
        Update the triangulation by removing the cavity triangles and then
        filling the cavity with new triangles.
        '''
        boundary = {}
        triangles = {}

        # Find boundary facets and adjacent triangles
        for triangle in cavity:
            for neighbor in self.neighborhood[triangle]:
                # Adj triangles only
                if neighbor not in cavity:
                    triangles[neighbor] = None
            for point in triangle:
                facet = triangle.opposite_facet(point)
                if facet in boundary:
                    del boundary[facet]
                else:
                    boundary[facet] = None

        # Remove the cavity triangles from the triangulation
        for triangle in cavity:
            self._del_triangle(triangle)

        # Build each new triangle and include it to the triangulation
        new_triangles = {}
        for facet in boundary.keys():
            newTriangle = Triangle(facet[0], facet[1], site)
            self.neighborhood[newTriangle] = {}
            new_triangles[newTriangle] = None
            # Adj triangle + new triangles
            triangles[newTriangle] = None

        # Update the graph links for each new triangle
        for triangle in new_triangles:
            for other in triangles:
                if triangle.is_neighbor(other):
                    self._link_triangles(triangle, other)
                    
        if self.main_site is None:
            self.main_site = site
        self._new_version()
        if self.check_triangulation:
            if not self.valid():
                print "### Including failure"

    def _remove(self, site, points, triangles):
        '''
        Update the triangulation by removing the surrounding triangles 
        and then filling this cavity with new Delaunay triangles.
        Challenge: Maintain neighborhood control.
        Ref: Mir Abolfazl Mostafavi, Christopher Gold, and Maciej Dakowicz. 
             2003. Delete and insert operations in Voronoi/Delaunay methods 
             and applications. Comput. Geosci. 29, 4 (May 2003), 523-530. 
             DOI=10.1016/S0098-3004(03)00017-7 
             http://dx.doi.org/10.1016/S0098-3004(03)00017-7
        '''
        # verify compatibility of surrounding points and old triangles
        if len(points) != len(triangles):
            raise Exception("Triangulation has different sizes.")
        # verify is the triangulation is empty (or insufficient?)
        if len(points) < 3:
            return
        # initialize new triangulation controls
        i = -1
        new_triangles = {}
        # while there are points to form more than the last triangle
        while len(points) > 3:
            # checks the possible triangles considering 
            # all three consecutive points (i, i1, i2 in a cycle) 
            # of surrounding points of the site
            i += 1
            npoints = len(points)
            if (i >= npoints):
                raise Exception("Inexists a valid ear? Is it possible?")
            i1 = (i + 1) % npoints
            i2 = (i1 + 1) % npoints
            # verify if points represent a valid triangle to site,
            # like a ear listen to the site:
            # 1: gets triangle orientation (CW or CCW)
            o_ear = Triangle.orientation(points[i], points[i1], points[i2])
            # 2: gets direction of triangle to the site (CW or CCW)
            o_ear_site = Triangle.orientation(points[i], points[i2], site)
            # 3: if points are collinear, try another edge as a reference
            # ??why don't take this edge at first place??
            if o_ear_site == 0:
                o_ear_site = Triangle.orientation(points[i], points[i1], site)
            # 4: the directions is the same?
            if (o_ear * o_ear_site) > 0:
                # if so, this a valid ear (possible triangulation)
                valid_ear = Triangle(points[i], points[i1], points[i2])
                # verify if this ear is a Delaunay Triangulation
                ear_is_delaunay = True
                # 1. for all other surrounding points
                for p in points:
                    # 1.1: is this other point (not in ear)?
                    if not valid_ear.contains(p):
                        # verify if ear won't circumcircle it
                        if valid_ear.circumscribe(p):
                            # if circumcircle, ear is not a Delaunay triangle
                            ear_is_delaunay = False
                            break
                # if it is a Delaunay triangle...
                if ear_is_delaunay:
                    # include to new triangle control
                    new_triangles[valid_ear] = None
                    # include to neighborhood control
                    self.neighborhood[valid_ear] = {}
                    # link to the opposite triangles from the removed vertices
                    self._link_ear(site, valid_ear, triangles[i], 
                                   new_triangles)
                    self._link_ear(site, valid_ear, triangles[i1], 
                                   new_triangles)
                    # change triangle related to vertex by the new one
                    # remove old triangle by switching the diagonal
                    triangles[i] = valid_ear
                    # remove middle point (leave the corners) 
                    del points[i1]
                    del triangles[i1]
                    # restart cycle of surrounding points
                    i = -1
        # if has only three neighbours remaining in the surrounding points, 
        # merged these three points (triangles) into last triangulation
        last_ear = Triangle(points[0], points[1], points[2])
        self.neighborhood[last_ear] = {}
        new_triangles[last_ear] = None
        # last triangle closes the triangulation and 
        # needs update the link with all sides (neighborhood)
        self._link_ear(site, last_ear, triangles[0], new_triangles)
        self._link_ear(site, last_ear, triangles[1], new_triangles)
        self._link_ear(site, last_ear, triangles[2], new_triangles)

        if self.main_site is not None:
            if self.main_site == site:
                self.main_site = None
        self._new_version()
        if self.check_triangulation:
            if not self.valid():
                print "### Removing failure"

    def _link_ear(self, site, ear, triangle, new_triangles):
        # check if the triangle has neighbor that is opposite from the site
        neighbor = self.neighbor_opposite(site, triangle)
        # if not, it's a external triangulation (from initial triangle)
        # or it's a new triangulation...
        if neighbor is None:
            # if the triangle related to the vertex is new
            if triangle in new_triangles:
                # update neighborhood with the new triangle
                self._link_triangles(ear, triangle)
            else:
                # otherwise, old triangle doesn't had a neighbor: 
                # ignore its neighborhood and delete it
                self._del_triangle(triangle)
        else:
            # update neighborhood of new triangle (ear)
            self._link_triangles(ear, neighbor)
            # delete old voronoi cell triangle
            self._del_triangle(triangle)

    def neighbor_opposite(self, site, triangle):
        '''
        Report neighbor opposite the given vertex of triangle.
        '''
        if site not in triangle:
            return None
        with self.lock:
            for neighbor in self.neighborhood[triangle]:
                if not neighbor.contains(site):
                    return neighbor
            return None

    def surrounding_triangles(self, site, triangle):
        '''
        Report triangles and points surrounding site in order (cw or ccw).
        '''
        if site not in triangle:
            raise Exception("Site not in triangle.")
        with self.lock:
            points = []
            triangles = []
            start = triangle
            # cw or cww (pay attention)
            guide = triangle.next_vertex_except({site})
            while triangle is not None:
                triangles.append(triangle)
                points.append(guide)
                previous = triangle
                triangle = self.neighbor_opposite(guide, triangle)
                guide = previous.next_vertex_except({site, guide})
                if (triangle == start):
                    break
            return triangles, points

    def voronoi_cells(self):
        '''
        Report polygons of each voronoi cell
        '''
        with self.lock:
            cells = {}
            ignore = {} #x:None for x in self._initTriangle}
            for triangle in self.neighborhood.keys():
                for site in triangle:
                    if site in ignore:
                        continue
                    ignore[site] = None
                    triangles, points = self.surrounding_triangles(site, 
                                                                   triangle)
                    cell = []
                    for tri in triangles:
                        cell.append(tri.circumcenter())
                    cells[site] = cell 
            return cells

    def voronoi_cell_trinagulation(self, site):
        '''
        Test purpose only
        '''
        with self.lock:
            triangle = self._find_triangle_by_vertex(site)
            triangles, points = self.surrounding_triangles(site, triangle)
            return triangles, triangle

    def valid(self):
        for triangle in self.neighborhood.keys():
            for other in self.neighborhood.keys():
                if (other != triangle):
                    for vertex in other:
                        if not triangle.contains(vertex):
                            if triangle.circumscribe(vertex):
                                self.fail_site = vertex
                                self.fail_triangle = triangle
                                self.fail_witness = other
                                print "Triangle     : " + str(triangle)
                                print "Other        : " + str(other)
                                print "Other vertex : " + str(vertex)
                                triangle.debug_distance(vertex)
                                return False
        return True