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

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

Work on file boundary (cyclone)

File size: 47.0 KB
Line 
1"""This module contains various auxiliary function used by pyvolution.
2
3It is also a clearing house for functions that 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    try:       
20        theta = acos(v1)
21    except ValueError, e:
22        print 'WARNING (util.py): Angle acos(%s) failed: %s' %(str(v1), e)
23
24        #FIXME, hack to avoid acos(1.0) Value error
25        # why is it happening?
26        # is it catching something we should avoid?
27        s = 1e-6
28        if (v1+s >  1.0) and (v1-s < 1.0) :
29            theta = 0.0
30        elif (v1+s >  -1.0) and (v1-s < -1.0):
31            theta = 3.1415926535897931
32        print 'WARNING (util.py): angle v1 is %f, setting acos to %f '\
33              %(v1, theta)   
34             
35    if v2 < 0:
36        #Quadrant 3 or 4
37        theta = 2*pi-theta
38
39    return theta
40
41
42def anglediff(v0, v1):
43    """Compute difference between angle of vector x0, y0 and x1, y1.
44    This is used for determining the ordering of vertices,
45    e.g. for checking if they are counter clockwise.
46   
47    Always return a positive value
48    """
49       
50    from math import pi
51   
52    a0 = angle(v0)
53    a1 = angle(v1)
54
55    #Ensure that difference will be positive
56    if a0 < a1:
57        a0 += 2*pi
58           
59    return a0-a1
60
61
62def mean(x):
63    from Numeric import sum
64    return sum(x)/len(x)
65
66
67def point_on_line(x, y, x0, y0, x1, y1): 
68    """Determine whether a point is on a line segment
69   
70    Input: x, y, x0, x0, x1, y1: where
71        point is given by x, y
72        line is given by (x0, y0) and (x1, y1)
73       
74    """
75       
76    from Numeric import array, dot, allclose
77    from math import sqrt
78
79    a = array([x - x0, y - y0]) 
80    a_normal = array([a[1], -a[0]])
81                       
82    b = array([x1 - x0, y1 - y0])
83   
84    if dot(a_normal, b) == 0:
85        #Point is somewhere on the infinite extension of the line
86
87        len_a = sqrt(sum(a**2))               
88        len_b = sqrt(sum(b**2))                                 
89        if dot(a, b) >= 0 and len_a <= len_b:
90           return True
91        else:   
92           return False
93    else:
94      return False 
95   
96
97def file_function(filename, domain=None, quantities = None, interpolation_points = None, verbose = False):
98    """If domain is specified, don't specify quantites as they are automatically derived
99    """
100
101
102    #FIXME (OLE): Should check origin of domain against that of file
103    #In fact, this is where origin should be converted to that of domain
104    #Also, check that file covers domain fully.
105   
106    assert type(filename) == type(''),\
107               'First argument to File_function must be a string'
108
109    try:
110        fid = open(filename)
111    except Exception, e:
112        msg = 'File "%s" could not be opened: Error="%s"'\
113                  %(filename, e)
114        raise msg
115
116    line = fid.readline()
117    fid.close()
118
119    if domain is not None:
120        quantities = domain.conserved_quantities   
121    else:
122        quantities = None
123       
124    #Choose format
125    #FIXME: Maybe these can be merged later on
126    if line[:3] == 'CDF':
127        return File_function_NetCDF(filename, domain, quantities, interpolation_points, verbose = verbose)
128    else:
129        return File_function_ASCII(filename, domain, quantities, interpolation_points)   
130           
131             
132class File_function_NetCDF:
133    """Read time history of spatial data from NetCDF sww file and
134    return a callable object f(t,x,y) 
135    which will return interpolated values based on the input file.   
136   
137    x, y may be either scalars or vectors
138   
139    #FIXME: More about format, interpolation  and order of quantities
140   
141    The quantities returned by the callable objects are specified by
142    the list quantities which must contain the names of the
143    quantities to be returned and also reflect the order, e.g. for
144    the shallow water wave equation, on would have
145    quantities = ['stage', 'xmomentum', 'ymomentum']
146   
147    interpolation_points decides at which points interpolated
148    quantities are to be computed whenever object is called.
149   
150   
151   
152    If None, return average value
153    """
154   
155    def  __init__(self, filename, domain=None, quantities=None,
156                  interpolation_points=None, verbose = False):
157        """Initialise callable object from a file.
158
159        If domain is specified, model time (domain.starttime)
160        will be checked and possibly modified
161       
162        All times are assumed to be in UTC
163        """
164
165        #FIXME: Check that model origin is the same as file's origin
166        #(both in UTM coordinates)
167        #If not - modify those from file to match domain
168       
169        import time, calendar
170        from config import time_format
171        from Scientific.IO.NetCDF import NetCDFFile     
172
173        #Open NetCDF file
174        if verbose: print 'Reading', filename
175        fid = NetCDFFile(filename, 'r')
176
177        if quantities is None or len(quantities) < 1:
178            msg = 'ERROR: File must contain at least one independent value'
179            raise msg
180
181        missing = []
182        for quantity in ['x', 'y', 'time'] + quantities:
183             if not fid.variables.has_key(quantity):
184                 missing.append(quantity)
185                 
186        if len(missing) > 0:
187            msg = 'Quantities %s could not be found in file %s'\
188                  %(str(missing), filename)
189            raise msg
190                 
191
192        #Get first timestep
193        try:
194            self.starttime = fid.starttime[0]
195        except ValueError:   
196            msg = 'Could not read starttime from file %s' %filename
197            raise msg
198
199        #Get origin
200        self.xllcorner = fid.xllcorner[0]
201        self.yllcorner = fid.yllcorner[0]           
202   
203        self.number_of_values = len(quantities)
204        self.fid = fid
205        self.filename = filename
206        self.quantities = quantities
207        self.domain = domain
208        self.interpolation_points = interpolation_points
209
210        if domain is not None:
211            msg = 'WARNING: Start time as specified in domain (%f)'\
212                  %domain.starttime
213            msg += ' is earlier than the starttime of file %s (%f).'\
214                     %(self.filename, self.starttime)
215            msg += ' Modifying domain starttime accordingly.'
216            if self.starttime > domain.starttime:
217                if verbose: print msg
218                domain.starttime = self.starttime #Modifying model time
219                if verbose: print 'Domain starttime is now set to %f' %domain.starttime
220
221        #Read all data in and produce values for desired data points at each timestep
222        self.spatial_interpolation(interpolation_points, verbose = verbose) 
223        fid.close()
224
225    def spatial_interpolation(self, interpolation_points, verbose = False):
226        """For each timestep in fid: read surface, interpolate to desired points
227        and store values for use when object is called.
228        """
229       
230        from Numeric import array, zeros, Float, alltrue, concatenate, reshape
231
232        from config import time_format
233        from least_squares import Interpolation
234        import time, calendar
235       
236        fid = self.fid
237
238        #Get variables
239        if verbose: print 'Get variables'
240        x = fid.variables['x'][:]
241        y = fid.variables['y'][:]
242        z = fid.variables['elevation'][:]
243        triangles = fid.variables['volumes'][:]
244        time = fid.variables['time'][:]
245       
246        #Check
247        msg = 'File %s must list time as a monotonuosly ' %self.filename
248        msg += 'increasing sequence'
249        assert alltrue(time[1:] - time[:-1] > 0 ), msg 
250       
251
252        if interpolation_points is not None: 
253       
254            try:
255                P = array(interpolation_points) 
256            except:
257                msg = 'Interpolation points must be an N x 2 Numeric array or a list of points\n'
258                msg += 'I got: %s.' %( str(interpolation_points)[:60] + '...')
259                raise msg
260           
261           
262            self.T = time[:]  #Time assumed to be relative to starttime
263            self.index = 0    #Initial time index       
264
265            self.values = {}               
266            for name in self.quantities:
267                self.values[name] = zeros( (len(self.T), 
268                                            len(interpolation_points)), 
269                                            Float)                 
270
271            #Build interpolator
272            if verbose: print 'Build interpolation matrix'
273            x = reshape(x, (len(x),1))
274            y = reshape(y, (len(y),1))               
275            vertex_coordinates = concatenate((x,y), axis=1) #m x 2 array
276
277            interpol = Interpolation(vertex_coordinates, 
278                                     triangles,
279                                     point_coordinates = P,
280                                     alpha = 0, 
281                                     verbose = verbose)
282
283
284            if verbose: print 'Interpolate'
285            for i, t in enumerate(self.T):
286                #Interpolate quantities at this timestep
287                #print ' time step %d of %d' %(i, len(self.T))
288                for name in self.quantities:
289                    self.values[name][i, :] =\
290                    interpol.interpolate(fid.variables[name][i,:])   
291
292            #Report
293            if verbose:
294                print '------------------------------------------------'
295                print 'File_function statistics:'
296                print '  Name: %s' %self.filename
297                print '  Reference:'
298                print '    Lower left corner: [%f, %f]'\
299                      %(self.xllcorner, self.yllcorner) 
300                print '    Start time (file):   %f' %self.starttime
301                #print '    Start time (domain): %f' %domain.starttime
302                print '  Extent:'
303                print '    x in [%f, %f], len(x) == %d'\
304                      %(min(x.flat), max(x.flat), len(x.flat))
305                print '    y in [%f, %f], len(y) == %d'\
306                      %(min(y.flat), max(y.flat), len(y.flat))
307                print '    t in [%f, %f], len(t) == %d'\
308                      %(min(self.T), max(self.T), len(self.T))
309                print '  Quantities:'
310                for name in self.quantities:
311                    q = fid.variables[name][:].flat
312                    print '    %s in [%f, %f]' %(name, min(q), max(q))
313
314                   
315                print '  Interpolation points (xi, eta):'\
316                      'number of points == %d ' %P.shape[0]
317                print '    xi in [%f, %f]' %(min(P[:,0]), max(P[:,0]))
318                print '    eta in [%f, %f]' %(min(P[:,1]), max(P[:,1]))
319                print '  Interpolated quantities (over all timesteps):'
320                for name in self.quantities:
321                    q = self.values[name][:].flat                   
322                    print '    %s at interpolation points in [%f, %f]'\
323                          %(name, min(q), max(q))               
324               
325               
326               
327
328                print '------------------------------------------------'
329        else:
330            msg = 'File_function_NetCDF must be invoked with '
331            msg += 'a list of interpolation points'
332            raise msg
333
334    def __repr__(self):
335        return 'File function'
336
337    def __call__(self, t, x=None, y=None, point_id = None):
338        """Evaluate f(t, point_id)
339       
340        Inputs:
341          t: time - Model time (tau = domain.starttime-self.starttime+t) 
342                    must lie within existing timesteps   
343          point_id: index of one of the preprocessed points.
344                    If point_id is None all preprocessed points are computed
345
346          FIXME: point_id could also be a slice     
347          FIXME: One could allow arbitrary x, y coordinates
348          (requires computation of a new interpolator)
349          Maybe not,.,.
350          FIXME: What if x and y are vectors?
351        """
352
353        from math import pi, cos, sin, sqrt
354        from Numeric import zeros, Float
355
356
357        if point_id is None:
358            msg = 'NetCDF File function needs a point_id when invoked'
359            raise msg
360
361
362        #Find time tau such that
363        #
364        # domain.starttime + t == self.starttime + tau
365       
366        if self.domain is not None:
367            tau = self.domain.starttime-self.starttime+t
368        else:
369            #print 'DOMAIN IS NONE!!!!!!!!!'
370            tau = t
371
372        #print 'D start', self.domain.starttime
373        #print 'S start', self.starttime
374        #print 't', t
375        #print 'tau', tau             
376               
377        msg = 'Time interval derived from file %s [%s:%s]'\
378              %(self.filename, self.T[0], self.T[1])
379        msg += ' does not match model time: %s\n' %tau
380        msg += 'Domain says its starttime == %f' %(self.domain.starttime)
381
382        if tau < self.T[0]: raise msg
383        if tau > self.T[-1]: raise msg       
384
385
386        oldindex = self.index #Time index
387
388        #Find time slot
389        while tau > self.T[self.index]: self.index += 1
390        while tau < self.T[self.index]: self.index -= 1
391
392
393        if tau == self.T[self.index]:
394            #Protect against case where tau == T[-1] (last time)
395            # - also works in general when tau == T[i]
396            ratio = 0 
397        else:
398            #t is now between index and index+1           
399            ratio = (tau - self.T[self.index])/\
400                    (self.T[self.index+1] - self.T[self.index])
401
402
403
404               
405        #Compute interpolated values
406        q = zeros( len(self.quantities), Float)
407
408        for i, name in enumerate(self.quantities):
409            Q = self.values[name]   
410
411            if ratio > 0:
412                q[i] = Q[self.index, point_id] +\
413                       ratio*(Q[self.index+1, point_id] -\
414                              Q[self.index, point_id])
415            else:
416                q[i] = Q[self.index, point_id]
417
418
419
420        #Return vector of interpolated values
421        return q
422               
423             
424class File_function_ASCII:
425    """Read time series from file and return a callable object:
426    f(t,x,y)
427    which will return interpolated values based on the input file.
428
429    The file format is assumed to be either two fields separated by a comma:
430
431        time [DD/MM/YY hh:mm:ss], value0 value1 value2 ...
432
433    or four comma separated fields
434
435        time [DD/MM/YY hh:mm:ss], x, y, value0 value1 value2 ...
436
437    In either case, the callable object will return a tuple of
438    interpolated values, one each value stated in the file.
439
440   
441    E.g
442
443      31/08/04 00:00:00, 1.328223 0 0
444      31/08/04 00:15:00, 1.292912 0 0
445
446    will provide a time dependent function f(t,x=None,y=None),
447    ignoring x and y, which returns three values per call.
448
449   
450    NOTE: At this stage the function is assumed to depend on
451    time only, i.e  no spatial dependency!!!!!
452    When that is needed we can use the least_squares interpolation.
453   
454    #FIXME: This should work with netcdf (e.g. sww) and thus render the
455    #spatio-temporal boundary condition in shallow water fully general
456   
457    #FIXME: Specified quantites not used here -
458    #always return whatever is in the file
459    """
460
461   
462    def __init__(self, filename, domain=None, quantities = None, interpolation_points=None):
463        """Initialise callable object from a file.
464        See docstring for class File_function
465
466        If domain is specified, model time (domain,starttime)
467        will be checked and possibly modified
468
469        All times are assumed to be in UTC
470        """
471
472        import time, calendar
473        from Numeric import array
474        from config import time_format
475
476        fid = open(filename)
477        line = fid.readline()
478        fid.close()
479   
480        fields = line.split(',')
481        msg = 'File %s must have the format date, value0 value1 value2 ...'
482        msg += ' or date, x, y, value0 value1 value2 ...'
483        mode = len(fields)
484        assert mode in [2,4], msg
485
486        try:
487            starttime = calendar.timegm(time.strptime(fields[0], time_format))
488        except ValueError:   
489            msg = 'First field in file %s must be' %filename
490            msg += ' date-time with format %s.\n' %time_format
491            msg += 'I got %s instead.' %fields[0] 
492            raise msg
493
494
495        #Split values
496        values = []
497        for value in fields[mode-1].split():
498            values.append(float(value))
499
500        q = array(values)
501       
502        msg = 'ERROR: File must contain at least one independent value'
503        assert len(q.shape) == 1, msg
504           
505        self.number_of_values = len(q)
506
507        self.filename = filename
508        self.starttime = starttime
509        self.domain = domain
510
511        if domain is not None:
512            msg = 'WARNING: Start time as specified in domain (%s)'\
513                  %domain.starttime
514            msg += ' is earlier than the starttime of file %s: %s.'\
515                     %(self.filename, self.starttime)
516            msg += 'Modifying starttime accordingly.'
517            if self.starttime > domain.starttime:
518                #FIXME: Print depending on some verbosity setting
519                ##if verbose: print msg
520                domain.starttime = self.starttime #Modifying model time
521       
522            #if domain.starttime is None:
523            #    domain.starttime = self.starttime
524            #else:
525            #    msg = 'WARNING: Start time as specified in domain (%s)'\
526            #          %domain.starttime
527            #    msg += ' is earlier than the starttime of file %s: %s.'\
528            #             %(self.filename, self.starttime)
529            #    msg += 'Modifying starttime accordingly.'
530            #    if self.starttime > domain.starttime:
531            #        #FIXME: Print depending on some verbosity setting
532            #        #print msg
533            #        domain.starttime = self.starttime #Modifying model time
534
535        if mode == 2:       
536            self.read_times() #Now read all times in.
537        else:
538            raise 'x,y dependency not yet implemented'
539
540       
541    def read_times(self):
542        """Read time and values
543        """
544        from Numeric import zeros, Float, alltrue
545        from config import time_format
546        import time, calendar
547       
548        fid = open(self.filename)       
549        lines = fid.readlines()
550        fid.close()
551       
552        N = len(lines)
553        d = self.number_of_values
554
555        T = zeros(N, Float)       #Time
556        Q = zeros((N, d), Float)  #Values
557       
558        for i, line in enumerate(lines):
559            fields = line.split(',')
560            realtime = calendar.timegm(time.strptime(fields[0], time_format))
561
562            T[i] = realtime - self.starttime
563
564            for j, value in enumerate(fields[1].split()):
565                Q[i, j] = float(value)
566
567        msg = 'File %s must list time as a monotonuosly ' %self.filename
568        msg += 'increasing sequence'
569        assert alltrue( T[1:] - T[:-1] > 0 ), msg
570
571        self.T = T     #Time
572        self.Q = Q     #Values
573        self.index = 0 #Initial index
574
575       
576    def __repr__(self):
577        return 'File function'
578
579    def __call__(self, t, x=None, y=None, point_id=None):
580        """Evaluate f(t,x,y)
581
582        FIXME: x, y dependency not yet implemented except that
583        result is a vector of same length as x and y
584        FIXME: Naaaa
585       
586        FIXME: Rethink semantics when x,y are vectors.
587        """
588
589        from math import pi, cos, sin, sqrt
590       
591
592        #Find time tau such that
593        #
594        # domain.starttime + t == self.starttime + tau
595       
596        if self.domain is not None:
597            tau = self.domain.starttime-self.starttime+t
598        else:
599            tau = t
600       
601               
602        msg = 'Time interval derived from file %s (%s:%s) does not match model time: %s'\
603              %(self.filename, self.T[0], self.T[1], tau)
604        if tau < self.T[0]: raise msg
605        if tau > self.T[-1]: raise msg       
606
607        oldindex = self.index
608
609        #Find slot
610        while tau > self.T[self.index]: self.index += 1
611        while tau < self.T[self.index]: self.index -= 1
612
613        #t is now between index and index+1
614        ratio = (tau - self.T[self.index])/\
615                (self.T[self.index+1] - self.T[self.index])
616
617        #Compute interpolated values
618        q = self.Q[self.index,:] +\
619            ratio*(self.Q[self.index+1,:] - self.Q[self.index,:]) 
620
621        #Return vector of interpolated values
622        if x == None and y == None:
623            return q
624        else:
625            try:
626                N = len(x)
627            except:
628                return q
629            else:
630                from Numeric import ones, Float
631                #x is a vector - Create one constant column for each value
632                N = len(x)
633                assert len(y) == N, 'x and y must have same length'
634             
635                res = []
636                for col in q:
637                    res.append(col*ones(N, Float))
638           
639                return res
640           
641           
642#FIXME: TEMP       
643class File_function_copy:
644    """Read time series from file and return a callable object:
645    f(t,x,y)
646    which will return interpolated values based on the input file.
647
648    The file format is assumed to be either two fields separated by a comma:
649
650        time [DD/MM/YY hh:mm:ss], value0 value1 value2 ...
651
652    or four comma separated fields
653
654        time [DD/MM/YY hh:mm:ss], x, y, value0 value1 value2 ...
655
656    In either case, the callable object will return a tuple of
657    interpolated values, one each value stated in the file.
658
659   
660    E.g
661
662      31/08/04 00:00:00, 1.328223 0 0
663      31/08/04 00:15:00, 1.292912 0 0
664
665    will provide a time dependent function f(t,x=None,y=None),
666    ignoring x and y, which returns three values per call.
667
668   
669    NOTE: At this stage the function is assumed to depend on
670    time only, i.e  no spatial dependency!!!!!
671    When that is needed we can use the least_squares interpolation.
672   
673    #FIXME: This should work with netcdf (e.g. sww) and thus render the
674    #spatio-temporal boundary condition in shallow water fully general
675    """
676
677   
678    def __init__(self, filename, domain=None):
679        """Initialise callable object from a file.
680        See docstring for class File_function
681
682        If domain is specified, model time (domain,starttime)
683        will be checked and possibly modified
684
685        All times are assumed to be in UTC
686        """
687
688        import time, calendar
689        from Numeric import array
690        from config import time_format
691
692        assert type(filename) == type(''),\
693               'First argument to File_function must be a string'
694
695
696        try:
697            fid = open(filename)
698        except Exception, e:
699            msg = 'File "%s" could not be opened: Error="%s"'\
700                  %(filename, e)
701            raise msg
702
703
704        line = fid.readline()
705        fid.close()
706        fields = line.split(',')
707        msg = 'File %s must have the format date, value0 value1 value2 ...'
708        msg += ' or date, x, y, value0 value1 value2 ...'
709        mode = len(fields)
710        assert mode in [2,4], msg
711
712        try:
713            starttime = calendar.timegm(time.strptime(fields[0], time_format))
714        except ValueError:   
715            msg = 'First field in file %s must be' %filename
716            msg += ' date-time with format %s.\n' %time_format
717            msg += 'I got %s instead.' %fields[0] 
718            raise msg
719
720
721        #Split values
722        values = []
723        for value in fields[mode-1].split():
724            values.append(float(value))
725
726        q = array(values)
727       
728        msg = 'ERROR: File must contain at least one independent value'
729        assert len(q.shape) == 1, msg
730           
731        self.number_of_values = len(q)
732
733        self.filename = filename
734        self.starttime = starttime
735        self.domain = domain
736
737        if domain is not None:
738            if domain.starttime is None:
739                domain.starttime = self.starttime
740            else:
741                msg = 'WARNING: Start time as specified in domain (%s)'\
742                      %domain.starttime
743                msg += ' is earlier than the starttime of file %s: %s.'\
744                         %(self.filename, self.starttime)
745                msg += 'Modifying starttime accordingly.'
746                if self.starttime > domain.starttime:
747                    #FIXME: Print depending on some verbosity setting
748                    #print msg
749                    domain.starttime = self.starttime #Modifying model time
750
751        if mode == 2:       
752            self.read_times() #Now read all times in.
753        else:
754            raise 'x,y dependency not yet implemented'
755
756       
757    def read_times(self):
758        """Read time and values
759        """
760        from Numeric import zeros, Float, alltrue
761        from config import time_format
762        import time, calendar
763       
764        fid = open(self.filename)       
765        lines = fid.readlines()
766        fid.close()
767       
768        N = len(lines)
769        d = self.number_of_values
770
771        T = zeros(N, Float)       #Time
772        Q = zeros((N, d), Float)  #Values
773       
774        for i, line in enumerate(lines):
775            fields = line.split(',')
776            realtime = calendar.timegm(time.strptime(fields[0], time_format))
777
778            T[i] = realtime - self.starttime
779
780            for j, value in enumerate(fields[1].split()):
781                Q[i, j] = float(value)
782
783        msg = 'File %s must list time as a monotonuosly ' %self.filename
784        msg += 'increasing sequence'
785        assert alltrue( T[1:] - T[:-1] > 0 ), msg
786
787        self.T = T     #Time
788        self.Q = Q     #Values
789        self.index = 0 #Initial index
790
791       
792    def __repr__(self):
793        return 'File function'
794
795       
796
797    def __call__(self, t, x=None, y=None):
798        """Evaluate f(t,x,y)
799
800        FIXME: x, y dependency not yet implemented except that
801        result is a vector of same length as x and y
802        FIXME: Naaaa
803        """
804
805        from math import pi, cos, sin, sqrt
806       
807
808        #Find time tau such that
809        #
810        # domain.starttime + t == self.starttime + tau
811       
812        if self.domain is not None:
813            tau = self.domain.starttime-self.starttime+t
814        else:
815            tau = t
816       
817               
818        msg = 'Time interval derived from file %s (%s:%s) does not match model time: %s'\
819              %(self.filename, self.T[0], self.T[1], tau)
820        if tau < self.T[0]: raise msg
821        if tau > self.T[-1]: raise msg       
822
823        oldindex = self.index
824
825        #Find slot
826        while tau > self.T[self.index]: self.index += 1
827        while tau < self.T[self.index]: self.index -= 1
828
829        #t is now between index and index+1
830        ratio = (tau - self.T[self.index])/\
831                (self.T[self.index+1] - self.T[self.index])
832
833        #Compute interpolated values
834        q = self.Q[self.index,:] +\
835            ratio*(self.Q[self.index+1,:] - self.Q[self.index,:]) 
836
837        #Return vector of interpolated values
838        if x == None and y == None:
839            return q
840        else:
841            try:
842                N = len(x)
843            except:
844                return q
845            else:
846                from Numeric import ones, Float
847                #x is a vector - Create one constant column for each value
848                N = len(x)
849                assert len(y) == N, 'x and y must have same length'
850             
851                res = []
852                for col in q:
853                    res.append(col*ones(N, Float))
854           
855                return res
856           
857
858def read_xya(filename, format = 'netcdf'):
859    """Read simple xya file, possibly with a header in the first line, just
860    x y [attributes]
861    separated by whitespace
862
863    Format can be either ASCII or NetCDF
864   
865    Return list of points, list of attributes and
866    attribute names if present otherwise None
867    """
868    #FIXME: Probably obsoleted by similar function in load_ASCII
869   
870    from Scientific.IO.NetCDF import NetCDFFile
871
872    if format.lower() == 'netcdf': 
873        #Get NetCDF
874       
875        fid = NetCDFFile(filename, 'r') 
876     
877        # Get the variables
878        points = fid.variables['points']
879        keys = fid.variables.keys()
880        attributes = {}
881        for key in keys:
882            attributes[key] = fid.variables[key]
883        #Don't close - arrays are needed outside this function,
884        #alternatively take a copy here
885    else:   
886        #Read as ASCII file assuming that it is separated by whitespace
887        fid = open(filename)
888        lines = fid.readlines()
889        fid.close()
890
891        #Check if there is a header line
892        fields = lines[0].strip().split()
893        try:
894            float(fields[0])
895        except:
896            #This must be a header line
897            attribute_names = fields
898            lines = lines[1:]
899        else:
900            attribute_names = ['elevation']  #HACK
901
902        attributes = {}   
903        for key in attribute_names:
904            attributes[key] = []
905
906        points = []
907        for line in lines:
908            fields = line.strip().split()
909            points.append( (float(fields[0]), float(fields[1])) )
910            for i, key in enumerate(attribute_names):
911                attributes[key].append( float(fields[2+i]) )
912       
913    return points, attributes
914   
915
916#####################################
917#POLYGON STUFF
918#
919#FIXME: All these should be put into new module polygon.py
920
921
922             
923#Redefine these to use separate_by_polygon which will put all inside indices
924#in the first part of the indices array and outside indices in the last
925
926def inside_polygon(points, polygon, closed = True, verbose = False):
927    """See separate_points_by_polygon for documentation
928    """
929   
930    from Numeric import array, Float, reshape   
931   
932    if verbose: print 'Checking input to inside_polygon'   
933    try:
934        points = array(points).astype(Float)       
935    except:
936        msg = 'Points could not be converted to Numeric array'
937        raise msg       
938       
939    try:
940        polygon = array(polygon).astype(Float)             
941    except:
942        msg = 'Polygon could not be converted to Numeric array'
943        raise msg               
944   
945   
946   
947    if len(points.shape) == 1:
948        one_point = True
949        points = reshape(points, (1,2))
950    else:       
951        one_point = False
952
953    indices, count = separate_points_by_polygon(points, polygon, 
954                                                closed, verbose)     
955   
956    if one_point:
957        return count == 1
958    else:
959        return indices[:count]   
960       
961def outside_polygon(points, polygon, closed = True, verbose = False):
962    """See separate_points_by_polygon for documentation
963    """
964   
965    from Numeric import array, Float, reshape   
966   
967    if verbose: print 'Checking input to outside_polygon'   
968    try:
969        points = array(points).astype(Float)       
970    except:
971        msg = 'Points could not be converted to Numeric array'
972        raise msg       
973       
974    try:
975        polygon = array(polygon).astype(Float)             
976    except:
977        msg = 'Polygon could not be converted to Numeric array'
978        raise msg               
979   
980   
981   
982    if len(points.shape) == 1:
983        one_point = True
984        points = reshape(points, (1,2))
985    else:       
986        one_point = False
987
988    indices, count = separate_points_by_polygon(points, polygon, 
989                                                closed, verbose)     
990   
991    if one_point:
992        return count != 1
993    else:
994        return indices[count:][::-1]  #return reversed   
995       
996
997def separate_points_by_polygon(points, polygon, 
998                               closed = True, verbose = False):
999    """Determine whether points are inside or outside a polygon
1000   
1001    Input:
1002       points - Tuple of (x, y) coordinates, or list of tuples
1003       polygon - list of vertices of polygon
1004       closed - (optional) determine whether points on boundary should be
1005       regarded as belonging to the polygon (closed = True)
1006       or not (closed = False)
1007   
1008    Outputs:
1009       indices: array of same length as points with indices of points falling
1010       inside the polygon listed from the beginning and indices of points
1011       falling outside listed from the end.
1012       
1013       count: count of points falling inside the polygon
1014       
1015       The indices of points inside are obtained as indices[:count]
1016       The indices of points outside are obtained as indices[count:]       
1017       
1018       
1019    Examples:
1020       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square
1021       
1022       separate_points_by_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)
1023       will return the indices [0, 2, 1] and count == 2 as only the first
1024       and the last point are inside the unit square
1025       
1026    Remarks:
1027       The vertices may be listed clockwise or counterclockwise and
1028       the first point may optionally be repeated.   
1029       Polygons do not need to be convex.
1030       Polygons can have holes in them and points inside a hole is
1031       regarded as being outside the polygon.         
1032               
1033    Algorithm is based on work by Darel Finley,
1034    http://www.alienryderflex.com/polygon/
1035   
1036    """   
1037
1038    from Numeric import array, Float, reshape, Int, zeros
1039   
1040   
1041    #Input checks
1042    try:
1043        points = array(points).astype(Float)       
1044    except:
1045        msg = 'Points could not be converted to Numeric array'
1046        raise msg       
1047       
1048    try:
1049        polygon = array(polygon).astype(Float)             
1050    except:
1051        msg = 'Polygon could not be converted to Numeric array'
1052        raise msg               
1053
1054    assert len(polygon.shape) == 2,\
1055       'Polygon array must be a 2d array of vertices'
1056
1057    assert polygon.shape[1] == 2,\
1058       'Polygon array must have two columns'
1059
1060    assert len(points.shape) == 2,\
1061       'Points array must be a 2d array'
1062       
1063    assert points.shape[1] == 2,\
1064       'Points array must have two columns'
1065       
1066    N = polygon.shape[0] #Number of vertices in polygon
1067    M = points.shape[0]  #Number of points
1068   
1069    px = polygon[:,0]
1070    py = polygon[:,1]   
1071
1072    #Used for an optimisation when points are far away from polygon
1073    minpx = min(px); maxpx = max(px)
1074    minpy = min(py); maxpy = max(py)   
1075
1076
1077    #Begin main loop
1078    indices = zeros(M, Int)
1079   
1080    inside_index = 0    #Keep track of points inside
1081    outside_index = M-1 #Keep track of points outside (starting from end)
1082   
1083    for k in range(M):
1084
1085        if verbose:
1086            if k %((M+10)/10)==0: print 'Doing %d of %d' %(k, M)
1087       
1088        #for each point   
1089        x = points[k, 0]
1090        y = points[k, 1]       
1091
1092        inside = False
1093
1094        if not x > maxpx or x < minpx or y > maxpy or y < minpy: 
1095            #Check polygon
1096            for i in range(N):
1097                j = (i+1)%N
1098               
1099                #Check for case where point is contained in line segment       
1100                if point_on_line(x, y, px[i], py[i], px[j], py[j]):
1101                    if closed:
1102                        inside = True
1103                    else:       
1104                        inside = False
1105                    break
1106                else:   
1107                    #Check if truly inside polygon                 
1108                    if py[i] < y and py[j] >= y or\
1109                       py[j] < y and py[i] >= y:
1110                        if px[i] + (y-py[i])/(py[j]-py[i])*(px[j]-px[i]) < x:
1111                            inside = not inside
1112       
1113        if inside: 
1114            indices[inside_index] = k
1115            inside_index += 1
1116        else:
1117            indices[outside_index] = k
1118            outside_index -= 1   
1119           
1120    return indices, inside_index
1121
1122
1123def separate_points_by_polygon_c(points, polygon, 
1124                                 closed = True, verbose = False):
1125    """Determine whether points are inside or outside a polygon
1126
1127    C-wrapper
1128    """   
1129
1130    from Numeric import array, Float, reshape, zeros, Int
1131   
1132
1133    if verbose: print 'Checking input to separate_points_by_polygon'   
1134    #Input checks
1135    try:
1136        points = array(points).astype(Float)       
1137    except:
1138        msg = 'Points could not be converted to Numeric array'
1139        raise msg       
1140       
1141    try:
1142        polygon = array(polygon).astype(Float)             
1143    except:
1144        msg = 'Polygon could not be converted to Numeric array'
1145        raise msg               
1146
1147    assert len(polygon.shape) == 2,\
1148       'Polygon array must be a 2d array of vertices'
1149
1150    assert polygon.shape[1] == 2,\
1151       'Polygon array must have two columns'
1152
1153    assert len(points.shape) == 2,\
1154       'Points array must be a 2d array'
1155       
1156    assert points.shape[1] == 2,\
1157       'Points array must have two columns'
1158       
1159    N = polygon.shape[0] #Number of vertices in polygon
1160    M = points.shape[0]  #Number of points
1161
1162    from util_ext import separate_points_by_polygon
1163
1164    if verbose: print 'Allocating array for indices'
1165
1166    indices = zeros( M, Int )
1167
1168    if verbose: print 'Calling C-version of inside poly' 
1169    count = separate_points_by_polygon(points, polygon, indices,
1170                                       int(closed), int(verbose))
1171
1172    return indices, count
1173
1174
1175
1176class Polygon_function:
1177    """Create callable object f: x,y -> z, where a,y,z are vectors and
1178    where f will return different values depending on whether x,y belongs
1179    to specified polygons.
1180   
1181    To instantiate:
1182     
1183       Polygon_function(polygons)
1184       
1185    where polygons is a list of tuples of the form
1186   
1187      [ (P0, v0), (P1, v1), ...]
1188     
1189      with Pi being lists of vertices defining polygons and vi either
1190      constants or functions of x,y to be applied to points with the polygon.
1191     
1192    The function takes an optional argument, default which is the value
1193    (or function) to used for points not belonging to any polygon.
1194    For example:
1195   
1196       Polygon_function(polygons, default = 0.03)       
1197     
1198    If omitted the default value will be 0.0 
1199   
1200    Note: If two polygons overlap, the one last in the list takes precedence
1201
1202    """
1203   
1204    def __init__(self, regions, default = 0.0):
1205       
1206        try:
1207            len(regions)
1208        except:
1209            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons           
1210            raise msg
1211
1212
1213        T = regions[0]                                   
1214        try:
1215            a = len(T)
1216        except:   
1217            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons           
1218            raise msg   
1219                   
1220        assert a == 2, 'Must have two component each: %s' %T       
1221
1222        self.regions = regions             
1223        self.default = default
1224         
1225         
1226    def __call__(self, x, y):
1227        from util import inside_polygon
1228        from Numeric import ones, Float, concatenate, array, reshape, choose
1229       
1230        x = array(x).astype(Float)
1231        y = array(y).astype(Float)     
1232       
1233        N = len(x)
1234        assert len(y) == N
1235       
1236        points = concatenate( (reshape(x, (N, 1)), 
1237                               reshape(y, (N, 1))), axis=1 )
1238
1239        if callable(self.default):
1240            z = self.default(x,y)
1241        else:                   
1242            z = ones(N, Float) * self.default
1243           
1244        for polygon, value in self.regions:
1245            indices = inside_polygon(points, polygon)
1246           
1247            #FIXME: This needs to be vectorised
1248            if callable(value):
1249                for i in indices:
1250                    xx = array([x[i]])
1251                    yy = array([y[i]])
1252                    z[i] = value(xx, yy)[0]
1253            else:   
1254                for i in indices:
1255                    z[i] = value
1256
1257        return z                 
1258
1259def read_polygon(filename):
1260    """Read points assumed to form a polygon
1261       There must be exactly two numbers in each line
1262    """             
1263   
1264    #Get polygon
1265    fid = open(filename)
1266    lines = fid.readlines()
1267    fid.close()
1268    polygon = []
1269    for line in lines:
1270        fields = line.split(',')
1271        polygon.append( [float(fields[0]), float(fields[1])] )
1272
1273    return polygon     
1274   
1275def populate_polygon(polygon, number_of_points, seed = None):
1276    """Populate given polygon with uniformly distributed points.
1277
1278    Input:
1279       polygon - list of vertices of polygon
1280       number_of_points - (optional) number of points
1281       seed - seed for random number generator (default=None)
1282       
1283    Output:
1284       points - list of points inside polygon
1285       
1286    Examples:
1287       populate_polygon( [[0,0], [1,0], [1,1], [0,1]], 5 )
1288       will return five randomly selected points inside the unit square
1289    """
1290
1291    from random import uniform, seed
1292
1293    seed(seed)
1294
1295    points = []
1296
1297    #Find outer extent of polygon
1298    max_x = min_x = polygon[0][0]
1299    max_y = min_y = polygon[0][1]
1300    for point in polygon[1:]:
1301        x = point[0] 
1302        if x > max_x: max_x = x
1303        if x < min_x: min_x = x           
1304        y = point[1] 
1305        if y > max_y: max_y = y
1306        if y < min_y: min_y = y           
1307
1308
1309    while len(points) < number_of_points:     
1310        x = uniform(min_x, max_x)
1311        y = uniform(min_y, max_y)       
1312
1313        if inside_polygon( [x,y], polygon ):
1314            points.append([x,y])
1315
1316    return points
1317
1318####################################################################
1319#Python versions of function that are also implemented in util_gateway.c
1320#
1321
1322def gradient_python(x0, y0, x1, y1, x2, y2, q0, q1, q2):
1323    """
1324    """
1325   
1326    det = (y2-y0)*(x1-x0) - (y1-y0)*(x2-x0)           
1327    a = (y2-y0)*(q1-q0) - (y1-y0)*(q2-q0)
1328    a /= det
1329
1330    b = (x1-x0)*(q2-q0) - (x2-x0)*(q1-q0)
1331    b /= det           
1332
1333    return a, b
1334
1335
1336
1337##############################################
1338#Initialise module
1339
1340import compile
1341if compile.can_use_C_extension('util_ext.c'):
1342    from util_ext import gradient, point_on_line
1343    separate_points_by_polygon = separate_points_by_polygon_c
1344else:
1345    gradient = gradient_python
1346
1347
1348if __name__ == "__main__":
1349    pass
1350
1351   
1352   
1353   
1354   
1355
1356#OBSOLETED STUFF   
1357def inside_polygon_old(point, polygon, closed = True, verbose = False):
1358    #FIXME Obsoleted
1359    """Determine whether points are inside or outside a polygon
1360   
1361    Input:
1362       point - Tuple of (x, y) coordinates, or list of tuples
1363       polygon - list of vertices of polygon
1364       closed - (optional) determine whether points on boundary should be
1365       regarded as belonging to the polygon (closed = True)
1366       or not (closed = False)
1367   
1368    Output:
1369       If one point is considered, True or False is returned.
1370       If multiple points are passed in, the function returns indices
1371       of those points that fall inside the polygon     
1372       
1373    Examples:
1374       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square
1375       inside_polygon( [0.5, 0.5], U)
1376       will evaluate to True as the point 0.5, 0.5 is inside the unit square
1377       
1378       inside_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)
1379       will return the indices [0, 2] as only the first and the last point
1380       is inside the unit square
1381       
1382    Remarks:
1383       The vertices may be listed clockwise or counterclockwise and
1384       the first point may optionally be repeated.   
1385       Polygons do not need to be convex.
1386       Polygons can have holes in them and points inside a hole is
1387       regarded as being outside the polygon.         
1388               
1389       
1390    Algorithm is based on work by Darel Finley,
1391    http://www.alienryderflex.com/polygon/
1392   
1393    """   
1394
1395    from Numeric import array, Float, reshape
1396   
1397   
1398    #Input checks
1399    try:
1400        point = array(point).astype(Float)         
1401    except:
1402        msg = 'Point %s could not be converted to Numeric array' %point
1403        raise msg       
1404       
1405    try:
1406        polygon = array(polygon).astype(Float)             
1407    except:
1408        msg = 'Polygon %s could not be converted to Numeric array' %polygon
1409        raise msg               
1410   
1411    assert len(polygon.shape) == 2,\
1412       'Polygon array must be a 2d array of vertices: %s' %polygon
1413
1414       
1415    assert polygon.shape[1] == 2,\
1416       'Polygon array must have two columns: %s' %polygon   
1417
1418
1419    if len(point.shape) == 1:
1420        one_point = True
1421        points = reshape(point, (1,2))
1422    else:       
1423        one_point = False
1424        points = point
1425
1426    N = polygon.shape[0] #Number of vertices in polygon
1427    M = points.shape[0]  #Number of points
1428   
1429    px = polygon[:,0]
1430    py = polygon[:,1]   
1431
1432    #Used for an optimisation when points are far away from polygon
1433    minpx = min(px); maxpx = max(px)
1434    minpy = min(py); maxpy = max(py)   
1435
1436
1437    #Begin main loop (FIXME: It'd be crunchy to have this written in C:-)
1438    indices = []
1439    for k in range(M):
1440
1441        if verbose:
1442            if k %((M+10)/10)==0: print 'Doing %d of %d' %(k, M)
1443       
1444        #for each point   
1445        x = points[k, 0]
1446        y = points[k, 1]       
1447
1448        inside = False
1449
1450        #Optimisation
1451        if x > maxpx or x < minpx: continue
1452        if y > maxpy or y < minpy: continue       
1453
1454        #Check polygon
1455        for i in range(N):
1456            j = (i+1)%N
1457           
1458            #Check for case where point is contained in line segment   
1459            if point_on_line(x, y, px[i], py[i], px[j], py[j]):
1460                if closed:
1461                    inside = True
1462                else:       
1463                    inside = False
1464                break
1465            else:       
1466                #Check if truly inside polygon             
1467                if py[i] < y and py[j] >= y or\
1468                   py[j] < y and py[i] >= y:
1469                    if px[i] + (y-py[i])/(py[j]-py[i])*(px[j]-px[i]) < x:
1470                        inside = not inside
1471                   
1472        if inside: indices.append(k)
1473
1474    if one_point: 
1475        return inside
1476    else:
1477        return indices
1478             
1479
1480#def remove_from(A, B):
1481#    """Assume that A
1482#    """
1483#    from Numeric import search_sorted##
1484#
1485#    ind = search_sorted(A, B)
1486
1487   
1488
1489def outside_polygon_old(point, polygon, closed = True, verbose = False):
1490    #OBSOLETED
1491    """Determine whether points are outside a polygon
1492   
1493    Input:
1494       point - Tuple of (x, y) coordinates, or list of tuples
1495       polygon - list of vertices of polygon
1496       closed - (optional) determine whether points on boundary should be
1497       regarded as belonging to the polygon (closed = True)
1498       or not (closed = False)
1499   
1500    Output:
1501       If one point is considered, True or False is returned.
1502       If multiple points are passed in, the function returns indices
1503       of those points that fall outside the polygon     
1504       
1505    Examples:
1506       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square   
1507       outside_polygon( [0.5, 0.5], U )
1508       will evaluate to False as the point 0.5, 0.5 is inside the unit square
1509
1510       ouside_polygon( [1.5, 0.5], U )
1511       will evaluate to True as the point 1.5, 0.5 is outside the unit square
1512       
1513       outside_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U )
1514       will return the indices [1] as only the first and the last point
1515       is inside the unit square
1516    """
1517
1518    #FIXME: This is too slow
1519   
1520    res  = inside_polygon(point, polygon, closed, verbose)
1521
1522    if res is True or res is False:
1523        return not res
1524
1525    #Now invert indices
1526    from Numeric import arrayrange, compress
1527    outside_indices = arrayrange(len(point))
1528    for i in res:
1529        outside_indices = compress(outside_indices != i, outside_indices)
1530    return outside_indices
1531
1532def inside_polygon_c(point, polygon, closed = True, verbose = False):
1533    #FIXME: Obsolete
1534    """Determine whether points are inside or outside a polygon
1535
1536    C-wrapper
1537    """   
1538
1539    from Numeric import array, Float, reshape, zeros, Int
1540   
1541
1542    if verbose: print 'Checking input to inside_polygon'   
1543    #Input checks
1544    try:
1545        point = array(point).astype(Float)         
1546    except:
1547        msg = 'Point %s could not be converted to Numeric array' %point
1548        raise msg       
1549       
1550    try:
1551        polygon = array(polygon).astype(Float)             
1552    except:
1553        msg = 'Polygon %s could not be converted to Numeric array' %polygon
1554        raise msg               
1555   
1556    assert len(polygon.shape) == 2,\
1557       'Polygon array must be a 2d array of vertices: %s' %polygon
1558
1559       
1560    assert polygon.shape[1] == 2,\
1561       'Polygon array must have two columns: %s' %polygon   
1562
1563
1564    if len(point.shape) == 1:
1565        one_point = True
1566        points = reshape(point, (1,2))
1567    else:       
1568        one_point = False
1569        points = point
1570
1571    from util_ext import inside_polygon
1572
1573    if verbose: print 'Allocating array for indices'
1574
1575    indices = zeros( points.shape[0], Int )
1576
1577    if verbose: print 'Calling C-version of inside poly' 
1578    count = inside_polygon(points, polygon, indices,
1579                           int(closed), int(verbose))
1580
1581    if one_point:
1582        return count == 1 #Return True if the point was inside
1583    else:
1584        if verbose: print 'Got %d points' %count
1585       
1586        return indices[:count]   #Return those indices that were inside
1587
Note: See TracBrowser for help on using the repository browser.