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

Last change on this file since 1454 was 1387, checked in by steve, 20 years ago

Need to look at uint test for inscribed circle

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