source: inundation/pyvolution/general_mesh.py @ 2694

Last change on this file since 2694 was 2694, checked in by duncan, 18 years ago

comments

File size: 10.1 KB
Line 
1
2from coordinate_transforms.geo_reference import Geo_reference
3
4class General_mesh:
5    """Collection of triangular elements (purely geometric)
6
7    A triangular element is defined in terms of three vertex ids,
8    ordered counter clock-wise,
9    each corresponding to a given coordinate set.
10    Vertices from different elements can point to the same
11    coordinate set.
12
13    Coordinate sets are implemented as an N x 2 Numeric array containing
14    x and y coordinates.
15
16
17    To instantiate:
18       Mesh(coordinates, triangles)
19
20    where
21
22      coordinates is either a list of 2-tuples or an Mx2 Numeric array of
23      floats representing all x, y coordinates in the mesh.
24
25      triangles is either a list of 3-tuples or an Nx3 Numeric array of
26      integers representing indices of all vertices in the mesh.
27      Each vertex is identified by its index i in [0, M-1].
28
29
30    Example:
31        a = [0.0, 0.0]
32        b = [0.0, 2.0]
33        c = [2.0,0.0]
34        e = [2.0, 2.0]
35
36        points = [a, b, c, e]
37        triangles = [ [1,0,2], [1,2,3] ]   #bac, bce
38        mesh = Mesh(points, triangles)
39
40        #creates two triangles: bac and bce
41
42    Other:
43
44      In addition mesh computes an Nx6 array called vertex_coordinates.
45      This structure is derived from coordinates and contains for each
46      triangle the three x,y coordinates at the vertices.
47
48
49        This is a cut down version of mesh from pyvolution mesh.py
50    """
51
52    #FIXME: It would be a good idea to use geospatial data as an alternative
53    #input
54    def __init__(self, coordinates, triangles,
55                 geo_reference=None):
56        """
57        Build triangles from x,y coordinates (sequence of 2-tuples or
58        Mx2 Numeric array of floats) and triangles (sequence of 3-tuples
59        or Nx3 Numeric array of non-negative integers).
60
61        origin is a 3-tuple consisting of UTM zone, easting and northing.
62        If specified coordinates are assumed to be relative to this origin.
63        """
64
65        from Numeric import array, zeros, Int, Float, maximum, sqrt, sum
66
67        self.triangles = array(triangles).astype(Int)
68        self.coordinates = array(coordinates)
69
70        # FIXME: this stores a geo_reference, but when coords are returned
71        # This geo_ref is not taken into account!
72        if geo_reference is None:
73            self.geo_reference = Geo_reference() #Use defaults
74        else:
75            self.geo_reference = geo_reference
76
77        #Input checks
78        msg = 'Triangles must an Nx3 Numeric array or a sequence of 3-tuples'
79        assert len(self.triangles.shape) == 2, msg
80
81        msg = 'Coordinates must an Mx2 Numeric array or a sequence of 2-tuples'
82        assert len(self.coordinates.shape) == 2, msg
83
84        msg = 'Vertex indices reference non-existing coordinate sets'
85        assert max(max(self.triangles)) <= self.coordinates.shape[0], msg
86
87
88        #Register number of elements (N)
89        self.number_of_elements = N = self.triangles.shape[0]
90
91        #FIXME: Maybe move to statistics?
92        xy_extent = [ min(self.coordinates[:,0]), min(self.coordinates[:,1]) ,
93                      max(self.coordinates[:,0]), max(self.coordinates[:,1]) ]
94
95        self.xy_extent = array(xy_extent, Float)
96
97
98        #Allocate space for geometric quantities
99        self.normals = zeros((N, 6), Float)
100        self.areas = zeros(N, Float)
101        self.edgelengths = zeros((N, 3), Float)
102
103        #Get x,y coordinates for all triangles and store
104        self.vertex_coordinates = V = self.compute_vertex_coordinates()
105
106        #Initialise each triangle
107        for i in range(N):
108            #if i % (N/10) == 0: print '(%d/%d)' %(i, N)
109
110            x0 = V[i, 0]; y0 = V[i, 1]
111            x1 = V[i, 2]; y1 = V[i, 3]
112            x2 = V[i, 4]; y2 = V[i, 5]
113
114            #Area
115            self.areas[i] = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
116
117            msg = 'Triangle (%f,%f), (%f,%f), (%f, %f)' %(x0,y0,x1,y1,x2,y2)
118            msg += ' is degenerate:  area == %f' %self.areas[i]
119            assert self.areas[i] > 0.0, msg
120
121
122            #Normals
123            #The normal vectors
124            # - point outward from each edge
125            # - are orthogonal to the edge
126            # - have unit length
127            # - Are enumerated according to the opposite corner:
128            #   (First normal is associated with the edge opposite
129            #    the first vertex, etc)
130            # - Stored as six floats n0x,n0y,n1x,n1y,n2x,n2y per triangle
131
132            n0 = array([x2 - x1, y2 - y1])
133            l0 = sqrt(sum(n0**2))
134
135            n1 = array([x0 - x2, y0 - y2])
136            l1 = sqrt(sum(n1**2))
137
138            n2 = array([x1 - x0, y1 - y0])
139            l2 = sqrt(sum(n2**2))
140
141            #Normalise
142            n0 /= l0
143            n1 /= l1
144            n2 /= l2
145
146            #Compute and store
147            self.normals[i, :] = [n0[1], -n0[0],
148                                  n1[1], -n1[0],
149                                  n2[1], -n2[0]]
150
151            #Edgelengths
152            self.edgelengths[i, :] = [l0, l1, l2]
153
154        #Build vertex list
155        self.build_vertexlist()
156
157           
158
159    def __len__(self):
160        return self.number_of_elements
161
162    def __repr__(self):
163        return 'Mesh: %d triangles, %d elements'\
164               %(self.coordinates.shape[0], len(self))
165
166    def get_normals(self):
167        """Return all normal vectors.
168        Return normal vectors for all triangles as an Nx6 array
169        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
170        """
171        return self.normals
172
173
174    def get_normal(self, i, j):
175        """Return normal vector j of the i'th triangle.
176        Return value is the numeric array slice [x, y]
177        """
178        return self.normals[i, 2*j:2*j+2]
179
180
181
182    def get_vertex_coordinates(self, obj = False):
183        """Return all vertex coordinates.
184        Return all vertex coordinates for all triangles as an Nx6 array
185        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
186
187        if obj is True, the x/y pairs are returned in a 3*N x 2 array.
188        FIXME, we might make that the default.
189        FIXME Maybe use keyword: continuous = False for this condition?
190        FIXME - Maybe use something referring to unique vertices?
191
192       
193        """
194
195        if obj is True:
196            from Numeric import concatenate, reshape
197            V = self.vertex_coordinates
198            #return concatenate( (V[:,0:2], V[:,2:4], V[:,4:6]), axis=0)
199
200            N = V.shape[0]
201            return reshape(V, (3*N, 2))
202        else:   
203            return self.vertex_coordinates
204
205
206    def get_vertex_coordinate(self, i, j):
207        """Return coordinates for vertex j of the i'th triangle.
208        Return value is the numeric array slice [x, y]
209        """
210        return self.vertex_coordinates[i, 2*j:2*j+2]
211
212
213    def compute_vertex_coordinates(self):
214        """Return vertex coordinates for all triangles as an Nx6 array
215        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
216        """
217
218        #FIXME (Ole): Perhaps they should be ordered as in obj files??
219        #See quantity.get_vertex_values
220        #FIXME (Ole) - oh yes they should
221
222        from Numeric import zeros, Float
223
224        N = self.number_of_elements
225        vertex_coordinates = zeros((N, 6), Float)
226
227        for i in range(N):
228            for j in range(3):
229                k = self.triangles[i,j]  #Index of vertex 0
230                v_k = self.coordinates[k]
231                vertex_coordinates[i, 2*j+0] = v_k[0]
232                vertex_coordinates[i, 2*j+1] = v_k[1]
233
234        return vertex_coordinates
235
236    def get_vertices(self, indices=None):
237        """Get connectivity
238        indices is the set of element ids of interest
239        """
240
241        from Numeric import take
242
243        if (indices ==  None):
244            indices = range(len(self))  #len(self)=number of elements
245
246        return  take(self.triangles, indices)
247
248    #FIXME - merge these two (get_vertices and get_triangles)
249    def get_triangles(self, obj = False):
250        """Get connetivity
251        Return triangles (triplets of indices into point coordinates)
252       
253        If obj is True return structure commensurate with replicated
254        points, allowing for discontinuities
255        (FIXME: Need good name for this concept)
256        """
257
258        if obj is True:
259            from Numeric import array, reshape, Int       
260            m = len(self)  #Number of triangles
261            M = 3*m        #Total number of unique vertices
262            T = reshape(array(range(M)).astype(Int), (m,3))
263        else:
264            T = self.triangles
265
266        return T     
267
268   
269
270    def get_unique_vertices(self,  indices=None):
271        triangles = self.get_vertices(indices=indices)
272        unique_verts = {}
273        for triangle in triangles:
274           unique_verts[triangle[0]] = 0
275           unique_verts[triangle[1]] = 0
276           unique_verts[triangle[2]] = 0
277        return unique_verts.keys()
278
279    def build_vertexlist(self):
280        """Build vertexlist index by vertex ids and for each entry (point id)
281        build a list of (triangles, vertex_id) pairs that use the point
282        as vertex.
283
284        Preconditions:
285          self.coordinates and self.triangles are defined
286
287        Postcondition:
288          self.vertexlist is built
289        """
290
291        vertexlist = [None]*len(self.coordinates)
292        for i in range(self.number_of_elements):
293
294            a = self.triangles[i, 0]
295            b = self.triangles[i, 1]
296            c = self.triangles[i, 2]
297
298            #Register the vertices v as lists of
299            #(triangle_id, vertex_id) tuples associated with them
300            #This is used for smoothing
301            for vertex_id, v in enumerate([a,b,c]):
302                if vertexlist[v] is None:
303                    vertexlist[v] = []
304
305                vertexlist[v].append( (i, vertex_id) )
306
307        self.vertexlist = vertexlist
308
309
310    def get_extent(self):
311        """Return min and max of all x and y coordinates
312        """
313
314
315        C = self.get_vertex_coordinates()
316        X = C[:,0:6:2].copy()
317        Y = C[:,1:6:2].copy()
318
319        xmin = min(X.flat)
320        xmax = max(X.flat)
321        ymin = min(Y.flat)
322        ymax = max(Y.flat)
323
324        return xmin, xmax, ymin, ymax
325
326
327    def get_area(self):
328        """Return total area of mesh
329        """
330
331        return sum(self.areas)
332
333       
Note: See TracBrowser for help on using the repository browser.