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

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

Comments about gradient computation

File size: 49.7 KB
Line 
1"""This module contains various auxiliary function used by pyvolution.
2
3It is also a clearing house for functions that may later earn a module
4of their own.
5"""
6
7def angle(v):
8    """Compute angle between e1 (the unit vector in the x-direction)
9    and the specified vector
10    """
11
12    from math import acos, pi, sqrt
13    from Numeric import sum, array
14
15    l = sqrt( sum (array(v)**2))
16    v1 = v[0]/l
17    v2 = v[1]/l
18
19    try:
20        theta = acos(v1)
21    except ValueError, e:
22        print 'WARNING (util.py): Angle acos(%s) failed: %s' %(str(v1), e)
23
24        #FIXME, hack to avoid acos(1.0) Value error
25        # why is it happening?
26        # is it catching something we should avoid?
27        s = 1e-6
28        if (v1+s >  1.0) and (v1-s < 1.0) :
29            theta = 0.0
30        elif (v1+s >  -1.0) and (v1-s < -1.0):
31            theta = 3.1415926535897931
32        print 'WARNING (util.py): angle v1 is %f, setting acos to %f '\
33              %(v1, theta)
34
35    if v2 < 0:
36        #Quadrant 3 or 4
37        theta = 2*pi-theta
38
39    return theta
40
41
42def anglediff(v0, v1):
43    """Compute difference between angle of vector x0, y0 and x1, y1.
44    This is used for determining the ordering of vertices,
45    e.g. for checking if they are counter clockwise.
46
47    Always return a positive value
48    """
49
50    from math import pi
51
52    a0 = angle(v0)
53    a1 = angle(v1)
54
55    #Ensure that difference will be positive
56    if a0 < a1:
57        a0 += 2*pi
58
59    return a0-a1
60
61
62def mean(x):
63    from Numeric import sum
64    return sum(x)/len(x)
65
66
67def point_on_line(x, y, x0, y0, x1, y1):
68    """Determine whether a point is on a line segment
69
70    Input: x, y, x0, x0, x1, y1: where
71        point is given by x, y
72        line is given by (x0, y0) and (x1, y1)
73
74    """
75
76    from Numeric import array, dot, allclose
77    from math import sqrt
78
79    a = array([x - x0, y - y0])
80    a_normal = array([a[1], -a[0]])
81
82    b = array([x1 - x0, y1 - y0])
83
84    if dot(a_normal, b) == 0:
85        #Point is somewhere on the infinite extension of the line
86
87        len_a = sqrt(sum(a**2))
88        len_b = sqrt(sum(b**2))
89        if dot(a, b) >= 0 and len_a <= len_b:
90           return True
91        else:
92           return False
93    else:
94      return False
95
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#These have been redefined to use separate_by_polygon which will
954#put all inside indices in the first part of the indices array and
955#outside indices in the last
956
957def inside_polygon(points, polygon, closed = True, verbose = False):
958    """See separate_points_by_polygon for documentation
959    """
960
961    from Numeric import array, Float, reshape
962
963    if verbose: print 'Checking input to inside_polygon'
964    try:
965        points = ensure_numeric(points, Float)
966    except:
967        msg = 'Points could not be converted to Numeric array'
968        raise msg
969
970    try:
971        polygon = ensure_numeric(polygon, Float)
972    except:
973        msg = 'Polygon could not be converted to Numeric array'
974        raise msg
975
976
977
978    if len(points.shape) == 1:
979        one_point = True
980        points = reshape(points, (1,2))
981    else:
982        one_point = False
983
984    indices, count = separate_points_by_polygon(points, polygon,
985                                                closed, verbose)
986
987    if one_point:
988        return count == 1
989    else:
990        return indices[:count]
991
992def outside_polygon(points, polygon, closed = True, verbose = False):
993    """See separate_points_by_polygon for documentation
994    """
995
996    from Numeric import array, Float, reshape
997
998    if verbose: print 'Checking input to outside_polygon'
999    try:
1000        points = ensure_numeric(points, Float)
1001    except:
1002        msg = 'Points could not be converted to Numeric array'
1003        raise msg
1004
1005    try:
1006        polygon = ensure_numeric(polygon, Float)
1007    except:
1008        msg = 'Polygon could not be converted to Numeric array'
1009        raise msg
1010
1011
1012
1013    if len(points.shape) == 1:
1014        one_point = True
1015        points = reshape(points, (1,2))
1016    else:
1017        one_point = False
1018
1019    indices, count = separate_points_by_polygon(points, polygon,
1020                                                closed, verbose)
1021
1022    if one_point:
1023        return count != 1
1024    else:
1025        return indices[count:][::-1]  #return reversed
1026
1027
1028def separate_points_by_polygon(points, polygon,
1029                               closed = True, verbose = False):
1030    """Determine whether points are inside or outside a polygon
1031
1032    Input:
1033       points - Tuple of (x, y) coordinates, or list of tuples
1034       polygon - list of vertices of polygon
1035       closed - (optional) determine whether points on boundary should be
1036       regarded as belonging to the polygon (closed = True)
1037       or not (closed = False)
1038
1039    Outputs:
1040       indices: array of same length as points with indices of points falling
1041       inside the polygon listed from the beginning and indices of points
1042       falling outside listed from the end.
1043
1044       count: count of points falling inside the polygon
1045
1046       The indices of points inside are obtained as indices[:count]
1047       The indices of points outside are obtained as indices[count:]
1048
1049
1050    Examples:
1051       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square
1052
1053       separate_points_by_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)
1054       will return the indices [0, 2, 1] and count == 2 as only the first
1055       and the last point are inside the unit square
1056
1057    Remarks:
1058       The vertices may be listed clockwise or counterclockwise and
1059       the first point may optionally be repeated.
1060       Polygons do not need to be convex.
1061       Polygons can have holes in them and points inside a hole is
1062       regarded as being outside the polygon.
1063
1064    Algorithm is based on work by Darel Finley,
1065    http://www.alienryderflex.com/polygon/
1066
1067    """
1068
1069    from Numeric import array, Float, reshape, Int, zeros
1070
1071
1072    #Input checks
1073    try:
1074        points = ensure_numeric(points, Float)
1075    except:
1076        msg = 'Points could not be converted to Numeric array'
1077        raise msg
1078
1079    try:
1080        polygon = ensure_numeric(polygon, Float)
1081    except:
1082        msg = 'Polygon could not be converted to Numeric array'
1083        raise msg
1084
1085    assert len(polygon.shape) == 2,\
1086       'Polygon array must be a 2d array of vertices'
1087
1088    assert polygon.shape[1] == 2,\
1089       'Polygon array must have two columns'
1090
1091    assert len(points.shape) == 2,\
1092       'Points array must be a 2d array'
1093
1094    assert points.shape[1] == 2,\
1095       'Points array must have two columns'
1096
1097    N = polygon.shape[0] #Number of vertices in polygon
1098    M = points.shape[0]  #Number of points
1099
1100    px = polygon[:,0]
1101    py = polygon[:,1]
1102
1103    #Used for an optimisation when points are far away from polygon
1104    minpx = min(px); maxpx = max(px)
1105    minpy = min(py); maxpy = max(py)
1106
1107
1108    #Begin main loop
1109    indices = zeros(M, Int)
1110
1111    inside_index = 0    #Keep track of points inside
1112    outside_index = M-1 #Keep track of points outside (starting from end)
1113
1114    for k in range(M):
1115
1116        if verbose:
1117            if k %((M+10)/10)==0: print 'Doing %d of %d' %(k, M)
1118
1119        #for each point
1120        x = points[k, 0]
1121        y = points[k, 1]
1122
1123        inside = False
1124
1125        if not x > maxpx or x < minpx or y > maxpy or y < minpy:
1126            #Check polygon
1127            for i in range(N):
1128                j = (i+1)%N
1129
1130                #Check for case where point is contained in line segment
1131                if point_on_line(x, y, px[i], py[i], px[j], py[j]):
1132                    if closed:
1133                        inside = True
1134                    else:
1135                        inside = False
1136                    break
1137                else:
1138                    #Check if truly inside polygon
1139                    if py[i] < y and py[j] >= y or\
1140                       py[j] < y and py[i] >= y:
1141                        if px[i] + (y-py[i])/(py[j]-py[i])*(px[j]-px[i]) < x:
1142                            inside = not inside
1143
1144        if inside:
1145            indices[inside_index] = k
1146            inside_index += 1
1147        else:
1148            indices[outside_index] = k
1149            outside_index -= 1
1150
1151    return indices, inside_index
1152
1153
1154def separate_points_by_polygon_c(points, polygon,
1155                                 closed = True, verbose = False):
1156    """Determine whether points are inside or outside a polygon
1157
1158    C-wrapper
1159    """
1160
1161    from Numeric import array, Float, reshape, zeros, Int
1162
1163
1164    if verbose: print 'Checking input to separate_points_by_polygon'
1165    #Input checks
1166    try:
1167        points = ensure_numeric(points, Float)
1168    except:
1169        msg = 'Points could not be converted to Numeric array'
1170        raise msg
1171
1172    #if verbose: print 'Checking input to separate_points_by_polygon 2'
1173    try:
1174        polygon = ensure_numeric(polygon, Float)
1175    except:
1176        msg = 'Polygon could not be converted to Numeric array'
1177        raise msg
1178
1179    if verbose: print 'check'
1180
1181    assert len(polygon.shape) == 2,\
1182       'Polygon array must be a 2d array of vertices'
1183
1184    assert polygon.shape[1] == 2,\
1185       'Polygon array must have two columns'
1186
1187    assert len(points.shape) == 2,\
1188       'Points array must be a 2d array'
1189
1190    assert points.shape[1] == 2,\
1191       'Points array must have two columns'
1192
1193    N = polygon.shape[0] #Number of vertices in polygon
1194    M = points.shape[0]  #Number of points
1195
1196    from util_ext import separate_points_by_polygon
1197
1198    if verbose: print 'Allocating array for indices'
1199
1200    indices = zeros( M, Int )
1201
1202    if verbose: print 'Calling C-version of inside poly'
1203    count = separate_points_by_polygon(points, polygon, indices,
1204                                       int(closed), int(verbose))
1205
1206    return indices, count
1207
1208
1209
1210class Polygon_function:
1211    """Create callable object f: x,y -> z, where a,y,z are vectors and
1212    where f will return different values depending on whether x,y belongs
1213    to specified polygons.
1214
1215    To instantiate:
1216
1217       Polygon_function(polygons)
1218
1219    where polygons is a list of tuples of the form
1220
1221      [ (P0, v0), (P1, v1), ...]
1222
1223      with Pi being lists of vertices defining polygons and vi either
1224      constants or functions of x,y to be applied to points with the polygon.
1225
1226    The function takes an optional argument, default which is the value
1227    (or function) to used for points not belonging to any polygon.
1228    For example:
1229
1230       Polygon_function(polygons, default = 0.03)
1231
1232    If omitted the default value will be 0.0
1233
1234    Note: If two polygons overlap, the one last in the list takes precedence
1235
1236    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???
1237
1238    """
1239
1240    def __init__(self, regions, default = 0.0):
1241
1242        try:
1243            len(regions)
1244        except:
1245            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons
1246            raise msg
1247
1248
1249        T = regions[0]
1250        try:
1251            a = len(T)
1252        except:
1253            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons
1254            raise msg
1255
1256        assert a == 2, 'Must have two component each: %s' %T
1257
1258        self.regions = regions
1259        self.default = default
1260
1261
1262    def __call__(self, x, y):
1263        from util import inside_polygon
1264        from Numeric import ones, Float, concatenate, array, reshape, choose
1265
1266        x = array(x).astype(Float)
1267        y = array(y).astype(Float)
1268
1269        N = len(x)
1270        assert len(y) == N
1271
1272        points = concatenate( (reshape(x, (N, 1)),
1273                               reshape(y, (N, 1))), axis=1 )
1274
1275        if callable(self.default):
1276            z = self.default(x,y)
1277        else:
1278            z = ones(N, Float) * self.default
1279
1280        for polygon, value in self.regions:
1281            indices = inside_polygon(points, polygon)
1282
1283            #FIXME: This needs to be vectorised
1284            if callable(value):
1285                for i in indices:
1286                    xx = array([x[i]])
1287                    yy = array([y[i]])
1288                    z[i] = value(xx, yy)[0]
1289            else:
1290                for i in indices:
1291                    z[i] = value
1292
1293        return z
1294
1295def read_polygon(filename):
1296    """Read points assumed to form a polygon
1297       There must be exactly two numbers in each line
1298    """
1299
1300    #Get polygon
1301    fid = open(filename)
1302    lines = fid.readlines()
1303    fid.close()
1304    polygon = []
1305    for line in lines:
1306        fields = line.split(',')
1307        polygon.append( [float(fields[0]), float(fields[1])] )
1308
1309    return polygon
1310
1311def populate_polygon(polygon, number_of_points, seed = None):
1312    """Populate given polygon with uniformly distributed points.
1313
1314    Input:
1315       polygon - list of vertices of polygon
1316       number_of_points - (optional) number of points
1317       seed - seed for random number generator (default=None)
1318
1319    Output:
1320       points - list of points inside polygon
1321
1322    Examples:
1323       populate_polygon( [[0,0], [1,0], [1,1], [0,1]], 5 )
1324       will return five randomly selected points inside the unit square
1325    """
1326
1327    from random import uniform, seed
1328
1329    seed(seed)
1330
1331    points = []
1332
1333    #Find outer extent of polygon
1334    max_x = min_x = polygon[0][0]
1335    max_y = min_y = polygon[0][1]
1336    for point in polygon[1:]:
1337        x = point[0]
1338        if x > max_x: max_x = x
1339        if x < min_x: min_x = x
1340        y = point[1]
1341        if y > max_y: max_y = y
1342        if y < min_y: min_y = y
1343
1344
1345    while len(points) < number_of_points:
1346        x = uniform(min_x, max_x)
1347        y = uniform(min_y, max_y)
1348
1349        if inside_polygon( [x,y], polygon ):
1350            points.append([x,y])
1351
1352    return points
1353
1354def tsh2sww(filename, verbose=True):
1355    """
1356    to check if a tsh/msh file 'looks' good.
1357    """
1358    from shallow_water import Domain
1359    from pmesh2domain import pmesh_to_domain_instance
1360    import time, os
1361    from data_manager import get_dataobject
1362    from os import sep, path
1363    #from util import mean
1364
1365    if verbose == True:print 'Creating domain from', filename
1366    domain = pmesh_to_domain_instance(filename, Domain)
1367    if verbose == True:print "Number of triangles = ", len(domain)
1368
1369    domain.smooth = True
1370    domain.format = 'sww'   #Native netcdf visualisation format
1371    file_path, filename = path.split(filename)
1372    filename, ext = path.splitext(filename)
1373    domain.filename = filename
1374    domain.reduction = mean
1375    if verbose == True:print "file_path",file_path
1376    if file_path == "":file_path = "."
1377    domain.set_datadir(file_path)
1378
1379    if verbose == True:
1380        print "Output written to " + domain.get_datadir() + sep + \
1381              domain.filename + "." + domain.format
1382    sww = get_dataobject(domain)
1383    sww.store_connectivity()
1384    sww.store_timestep('stage')
1385
1386####################################################################
1387#Python versions of function that are also implemented in util_gateway.c
1388#
1389
1390def gradient_python(x0, y0, x1, y1, x2, y2, q0, q1, q2):
1391    """
1392    """
1393
1394    det = (y2-y0)*(x1-x0) - (y1-y0)*(x2-x0)
1395    a = (y2-y0)*(q1-q0) - (y1-y0)*(q2-q0)
1396    a /= det
1397
1398    b = (x1-x0)*(q2-q0) - (x2-x0)*(q1-q0)
1399    b /= det
1400
1401    return a, b
1402
1403
1404def gradient2_python(x0, y0, x1, y1, q0, q1):
1405    """Compute radient based on two points and enforce zero gradient
1406    in the direction orthogonal to (x1-x0), (y1-y0)
1407    """
1408
1409    #Old code
1410    #det = x0*y1 - x1*y0
1411    #if det != 0.0:
1412    #    a = (y1*q0 - y0*q1)/det
1413    #    b = (x0*q1 - x1*q0)/det
1414
1415    #Correct code (ON)
1416    det = (x1-x0)**2 + (y1-y0)**2
1417    if det != 0.0:
1418        a = (x1-x0)*(q1-q0)/det
1419        b = (y1-y0)*(q1-q0)/det
1420       
1421    return a, b       
1422
1423
1424##############################################
1425#Initialise module
1426
1427import compile
1428if compile.can_use_C_extension('util_ext.c'):
1429    from util_ext import gradient, gradient2, point_on_line
1430    separate_points_by_polygon = separate_points_by_polygon_c
1431else:
1432    gradient = gradient_python
1433    gradient2 = gradient2_python   
1434
1435
1436if __name__ == "__main__":
1437    pass
1438
1439
1440
1441
1442
1443
1444#OBSOLETED STUFF
1445def inside_polygon_old(point, polygon, closed = True, verbose = False):
1446    #FIXME Obsoleted
1447    """Determine whether points are inside or outside a polygon
1448
1449    Input:
1450       point - Tuple of (x, y) coordinates, or list of tuples
1451       polygon - list of vertices of polygon
1452       closed - (optional) determine whether points on boundary should be
1453       regarded as belonging to the polygon (closed = True)
1454       or not (closed = False)
1455
1456    Output:
1457       If one point is considered, True or False is returned.
1458       If multiple points are passed in, the function returns indices
1459       of those points that fall inside the polygon
1460
1461    Examples:
1462       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square
1463       inside_polygon( [0.5, 0.5], U)
1464       will evaluate to True as the point 0.5, 0.5 is inside the unit square
1465
1466       inside_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)
1467       will return the indices [0, 2] as only the first and the last point
1468       is inside the unit square
1469
1470    Remarks:
1471       The vertices may be listed clockwise or counterclockwise and
1472       the first point may optionally be repeated.
1473       Polygons do not need to be convex.
1474       Polygons can have holes in them and points inside a hole is
1475       regarded as being outside the polygon.
1476
1477
1478    Algorithm is based on work by Darel Finley,
1479    http://www.alienryderflex.com/polygon/
1480
1481    """
1482
1483    from Numeric import array, Float, reshape
1484
1485
1486    #Input checks
1487    try:
1488        point = array(point).astype(Float)
1489    except:
1490        msg = 'Point %s could not be converted to Numeric array' %point
1491        raise msg
1492
1493    try:
1494        polygon = array(polygon).astype(Float)
1495    except:
1496        msg = 'Polygon %s could not be converted to Numeric array' %polygon
1497        raise msg
1498
1499    assert len(polygon.shape) == 2,\
1500       'Polygon array must be a 2d array of vertices: %s' %polygon
1501
1502
1503    assert polygon.shape[1] == 2,\
1504       'Polygon array must have two columns: %s' %polygon
1505
1506
1507    if len(point.shape) == 1:
1508        one_point = True
1509        points = reshape(point, (1,2))
1510    else:
1511        one_point = False
1512        points = point
1513
1514    N = polygon.shape[0] #Number of vertices in polygon
1515    M = points.shape[0]  #Number of points
1516
1517    px = polygon[:,0]
1518    py = polygon[:,1]
1519
1520    #Used for an optimisation when points are far away from polygon
1521    minpx = min(px); maxpx = max(px)
1522    minpy = min(py); maxpy = max(py)
1523
1524
1525    #Begin main loop (FIXME: It'd be crunchy to have this written in C:-)
1526    indices = []
1527    for k in range(M):
1528
1529        if verbose:
1530            if k %((M+10)/10)==0: print 'Doing %d of %d' %(k, M)
1531
1532        #for each point
1533        x = points[k, 0]
1534        y = points[k, 1]
1535
1536        inside = False
1537
1538        #Optimisation
1539        if x > maxpx or x < minpx: continue
1540        if y > maxpy or y < minpy: continue
1541
1542        #Check polygon
1543        for i in range(N):
1544            j = (i+1)%N
1545
1546            #Check for case where point is contained in line segment
1547            if point_on_line(x, y, px[i], py[i], px[j], py[j]):
1548                if closed:
1549                    inside = True
1550                else:
1551                    inside = False
1552                break
1553            else:
1554                #Check if truly inside polygon
1555                if py[i] < y and py[j] >= y or\
1556                   py[j] < y and py[i] >= y:
1557                    if px[i] + (y-py[i])/(py[j]-py[i])*(px[j]-px[i]) < x:
1558                        inside = not inside
1559
1560        if inside: indices.append(k)
1561
1562    if one_point:
1563        return inside
1564    else:
1565        return indices
1566
1567
1568#def remove_from(A, B):
1569#    """Assume that A
1570#    """
1571#    from Numeric import search_sorted##
1572#
1573#    ind = search_sorted(A, B)
1574
1575
1576
1577def outside_polygon_old(point, polygon, closed = True, verbose = False):
1578    #OBSOLETED
1579    """Determine whether points are outside a polygon
1580
1581    Input:
1582       point - Tuple of (x, y) coordinates, or list of tuples
1583       polygon - list of vertices of polygon
1584       closed - (optional) determine whether points on boundary should be
1585       regarded as belonging to the polygon (closed = True)
1586       or not (closed = False)
1587
1588    Output:
1589       If one point is considered, True or False is returned.
1590       If multiple points are passed in, the function returns indices
1591       of those points that fall outside the polygon
1592
1593    Examples:
1594       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square
1595       outside_polygon( [0.5, 0.5], U )
1596       will evaluate to False as the point 0.5, 0.5 is inside the unit square
1597
1598       ouside_polygon( [1.5, 0.5], U )
1599       will evaluate to True as the point 1.5, 0.5 is outside the unit square
1600
1601       outside_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U )
1602       will return the indices [1] as only the first and the last point
1603       is inside the unit square
1604    """
1605
1606    #FIXME: This is too slow
1607
1608    res  = inside_polygon(point, polygon, closed, verbose)
1609
1610    if res is True or res is False:
1611        return not res
1612
1613    #Now invert indices
1614    from Numeric import arrayrange, compress
1615    outside_indices = arrayrange(len(point))
1616    for i in res:
1617        outside_indices = compress(outside_indices != i, outside_indices)
1618    return outside_indices
1619
1620def inside_polygon_c(point, polygon, closed = True, verbose = False):
1621    #FIXME: Obsolete
1622    """Determine whether points are inside or outside a polygon
1623
1624    C-wrapper
1625    """
1626
1627    from Numeric import array, Float, reshape, zeros, Int
1628
1629
1630    if verbose: print 'Checking input to inside_polygon'
1631    #Input checks
1632    try:
1633        point = array(point).astype(Float)
1634    except:
1635        msg = 'Point %s could not be converted to Numeric array' %point
1636        raise msg
1637
1638    try:
1639        polygon = array(polygon).astype(Float)
1640    except:
1641        msg = 'Polygon %s could not be converted to Numeric array' %polygon
1642        raise msg
1643
1644    assert len(polygon.shape) == 2,\
1645       'Polygon array must be a 2d array of vertices: %s' %polygon
1646
1647
1648    assert polygon.shape[1] == 2,\
1649       'Polygon array must have two columns: %s' %polygon
1650
1651
1652    if len(point.shape) == 1:
1653        one_point = True
1654        points = reshape(point, (1,2))
1655    else:
1656        one_point = False
1657        points = point
1658
1659    from util_ext import inside_polygon
1660
1661    if verbose: print 'Allocating array for indices'
1662
1663    indices = zeros( points.shape[0], Int )
1664
1665    if verbose: print 'Calling C-version of inside poly'
1666    count = inside_polygon(points, polygon, indices,
1667                           int(closed), int(verbose))
1668
1669    if one_point:
1670        return count == 1 #Return True if the point was inside
1671    else:
1672        if verbose: print 'Got %d points' %count
1673
1674        return indices[:count]   #Return those indices that were inside
Note: See TracBrowser for help on using the repository browser.