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

Last change on this file since 1207 was 1207, checked in by duncan, 19 years ago

added function to check if a tsh/msh file lloks good

File size: 49.1 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    """
1236   
1237    def __init__(self, regions, default = 0.0):
1238       
1239        try:
1240            len(regions)
1241        except:
1242            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons           
1243            raise msg
1244
1245
1246        T = regions[0]                                   
1247        try:
1248            a = len(T)
1249        except:   
1250            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons           
1251            raise msg   
1252                   
1253        assert a == 2, 'Must have two component each: %s' %T       
1254
1255        self.regions = regions             
1256        self.default = default
1257         
1258         
1259    def __call__(self, x, y):
1260        from util import inside_polygon
1261        from Numeric import ones, Float, concatenate, array, reshape, choose
1262       
1263        x = array(x).astype(Float)
1264        y = array(y).astype(Float)     
1265       
1266        N = len(x)
1267        assert len(y) == N
1268       
1269        points = concatenate( (reshape(x, (N, 1)), 
1270                               reshape(y, (N, 1))), axis=1 )
1271
1272        if callable(self.default):
1273            z = self.default(x,y)
1274        else:                   
1275            z = ones(N, Float) * self.default
1276           
1277        for polygon, value in self.regions:
1278            indices = inside_polygon(points, polygon)
1279           
1280            #FIXME: This needs to be vectorised
1281            if callable(value):
1282                for i in indices:
1283                    xx = array([x[i]])
1284                    yy = array([y[i]])
1285                    z[i] = value(xx, yy)[0]
1286            else:   
1287                for i in indices:
1288                    z[i] = value
1289
1290        return z                 
1291
1292def read_polygon(filename):
1293    """Read points assumed to form a polygon
1294       There must be exactly two numbers in each line
1295    """             
1296   
1297    #Get polygon
1298    fid = open(filename)
1299    lines = fid.readlines()
1300    fid.close()
1301    polygon = []
1302    for line in lines:
1303        fields = line.split(',')
1304        polygon.append( [float(fields[0]), float(fields[1])] )
1305
1306    return polygon     
1307   
1308def populate_polygon(polygon, number_of_points, seed = None):
1309    """Populate given polygon with uniformly distributed points.
1310
1311    Input:
1312       polygon - list of vertices of polygon
1313       number_of_points - (optional) number of points
1314       seed - seed for random number generator (default=None)
1315       
1316    Output:
1317       points - list of points inside polygon
1318       
1319    Examples:
1320       populate_polygon( [[0,0], [1,0], [1,1], [0,1]], 5 )
1321       will return five randomly selected points inside the unit square
1322    """
1323
1324    from random import uniform, seed
1325
1326    seed(seed)
1327
1328    points = []
1329
1330    #Find outer extent of polygon
1331    max_x = min_x = polygon[0][0]
1332    max_y = min_y = polygon[0][1]
1333    for point in polygon[1:]:
1334        x = point[0] 
1335        if x > max_x: max_x = x
1336        if x < min_x: min_x = x           
1337        y = point[1] 
1338        if y > max_y: max_y = y
1339        if y < min_y: min_y = y           
1340
1341
1342    while len(points) < number_of_points:     
1343        x = uniform(min_x, max_x)
1344        y = uniform(min_y, max_y)       
1345
1346        if inside_polygon( [x,y], polygon ):
1347            points.append([x,y])
1348
1349    return points
1350
1351def tsh2sww(filename, verbose=True):
1352    """
1353    to check if a tsh/msh file 'looks' good.
1354    """
1355    from shallow_water import Domain
1356    from pmesh2domain import pmesh_to_domain_instance
1357    import time, os 
1358    from data_manager import get_dataobject
1359    from os import sep, path 
1360    #from util import mean
1361   
1362    if verbose == True:print 'Creating domain from', filename
1363    domain = pmesh_to_domain_instance(filename, Domain)
1364    if verbose == True:print "Number of triangles = ", len(domain)
1365
1366    domain.smooth = True
1367    domain.format = 'sww'   #Native netcdf visualisation format
1368    file_path, filename = path.split(filename)
1369    filename, ext = path.splitext(filename)
1370    domain.filename = filename
1371    domain.reduction = mean
1372    if verbose == True:print "file_path",file_path
1373    if file_path == "":file_path = "."
1374    domain.set_datadir(file_path)
1375
1376    if verbose == True:
1377        print "Output written to " + domain.get_datadir() + sep + \
1378              domain.filename + "." + domain.format 
1379    sww = get_dataobject(domain)
1380    sww.store_connectivity()
1381    sww.store_timestep('stage')
1382
1383####################################################################
1384#Python versions of function that are also implemented in util_gateway.c
1385#
1386
1387def gradient_python(x0, y0, x1, y1, x2, y2, q0, q1, q2):
1388    """
1389    """
1390   
1391    det = (y2-y0)*(x1-x0) - (y1-y0)*(x2-x0)           
1392    a = (y2-y0)*(q1-q0) - (y1-y0)*(q2-q0)
1393    a /= det
1394
1395    b = (x1-x0)*(q2-q0) - (x2-x0)*(q1-q0)
1396    b /= det           
1397
1398    return a, b
1399
1400
1401
1402##############################################
1403#Initialise module
1404
1405import compile
1406if compile.can_use_C_extension('util_ext.c'):
1407    from util_ext import gradient, point_on_line
1408    separate_points_by_polygon = separate_points_by_polygon_c
1409else:
1410    gradient = gradient_python
1411
1412
1413if __name__ == "__main__":
1414    pass
1415
1416   
1417   
1418   
1419   
1420
1421#OBSOLETED STUFF   
1422def inside_polygon_old(point, polygon, closed = True, verbose = False):
1423    #FIXME Obsoleted
1424    """Determine whether points are inside or outside a polygon
1425   
1426    Input:
1427       point - Tuple of (x, y) coordinates, or list of tuples
1428       polygon - list of vertices of polygon
1429       closed - (optional) determine whether points on boundary should be
1430       regarded as belonging to the polygon (closed = True)
1431       or not (closed = False)
1432   
1433    Output:
1434       If one point is considered, True or False is returned.
1435       If multiple points are passed in, the function returns indices
1436       of those points that fall inside the polygon     
1437       
1438    Examples:
1439       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square
1440       inside_polygon( [0.5, 0.5], U)
1441       will evaluate to True as the point 0.5, 0.5 is inside the unit square
1442       
1443       inside_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)
1444       will return the indices [0, 2] as only the first and the last point
1445       is inside the unit square
1446       
1447    Remarks:
1448       The vertices may be listed clockwise or counterclockwise and
1449       the first point may optionally be repeated.   
1450       Polygons do not need to be convex.
1451       Polygons can have holes in them and points inside a hole is
1452       regarded as being outside the polygon.         
1453               
1454       
1455    Algorithm is based on work by Darel Finley,
1456    http://www.alienryderflex.com/polygon/
1457   
1458    """   
1459
1460    from Numeric import array, Float, reshape
1461   
1462   
1463    #Input checks
1464    try:
1465        point = array(point).astype(Float)         
1466    except:
1467        msg = 'Point %s could not be converted to Numeric array' %point
1468        raise msg       
1469       
1470    try:
1471        polygon = array(polygon).astype(Float)             
1472    except:
1473        msg = 'Polygon %s could not be converted to Numeric array' %polygon
1474        raise msg               
1475   
1476    assert len(polygon.shape) == 2,\
1477       'Polygon array must be a 2d array of vertices: %s' %polygon
1478
1479       
1480    assert polygon.shape[1] == 2,\
1481       'Polygon array must have two columns: %s' %polygon   
1482
1483
1484    if len(point.shape) == 1:
1485        one_point = True
1486        points = reshape(point, (1,2))
1487    else:       
1488        one_point = False
1489        points = point
1490
1491    N = polygon.shape[0] #Number of vertices in polygon
1492    M = points.shape[0]  #Number of points
1493   
1494    px = polygon[:,0]
1495    py = polygon[:,1]   
1496
1497    #Used for an optimisation when points are far away from polygon
1498    minpx = min(px); maxpx = max(px)
1499    minpy = min(py); maxpy = max(py)   
1500
1501
1502    #Begin main loop (FIXME: It'd be crunchy to have this written in C:-)
1503    indices = []
1504    for k in range(M):
1505
1506        if verbose:
1507            if k %((M+10)/10)==0: print 'Doing %d of %d' %(k, M)
1508       
1509        #for each point   
1510        x = points[k, 0]
1511        y = points[k, 1]       
1512
1513        inside = False
1514
1515        #Optimisation
1516        if x > maxpx or x < minpx: continue
1517        if y > maxpy or y < minpy: continue       
1518
1519        #Check polygon
1520        for i in range(N):
1521            j = (i+1)%N
1522           
1523            #Check for case where point is contained in line segment   
1524            if point_on_line(x, y, px[i], py[i], px[j], py[j]):
1525                if closed:
1526                    inside = True
1527                else:       
1528                    inside = False
1529                break
1530            else:       
1531                #Check if truly inside polygon             
1532                if py[i] < y and py[j] >= y or\
1533                   py[j] < y and py[i] >= y:
1534                    if px[i] + (y-py[i])/(py[j]-py[i])*(px[j]-px[i]) < x:
1535                        inside = not inside
1536                   
1537        if inside: indices.append(k)
1538
1539    if one_point: 
1540        return inside
1541    else:
1542        return indices
1543             
1544
1545#def remove_from(A, B):
1546#    """Assume that A
1547#    """
1548#    from Numeric import search_sorted##
1549#
1550#    ind = search_sorted(A, B)
1551
1552   
1553
1554def outside_polygon_old(point, polygon, closed = True, verbose = False):
1555    #OBSOLETED
1556    """Determine whether points are outside a polygon
1557   
1558    Input:
1559       point - Tuple of (x, y) coordinates, or list of tuples
1560       polygon - list of vertices of polygon
1561       closed - (optional) determine whether points on boundary should be
1562       regarded as belonging to the polygon (closed = True)
1563       or not (closed = False)
1564   
1565    Output:
1566       If one point is considered, True or False is returned.
1567       If multiple points are passed in, the function returns indices
1568       of those points that fall outside the polygon     
1569       
1570    Examples:
1571       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square   
1572       outside_polygon( [0.5, 0.5], U )
1573       will evaluate to False as the point 0.5, 0.5 is inside the unit square
1574
1575       ouside_polygon( [1.5, 0.5], U )
1576       will evaluate to True as the point 1.5, 0.5 is outside the unit square
1577       
1578       outside_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U )
1579       will return the indices [1] as only the first and the last point
1580       is inside the unit square
1581    """
1582
1583    #FIXME: This is too slow
1584   
1585    res  = inside_polygon(point, polygon, closed, verbose)
1586
1587    if res is True or res is False:
1588        return not res
1589
1590    #Now invert indices
1591    from Numeric import arrayrange, compress
1592    outside_indices = arrayrange(len(point))
1593    for i in res:
1594        outside_indices = compress(outside_indices != i, outside_indices)
1595    return outside_indices
1596
1597def inside_polygon_c(point, polygon, closed = True, verbose = False):
1598    #FIXME: Obsolete
1599    """Determine whether points are inside or outside a polygon
1600
1601    C-wrapper
1602    """   
1603
1604    from Numeric import array, Float, reshape, zeros, Int
1605   
1606
1607    if verbose: print 'Checking input to inside_polygon'   
1608    #Input checks
1609    try:
1610        point = array(point).astype(Float)         
1611    except:
1612        msg = 'Point %s could not be converted to Numeric array' %point
1613        raise msg       
1614       
1615    try:
1616        polygon = array(polygon).astype(Float)             
1617    except:
1618        msg = 'Polygon %s could not be converted to Numeric array' %polygon
1619        raise msg               
1620   
1621    assert len(polygon.shape) == 2,\
1622       'Polygon array must be a 2d array of vertices: %s' %polygon
1623
1624       
1625    assert polygon.shape[1] == 2,\
1626       'Polygon array must have two columns: %s' %polygon   
1627
1628
1629    if len(point.shape) == 1:
1630        one_point = True
1631        points = reshape(point, (1,2))
1632    else:       
1633        one_point = False
1634        points = point
1635
1636    from util_ext import inside_polygon
1637
1638    if verbose: print 'Allocating array for indices'
1639
1640    indices = zeros( points.shape[0], Int )
1641
1642    if verbose: print 'Calling C-version of inside poly' 
1643    count = inside_polygon(points, polygon, indices,
1644                           int(closed), int(verbose))
1645
1646    if one_point:
1647        return count == 1 #Return True if the point was inside
1648    else:
1649        if verbose: print 'Got %d points' %count
1650       
1651        return indices[:count]   #Return those indices that were inside
1652
Note: See TracBrowser for help on using the repository browser.