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

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

Rewrote inside and outside polygon in terms of new separate_points_by_polygon in order to make outside_polygon as fast as inside_polygon.

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