source: inundation/pyvolution/general_mesh.py @ 2407

Last change on this file since 2407 was 2407, checked in by ole, 18 years ago

Pushed caching into conversion functions and beautified the Sydney scenario

File size: 10.2 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        if geo_reference is None:
71            self.geo_reference = Geo_reference() #Use defaults
72        else:
73            self.geo_reference = geo_reference
74
75        #Input checks
76        msg = 'Triangles must an Nx3 Numeric array or a sequence of 3-tuples'
77        assert len(self.triangles.shape) == 2, msg
78
79        msg = 'Coordinates must an Mx2 Numeric array or a sequence of 2-tuples'
80        assert len(self.coordinates.shape) == 2, msg
81
82        msg = 'Vertex indices reference non-existing coordinate sets'
83        assert max(max(self.triangles)) <= self.coordinates.shape[0], msg
84
85
86        #Register number of elements (N)
87        self.number_of_elements = N = self.triangles.shape[0]
88
89        xy_extent = [ min(self.coordinates[:,0]), min(self.coordinates[:,1]) ,
90                      max(self.coordinates[:,0]), max(self.coordinates[:,1]) ]
91
92        self.xy_extent = array(xy_extent, Float)
93
94
95        #Allocate space for geometric quantities
96        self.normals = zeros((N, 6), Float)
97        self.areas = zeros(N, Float)
98        self.edgelengths = zeros((N, 3), Float)
99
100        #Get x,y coordinates for all triangles and store
101        self.vertex_coordinates = V = self.compute_vertex_coordinates()
102
103        #Initialise each triangle
104        for i in range(N):
105            #if i % (N/10) == 0: print '(%d/%d)' %(i, N)
106
107            x0 = V[i, 0]; y0 = V[i, 1]
108            x1 = V[i, 2]; y1 = V[i, 3]
109            x2 = V[i, 4]; y2 = V[i, 5]
110
111            #Area
112            self.areas[i] = abs((x1*y0-x0*y1)+(x2*y1-x1*y2)+(x0*y2-x2*y0))/2
113
114            msg = 'Triangle (%f,%f), (%f,%f), (%f, %f)' %(x0,y0,x1,y1,x2,y2)
115            msg += ' is degenerate:  area == %f' %self.areas[i]
116            assert self.areas[i] > 0.0, msg
117
118
119            #Normals
120            #The normal vectors
121            # - point outward from each edge
122            # - are orthogonal to the edge
123            # - have unit length
124            # - Are enumerated according to the opposite corner:
125            #   (First normal is associated with the edge opposite
126            #    the first vertex, etc)
127            # - Stored as six floats n0x,n0y,n1x,n1y,n2x,n2y per triangle
128
129            n0 = array([x2 - x1, y2 - y1])
130            l0 = sqrt(sum(n0**2))
131
132            n1 = array([x0 - x2, y0 - y2])
133            l1 = sqrt(sum(n1**2))
134
135            n2 = array([x1 - x0, y1 - y0])
136            l2 = sqrt(sum(n2**2))
137
138            #Normalise
139            n0 /= l0
140            n1 /= l1
141            n2 /= l2
142
143            #Compute and store
144            self.normals[i, :] = [n0[1], -n0[0],
145                                  n1[1], -n1[0],
146                                  n2[1], -n2[0]]
147
148            #Edgelengths
149            self.edgelengths[i, :] = [l0, l1, l2]
150
151        #Build vertex list
152        self.build_vertexlist()
153
154    def __len__(self):
155        return self.number_of_elements
156
157    def __repr__(self):
158        return 'Mesh: %d triangles, %d elements'\
159               %(self.coordinates.shape[0], len(self))
160
161    def get_normals(self):
162        """Return all normal vectors.
163        Return normal vectors for all triangles as an Nx6 array
164        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
165        """
166        return self.normals
167
168
169    def get_normal(self, i, j):
170        """Return normal vector j of the i'th triangle.
171        Return value is the numeric array slice [x, y]
172        """
173        return self.normals[i, 2*j:2*j+2]
174
175
176
177    def get_vertex_coordinates(self, obj = False):
178        """Return all vertex coordinates.
179        Return all vertex coordinates for all triangles as an Nx6 array
180        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
181
182        if obj is True, the x/y pairs are returned in a 3*N x 2 array.
183        FIXME, we might make that the default.
184        FIXME Maybe use keyword: continuous = False for this condition?
185
186       
187        """
188
189        if obj is True:
190            from Numeric import concatenate, reshape
191            V = self.vertex_coordinates
192            #return concatenate( (V[:,0:2], V[:,2:4], V[:,4:6]), axis=0)
193
194            N = V.shape[0]
195            return reshape(V, (3*N, 2))
196        else:   
197            return self.vertex_coordinates
198
199
200    def get_vertex_coordinate(self, i, j):
201        """Return coordinates for vertex j of the i'th triangle.
202        Return value is the numeric array slice [x, y]
203        """
204        return self.vertex_coordinates[i, 2*j:2*j+2]
205
206
207    def compute_vertex_coordinates(self):
208        """Return vertex coordinates for all triangles as an Nx6 array
209        (ordered as x0, y0, x1, y1, x2, y2 for each triangle)
210        """
211
212        #FIXME (Ole): Perhaps they should be ordered as in obj files??
213        #See quantity.get_vertex_values
214        #FIXME (Ole) - oh yes they should
215
216        from Numeric import zeros, Float
217
218        N = self.number_of_elements
219        vertex_coordinates = zeros((N, 6), Float)
220
221        for i in range(N):
222            for j in range(3):
223                k = self.triangles[i,j]  #Index of vertex 0
224                v_k = self.coordinates[k]
225                vertex_coordinates[i, 2*j+0] = v_k[0]
226                vertex_coordinates[i, 2*j+1] = v_k[1]
227
228        return vertex_coordinates
229
230    def get_vertices(self, indices=None):
231        """Get connectivity
232        indices is the set of element ids of interest
233        """
234
235        from Numeric import take
236
237        if (indices ==  None):
238            indices = range(len(self))  #len(self)=number of elements
239
240        return  take(self.triangles, indices)
241
242    #FIXME - merge these two (get_vertices and get_triangles)
243    def get_triangles(self, obj = False):
244        """Get connetivity
245        Return triangles (triplets of indices into point coordinates)
246       
247        If obj is True return structure commensurate with replicated
248        points, allowing for discontinuities
249        (FIXME: Need good name for this concept)
250        """
251
252        if obj is True:
253            from Numeric import array, reshape, Int       
254            m = len(self)  #Number of triangles
255            M = 3*m        #Total number of unique vertices
256            T = reshape(array(range(M)).astype(Int), (m,3))
257        else:
258            T = self.triangles
259
260        return T     
261
262   
263
264    def get_unique_vertices(self,  indices=None):
265        triangles = self.get_vertices(indices=indices)
266        unique_verts = {}
267        for triangle in triangles:
268           unique_verts[triangle[0]] = 0
269           unique_verts[triangle[1]] = 0
270           unique_verts[triangle[2]] = 0
271        return unique_verts.keys()
272
273    def build_vertexlist(self):
274        """Build vertexlist index by vertex ids and for each entry (point id)
275        build a list of (triangles, vertex_id) pairs that use the point
276        as vertex.
277
278        Preconditions:
279          self.coordinates and self.triangles are defined
280
281        Postcondition:
282          self.vertexlist is built
283        """
284
285        vertexlist = [None]*len(self.coordinates)
286        for i in range(self.number_of_elements):
287
288            a = self.triangles[i, 0]
289            b = self.triangles[i, 1]
290            c = self.triangles[i, 2]
291
292            #Register the vertices v as lists of
293            #(triangle_id, vertex_id) tuples associated with them
294            #This is used for smoothing
295            for vertex_id, v in enumerate([a,b,c]):
296                if vertexlist[v] is None:
297                    vertexlist[v] = []
298
299                vertexlist[v].append( (i, vertex_id) )
300
301        self.vertexlist = vertexlist
302
303
304    def get_extent(self):
305        """Return min and max of all x and y coordinates
306        """
307
308
309        C = self.get_vertex_coordinates()
310        X = C[:,0:6:2].copy()
311        Y = C[:,1:6:2].copy()
312
313        xmin = min(X.flat)
314        xmax = max(X.flat)
315        ymin = min(Y.flat)
316        ymax = max(Y.flat)
317
318        return xmin, xmax, ymin, ymax
319
320
321    def get_area(self):
322        """Return total area of mesh
323        """
324
325        return sum(self.areas)
Note: See TracBrowser for help on using the repository browser.