source: inundation/ga/storm_surge/pyvolution/util.py @ 850

Last change on this file since 850 was 850, checked in by ole, 20 years ago

More work towards spatio-temporal boundary.

File size: 30.0 KB
Line 
1"""This modul contains various auxiliary function used by pyvolution.
2
3It is also a clearing house for function tat may later earn a module
4of their own.
5"""
6
7def angle(v):
8    """Compute angle between e1 (the unit vector in the x-direction)
9    and the specified vector
10    """
11
12    from math import acos, pi, sqrt
13    from Numeric import sum, array
14
15    l = sqrt( sum (array(v)**2))
16    v1 = v[0]/l
17    v2 = v[1]/l
18       
19    theta = acos(v1)
20
21    if v2 < 0:
22        #Quadrant 3 or 4
23        theta = 2*pi-theta
24
25    return theta
26
27
28def anglediff(v0, v1):
29    """Compute difference between angle of vector x0, y0 and x1, y1.
30    This is used for determining the ordering of vertices,
31    e.g. for checking if they are counter clockwise.
32   
33    Always return a positive value
34    """
35       
36    from math import pi
37   
38    a0 = angle(v0)
39    a1 = angle(v1)
40
41    #Ensure that difference will be positive
42    if a0 < a1:
43        a0 += 2*pi
44           
45    return a0-a1
46
47
48def mean(x):
49    from Numeric import sum
50    return sum(x)/len(x)
51
52
53def point_on_line(x, y, x0, y0, x1, y1): 
54    """Determine whether a point is on a line segment
55   
56    Input: x, y, x0, x0, x1, y1: where
57        point is given by x, y
58        line is given by (x0, y0) and (x1, y1)
59       
60    """
61       
62    from Numeric import array, dot, allclose
63    from math import sqrt
64
65    a = array([x - x0, y - y0]) 
66    a_normal = array([a[1], -a[0]])
67                       
68    b = array([x1 - x0, y1 - y0])
69   
70    if dot(a_normal, b) == 0:
71        #Point is somewhere on the infinite extension of the line
72
73        len_a = sqrt(sum(a**2))               
74        len_b = sqrt(sum(b**2))                                 
75        if dot(a, b) >= 0 and len_a <= len_b:
76           return True
77        else:   
78           return False
79    else:
80      return False 
81   
82
83def file_function(filename, domain=None, quantities = None, interpolation_points = None): 
84    assert type(filename) == type(''),\
85               'First argument to File_function must be a string'
86
87    try:
88        fid = open(filename)
89    except Exception, e:
90        msg = 'File "%s" could not be opened: Error="%s"'\
91                  %(filename, e)
92        raise msg
93
94    line = fid.readline()
95    fid.close()
96
97    if domain is not None:
98        quantities = domain.conserved_quantities   
99    else:
100        quantities = None
101       
102    #Choose format
103    #FIXME: Maybe these can be merged later on
104    if line[:3] == 'CDF':
105        return File_function_NetCDF(filename, domain, quantities, interpolation_points)
106    else:
107        return File_function_ASCII(filename, domain, quantities, interpolation_points)   
108           
109             
110class File_function_NetCDF:
111    """Read time history of spatial data from NetCDF sww file and
112    return a callable object f(t,x,y) 
113    which will return interpolated values based on the input file.   
114   
115    x, y may be either scalars or vectors
116   
117    #FIXME: More about format, interpolation  and order of quantities
118   
119    The quantities returned by the callable objects are specified by
120    the list quantities which must contain the names of the
121    quantities to be returned and also reflect the order, e.g. for
122    the shallow water wave equation, on would have
123    quantities = ['stage', 'xmomentum', 'ymomentum']
124   
125    interpolation_points decides at which points interpolated quantities are to be computed
126    If None, return average value
127    """
128   
129    def  __init__(self, filename, domain=None, quantities=None, interpolation_points=None):
130        """Initialise callable object from a file.
131
132        If domain is specified, model time (domain.starttime)
133        will be checked and possibly modified
134       
135
136
137        All times are assumed to be in UTC
138        """
139
140        import time, calendar
141        from config import time_format
142        from Scientific.IO.NetCDF import NetCDFFile     
143
144        #Open NetCDF file
145        fid = NetCDFFile(filename, 'r')
146
147        if quantities is None or len(quantities) < 1:
148            msg = 'ERROR: File must contain at least one independent value'
149            raise msg
150
151        missing = []
152        for quantity in ['x', 'y', 'time'] + quantities:
153             if not fid.variables.has_key(quantity):
154                 missing.append(quantity)
155                 
156        if len(missing) > 0:
157            msg = 'Quantities %s could not be found in file %s'\
158                  %(str(missing), filename)
159            raise msg
160                 
161
162        #Get first timestep
163        try:
164            starttime = fid.starttime[0]
165        except ValueError:   
166            msg = 'Could not read starttime from file %s' %filename
167            raise msg
168
169   
170        self.number_of_values = len(quantities)
171        self.fid = fid
172        self.filename = filename
173        self.starttime = starttime
174        self.quantities = quantities
175        self.domain = domain
176
177        if domain is not None:
178            msg = 'WARNING: Start time as specified in domain (%s)'\
179                  %domain.starttime
180            msg += ' is earlier than the starttime of file %s: %s.'\
181                     %(self.filename, self.starttime)
182            msg += 'Modifying starttime accordingly.'
183            if self.starttime > domain.starttime:
184                #FIXME: Print depending on some verbosity setting
185                #print msg
186                domain.starttime = self.starttime #Modifying model time
187
188        #Read all data in and produce values for desired data points at each timestep
189        self.spatial_interpolation(interpolation_points) 
190   
191
192    def spatial_interpolation(self, interpolation_points):
193        """For each timestep in fid: read surface, interpolate to desired points
194        and store values for use when object is called.
195        """
196       
197        from Numeric import array, zeros, Float, alltrue, concatenate
198
199        from config import time_format
200        from least_squares import Interpolation
201        import time, calendar
202       
203        fid = self.fid
204
205        #Get variables
206        x = fid.variables['x']
207        y = fid.variables['y']
208        z = fid.variables['elevation']
209        triangles = fid.variables['volumes']
210        time = fid.variables['time']
211       
212        #Check
213        msg = 'File %s must list time as a monotonuosly ' %self.filename
214        msg += 'increasing sequence'
215        assert alltrue( time[1:] - time[:-1] > 0 ), msg
216       
217
218        if interpolation_points is not None: 
219       
220            try:
221                P = array(interpolation_points) 
222            except:
223                msg = 'Interpolation points must be an N x 2 Numeric array or a list of points\n'
224                msg += 'I got: %s.' %( str(interpolation_points)[:60] + '...')
225                raise msg
226           
227           
228            ####self.Q = zeros( (len(time),
229             
230            for i, t in enumerate(time[:]):
231   
232                #Interpolate quantities at this timestep
233                vertex_coordinates = concatenate((x,y), axis=1) #m x 2 array
234                interpol = Interpolation(vertex_coordinates, 
235                                         triangles,
236                                         point_coordinates = P,
237                                         alpha = 0, 
238                                         verbose = False)
239
240                for name in self.quantities:                           
241                    print t, name, interpol.interpolate(fid.variables[name][i,:])                   
242
243        else:
244            pass 
245
246        self.T = time[:]
247       
248        return
249       
250        fid = open(self.filename)       
251        lines = fid.readlines()
252        fid.close()
253       
254        N = len(lines)
255        d = self.number_of_values
256
257        T = zeros(N, Float)       #Time
258        Q = zeros((N, d), Float)  #Values
259       
260        for i, line in enumerate(lines):
261            fields = line.split(',')
262            realtime = calendar.timegm(time.strptime(fields[0], time_format))
263
264            T[i] = realtime - self.starttime
265
266            for j, value in enumerate(fields[1].split()):
267                Q[i, j] = float(value)
268
269
270
271        self.T = T     #Time
272        self.Q = Q     #Values
273        self.index = 0 #Initial index
274
275       
276             
277class File_function_ASCII:
278    """Read time series from file and return a callable object:
279    f(t,x,y)
280    which will return interpolated values based on the input file.
281
282    The file format is assumed to be either two fields separated by a comma:
283
284        time [DD/MM/YY hh:mm:ss], value0 value1 value2 ...
285
286    or four comma separated fields
287
288        time [DD/MM/YY hh:mm:ss], x, y, value0 value1 value2 ...
289
290    In either case, the callable object will return a tuple of
291    interpolated values, one each value stated in the file.
292
293   
294    E.g
295
296      31/08/04 00:00:00, 1.328223 0 0
297      31/08/04 00:15:00, 1.292912 0 0
298
299    will provide a time dependent function f(t,x=None,y=None),
300    ignoring x and y, which returns three values per call.
301
302   
303    NOTE: At this stage the function is assumed to depend on
304    time only, i.e  no spatial dependency!!!!!
305    When that is needed we can use the least_squares interpolation.
306   
307    #FIXME: This should work with netcdf (e.g. sww) and thus render the
308    #spatio-temporal boundary condition in shallow water fully general
309   
310    #FIXME: Specified quantites not used here -
311    #always return whatever is in the file
312    """
313
314   
315    def __init__(self, filename, domain=None, quantities = None, interpolation_points=None):
316        """Initialise callable object from a file.
317        See docstring for class File_function
318
319        If domain is specified, model time (domain,starttime)
320        will be checked and possibly modified
321
322        All times are assumed to be in UTC
323        """
324
325        import time, calendar
326        from Numeric import array
327        from config import time_format
328
329        fid = open(filename)
330        line = fid.readline()
331        fid.close()
332   
333        fields = line.split(',')
334        msg = 'File %s must have the format date, value0 value1 value2 ...'
335        msg += ' or date, x, y, value0 value1 value2 ...'
336        mode = len(fields)
337        assert mode in [2,4], msg
338
339        try:
340            starttime = calendar.timegm(time.strptime(fields[0], time_format))
341        except ValueError:   
342            msg = 'First field in file %s must be' %filename
343            msg += ' date-time with format %s.\n' %time_format
344            msg += 'I got %s instead.' %fields[0] 
345            raise msg
346
347
348        #Split values
349        values = []
350        for value in fields[mode-1].split():
351            values.append(float(value))
352
353        q = array(values)
354       
355        msg = 'ERROR: File must contain at least one independent value'
356        assert len(q.shape) == 1, msg
357           
358        self.number_of_values = len(q)
359
360        self.filename = filename
361        self.starttime = starttime
362        self.domain = domain
363
364        if domain is not None:
365            msg = 'WARNING: Start time as specified in domain (%s)'\
366                  %domain.starttime
367            msg += ' is earlier than the starttime of file %s: %s.'\
368                     %(self.filename, self.starttime)
369            msg += 'Modifying starttime accordingly.'
370            if self.starttime > domain.starttime:
371                #FIXME: Print depending on some verbosity setting
372                #print msg
373                domain.starttime = self.starttime #Modifying model time
374       
375            #if domain.starttime is None:
376            #    domain.starttime = self.starttime
377            #else:
378            #    msg = 'WARNING: Start time as specified in domain (%s)'\
379            #          %domain.starttime
380            #    msg += ' is earlier than the starttime of file %s: %s.'\
381            #             %(self.filename, self.starttime)
382            #    msg += 'Modifying starttime accordingly.'
383            #    if self.starttime > domain.starttime:
384            #        #FIXME: Print depending on some verbosity setting
385            #        #print msg
386            #        domain.starttime = self.starttime #Modifying model time
387
388        if mode == 2:       
389            self.read_times() #Now read all times in.
390        else:
391            raise 'x,y dependency not yet implemented'
392
393       
394    def read_times(self):
395        """Read time and values
396        """
397        from Numeric import zeros, Float, alltrue
398        from config import time_format
399        import time, calendar
400       
401        fid = open(self.filename)       
402        lines = fid.readlines()
403        fid.close()
404       
405        N = len(lines)
406        d = self.number_of_values
407
408        T = zeros(N, Float)       #Time
409        Q = zeros((N, d), Float)  #Values
410       
411        for i, line in enumerate(lines):
412            fields = line.split(',')
413            realtime = calendar.timegm(time.strptime(fields[0], time_format))
414
415            T[i] = realtime - self.starttime
416
417            for j, value in enumerate(fields[1].split()):
418                Q[i, j] = float(value)
419
420        msg = 'File %s must list time as a monotonuosly ' %self.filename
421        msg += 'increasing sequence'
422        assert alltrue( T[1:] - T[:-1] > 0 ), msg
423
424        self.T = T     #Time
425        self.Q = Q     #Values
426        self.index = 0 #Initial index
427
428       
429    def __repr__(self):
430        return 'File function'
431
432       
433
434    def __call__(self, t, x=None, y=None):
435        """Evaluate f(t,x,y)
436
437        FIXME: x, y dependency not yet implemented except that
438        result is a vector of same length as x and y
439        FIXME: Naaaa
440        """
441
442        from math import pi, cos, sin, sqrt
443       
444
445        #Find time tau such that
446        #
447        # domain.starttime + t == self.starttime + tau
448       
449        if self.domain is not None:
450            tau = self.domain.starttime-self.starttime+t
451        else:
452            tau = t
453       
454               
455        msg = 'Time interval derived from file %s (%s:%s) does not match model time: %s'\
456              %(self.filename, self.T[0], self.T[1], tau)
457        if tau < self.T[0]: raise msg
458        if tau > self.T[-1]: raise msg       
459
460        oldindex = self.index
461
462        #Find slot
463        while tau > self.T[self.index]: self.index += 1
464        while tau < self.T[self.index]: self.index -= 1
465
466        #t is now between index and index+1
467        ratio = (tau - self.T[self.index])/\
468                (self.T[self.index+1] - self.T[self.index])
469
470        #Compute interpolated values
471        q = self.Q[self.index,:] +\
472            ratio*(self.Q[self.index+1,:] - self.Q[self.index,:]) 
473
474        #Return vector of interpolated values
475        if x == None and y == None:
476            return q
477        else:
478            try:
479                N = len(x)
480            except:
481                return q
482            else:
483                from Numeric import ones, Float
484                #x is a vector - Create one constant column for each value
485                N = len(x)
486                assert len(y) == N, 'x and y must have same length'
487             
488                res = []
489                for col in q:
490                    res.append(col*ones(N, Float))
491           
492                return res
493           
494           
495#FIXME: TEMP       
496class File_function_copy:
497    """Read time series from file and return a callable object:
498    f(t,x,y)
499    which will return interpolated values based on the input file.
500
501    The file format is assumed to be either two fields separated by a comma:
502
503        time [DD/MM/YY hh:mm:ss], value0 value1 value2 ...
504
505    or four comma separated fields
506
507        time [DD/MM/YY hh:mm:ss], x, y, value0 value1 value2 ...
508
509    In either case, the callable object will return a tuple of
510    interpolated values, one each value stated in the file.
511
512   
513    E.g
514
515      31/08/04 00:00:00, 1.328223 0 0
516      31/08/04 00:15:00, 1.292912 0 0
517
518    will provide a time dependent function f(t,x=None,y=None),
519    ignoring x and y, which returns three values per call.
520
521   
522    NOTE: At this stage the function is assumed to depend on
523    time only, i.e  no spatial dependency!!!!!
524    When that is needed we can use the least_squares interpolation.
525   
526    #FIXME: This should work with netcdf (e.g. sww) and thus render the
527    #spatio-temporal boundary condition in shallow water fully general
528    """
529
530   
531    def __init__(self, filename, domain=None):
532        """Initialise callable object from a file.
533        See docstring for class File_function
534
535        If domain is specified, model time (domain,starttime)
536        will be checked and possibly modified
537
538        All times are assumed to be in UTC
539        """
540
541        import time, calendar
542        from Numeric import array
543        from config import time_format
544
545        assert type(filename) == type(''),\
546               'First argument to File_function must be a string'
547
548
549        try:
550            fid = open(filename)
551        except Exception, e:
552            msg = 'File "%s" could not be opened: Error="%s"'\
553                  %(filename, e)
554            raise msg
555
556
557        line = fid.readline()
558        fid.close()
559        fields = line.split(',')
560        msg = 'File %s must have the format date, value0 value1 value2 ...'
561        msg += ' or date, x, y, value0 value1 value2 ...'
562        mode = len(fields)
563        assert mode in [2,4], msg
564
565        try:
566            starttime = calendar.timegm(time.strptime(fields[0], time_format))
567        except ValueError:   
568            msg = 'First field in file %s must be' %filename
569            msg += ' date-time with format %s.\n' %time_format
570            msg += 'I got %s instead.' %fields[0] 
571            raise msg
572
573
574        #Split values
575        values = []
576        for value in fields[mode-1].split():
577            values.append(float(value))
578
579        q = array(values)
580       
581        msg = 'ERROR: File must contain at least one independent value'
582        assert len(q.shape) == 1, msg
583           
584        self.number_of_values = len(q)
585
586        self.filename = filename
587        self.starttime = starttime
588        self.domain = domain
589
590        if domain is not None:
591            if domain.starttime is None:
592                domain.starttime = self.starttime
593            else:
594                msg = 'WARNING: Start time as specified in domain (%s)'\
595                      %domain.starttime
596                msg += ' is earlier than the starttime of file %s: %s.'\
597                         %(self.filename, self.starttime)
598                msg += 'Modifying starttime accordingly.'
599                if self.starttime > domain.starttime:
600                    #FIXME: Print depending on some verbosity setting
601                    #print msg
602                    domain.starttime = self.starttime #Modifying model time
603
604        if mode == 2:       
605            self.read_times() #Now read all times in.
606        else:
607            raise 'x,y dependency not yet implemented'
608
609       
610    def read_times(self):
611        """Read time and values
612        """
613        from Numeric import zeros, Float, alltrue
614        from config import time_format
615        import time, calendar
616       
617        fid = open(self.filename)       
618        lines = fid.readlines()
619        fid.close()
620       
621        N = len(lines)
622        d = self.number_of_values
623
624        T = zeros(N, Float)       #Time
625        Q = zeros((N, d), Float)  #Values
626       
627        for i, line in enumerate(lines):
628            fields = line.split(',')
629            realtime = calendar.timegm(time.strptime(fields[0], time_format))
630
631            T[i] = realtime - self.starttime
632
633            for j, value in enumerate(fields[1].split()):
634                Q[i, j] = float(value)
635
636        msg = 'File %s must list time as a monotonuosly ' %self.filename
637        msg += 'increasing sequence'
638        assert alltrue( T[1:] - T[:-1] > 0 ), msg
639
640        self.T = T     #Time
641        self.Q = Q     #Values
642        self.index = 0 #Initial index
643
644       
645    def __repr__(self):
646        return 'File function'
647
648       
649
650    def __call__(self, t, x=None, y=None):
651        """Evaluate f(t,x,y)
652
653        FIXME: x, y dependency not yet implemented except that
654        result is a vector of same length as x and y
655        FIXME: Naaaa
656        """
657
658        from math import pi, cos, sin, sqrt
659       
660
661        #Find time tau such that
662        #
663        # domain.starttime + t == self.starttime + tau
664       
665        if self.domain is not None:
666            tau = self.domain.starttime-self.starttime+t
667        else:
668            tau = t
669       
670               
671        msg = 'Time interval derived from file %s (%s:%s) does not match model time: %s'\
672              %(self.filename, self.T[0], self.T[1], tau)
673        if tau < self.T[0]: raise msg
674        if tau > self.T[-1]: raise msg       
675
676        oldindex = self.index
677
678        #Find slot
679        while tau > self.T[self.index]: self.index += 1
680        while tau < self.T[self.index]: self.index -= 1
681
682        #t is now between index and index+1
683        ratio = (tau - self.T[self.index])/\
684                (self.T[self.index+1] - self.T[self.index])
685
686        #Compute interpolated values
687        q = self.Q[self.index,:] +\
688            ratio*(self.Q[self.index+1,:] - self.Q[self.index,:]) 
689
690        #Return vector of interpolated values
691        if x == None and y == None:
692            return q
693        else:
694            try:
695                N = len(x)
696            except:
697                return q
698            else:
699                from Numeric import ones, Float
700                #x is a vector - Create one constant column for each value
701                N = len(x)
702                assert len(y) == N, 'x and y must have same length'
703             
704                res = []
705                for col in q:
706                    res.append(col*ones(N, Float))
707           
708                return res
709           
710
711def read_xya(filename, format = 'netcdf'):
712    """Read simple xya file, possibly with a header in the first line, just
713    x y [attributes]
714    separated by whitespace
715
716    Format can be either ASCII or NetCDF
717   
718    Return list of points, list of attributes and
719    attribute names if present otherwise None
720    """
721    #FIXME: Probably obsoleted by similar function in load_ASCII
722   
723    from Scientific.IO.NetCDF import NetCDFFile
724
725    if format.lower() == 'netcdf': 
726        #Get NetCDF
727       
728        fid = NetCDFFile(filename, 'r') 
729     
730        # Get the variables
731        points = fid.variables['points']
732        keys = fid.variables.keys()
733        attributes = {}
734        for key in keys:
735            attributes[key] = fid.variables[key]
736        #Don't close - arrays are needed outside this function,
737        #alternatively take a copy here
738    else:   
739        #Read as ASCII file assuming that it is separated by whitespace
740        fid = open(filename)
741        lines = fid.readlines()
742        fid.close()
743
744        #Check if there is a header line
745        fields = lines[0].strip().split()
746        try:
747            float(fields[0])
748        except:
749            #This must be a header line
750            attribute_names = fields
751            lines = lines[1:]
752        else:
753            attribute_names = ['elevation']  #HACK
754
755        attributes = {}   
756        for key in attribute_names:
757            attributes[key] = []
758
759        points = []
760        for line in lines:
761            fields = line.strip().split()
762            points.append( (float(fields[0]), float(fields[1])) )
763            for i, key in enumerate(attribute_names):
764                attributes[key].append( float(fields[2+i]) )
765       
766    return points, attributes
767   
768
769#####################################
770#POLYGON STUFF
771#
772#FIXME: ALl these should be put into new module polygon.py
773
774def inside_polygon(point, polygon, closed = True):
775    """Determine whether points are inside or outside a polygon
776   
777    Input:
778       point - Tuple of (x, y) coordinates, or list of tuples
779       polygon - list of vertices of polygon
780       closed - (optional) determine whether points on boundary should be
781       regarded as belonging to the polygon (closed = True)
782       or not (closed = False)
783   
784    Output:
785       If one point is considered, True or False is returned.
786       If multiple points are passed in, the function returns indices
787       of those points that fall inside the polygon     
788       
789    Examples:
790       inside_polygon( [0.5, 0.5], [[0,0], [1,0], [1,1], [0,1]] )
791       will evaluate to True as the point 0.5, 0.5 is inside the unit square
792       
793       inside_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]] )
794       will return the indices [0, 2] as only the first and the last point
795       is inside the unit square
796       
797    Remarks:
798       The vertices may be listed clockwise or counterclockwise and
799       the first point may optionally be repeated.   
800       Polygons do not need to be convex.
801       Polygons can have holes in them and points inside a hole is
802       regarded as being outside the polygon.         
803               
804       
805    Algorithm is based on work by Darel Finley,
806    http://www.alienryderflex.com/polygon/
807   
808    """   
809
810    from Numeric import array, Float, reshape
811   
812   
813    #Input checks
814    try:
815        point = array(point).astype(Float)         
816    except:
817        msg = 'Point %s could not be converted to Numeric array' %point
818        raise msg       
819       
820    try:
821        polygon = array(polygon).astype(Float)             
822    except:
823        msg = 'Polygon %s could not be converted to Numeric array' %polygon
824        raise msg               
825   
826    assert len(polygon.shape) == 2,\
827       'Polygon array must be a 2d array of vertices: %s' %polygon
828
829       
830    assert polygon.shape[1] == 2,\
831       'Polygon array must have two columns: %s' %polygon   
832
833
834    if len(point.shape) == 1:
835        one_point = True
836        points = reshape(point, (1,2))
837    else:       
838        one_point = False
839        points = point
840
841    N = polygon.shape[0] #Number of vertices in polygon
842
843    px = polygon[:,0]
844    py = polygon[:,1]   
845
846    #Begin algorithm
847    indices = []
848    for k in range(points.shape[0]):
849        #for each point   
850        x = points[k, 0]
851        y = points[k, 1]       
852
853        inside = False
854        for i in range(N):
855            j = (i+1)%N
856           
857            #Check for case where point is contained in line segment   
858            if point_on_line(x, y, px[i], py[i], px[j], py[j]):
859                if closed:
860                    inside = True
861                else:       
862                    inside = False
863                break
864            else:       
865                #Check if truly inside polygon             
866                if py[i] < y and py[j] >= y or\
867                   py[j] < y and py[i] >= y:
868                    if px[i] + (y-py[i])/(py[j]-py[i])*(px[j]-px[i]) < x:
869                        inside = not inside
870                   
871        if inside: indices.append(k)
872
873    if one_point: 
874        return inside
875    else:
876        return indices
877             
878
879class Polygon_function:
880    """Create callable object f: x,y -> z, where a,y,z are vectors and
881    where f will return different values depending on whether x,y belongs
882    to specified polygons.
883   
884    To instantiate:
885     
886       Polygon_function(polygons)
887       
888    where polygons is a dictionary of the form
889   
890      {P0: v0, P1: v1, ...}
891     
892      with Pi being lists of vertices defining polygons and vi either
893      constants or functions of x,y to be applied to points with the polygon.
894     
895    The function takes an optional argument, default which is the value
896    (or function) to used for points not belonging to any polygon.
897    For example:
898   
899       Polygon_function(polygons, default = 0.03)       
900     
901    If omitted the default value will be 0.0 
902   
903    Note: If two polygons overlap, the one last in the list takes precedence
904
905    """
906   
907    def __init__(self, regions, default = 0.0):
908       
909        try:
910            len(regions)
911        except:
912            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons           
913            raise msg
914
915
916        T = regions[0]                                   
917        try:
918            a = len(T)
919        except:   
920            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons           
921            raise msg   
922                   
923        assert a == 2, 'Must have two component each: %s' %T       
924
925        self.regions = regions             
926        self.default = default
927         
928         
929    def __call__(self, x, y):
930        from util import inside_polygon
931        from Numeric import ones, Float, concatenate, array, reshape, choose
932       
933        x = array(x).astype(Float)
934        y = array(y).astype(Float)     
935       
936        N = len(x)
937        assert len(y) == N
938       
939        points = concatenate( (reshape(x, (N, 1)), 
940                               reshape(y, (N, 1))), axis=1 )
941
942        if callable(self.default):
943            z = self.default(x,y)
944        else:                   
945            z = ones(N, Float) * self.default
946           
947        for polygon, value in self.regions:
948            indices = inside_polygon(points, polygon)
949           
950            #FIXME: This needs to be vectorised
951            if callable(value):
952                for i in indices:
953                    xx = array([x[i]])
954                    yy = array([y[i]])
955                    z[i] = value(xx, yy)[0]
956            else:   
957                for i in indices:
958                    z[i] = value
959
960        return z                 
961
962def read_polygon(filename):
963    """Read points assumed to form a polygon
964       There must be exactly two numbers in each line
965    """             
966   
967    #Get polygon
968    fid = open(filename)
969    lines = fid.readlines()
970    fid.close()
971    polygon = []
972    for line in lines:
973        fields = line.split(',')
974        polygon.append( [float(fields[0]), float(fields[1])] )
975
976    return polygon     
977   
978def populate_polygon(polygon, number_of_points, seed = None):
979    """Populate given polygon with uniformly distributed points.
980
981    Input:
982       polygon - list of vertices of polygon
983       number_of_points - (optional) number of points
984       seed - seed for random number generator (default=None)
985       
986    Output:
987       points - list of points inside polygon
988       
989    Examples:
990       populate_polygon( [[0,0], [1,0], [1,1], [0,1]], 5 )
991       will return five randomly sleected points inside the unit square
992    """
993
994    from random import uniform, seed
995
996    seed(seed)
997
998    points = []
999
1000    #Find outer extent of polygon
1001    max_x = min_x = polygon[0][0]
1002    max_y = min_y = polygon[0][1]
1003    for point in polygon[1:]:
1004        x = point[0] 
1005        if x > max_x: max_x = x
1006        if x < min_x: min_x = x           
1007        y = point[1] 
1008        if y > max_y: max_y = y
1009        if y < min_y: min_y = y           
1010
1011
1012    while len(points) < number_of_points:     
1013        x = uniform(min_x, max_x)
1014        y = uniform(min_y, max_y)       
1015
1016        if inside_polygon( [x,y], polygon ):
1017            points.append([x,y])
1018
1019    return points
1020
1021####################################################################
1022#Python versions of function that are also implemented in util_gateway.c
1023#
1024
1025def gradient_python(x0, y0, x1, y1, x2, y2, q0, q1, q2):
1026    """
1027    """
1028   
1029    det = (y2-y0)*(x1-x0) - (y1-y0)*(x2-x0)           
1030    a = (y2-y0)*(q1-q0) - (y1-y0)*(q2-q0)
1031    a /= det
1032
1033    b = (x1-x0)*(q2-q0) - (x2-x0)*(q1-q0)
1034    b /= det           
1035
1036    return a, b
1037
1038
1039
1040
1041
1042##############################################
1043#Initialise module
1044
1045import compile
1046if compile.can_use_C_extension('util_ext.c'):
1047    from util_ext import gradient, point_on_line
1048else:
1049    gradient = gradient_python
1050
1051
1052if __name__ == "__main__":
1053    pass
1054
Note: See TracBrowser for help on using the repository browser.