source: inundation/pyvolution/util.py @ 1903

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

removing / gutting duplication of methods reading xya files

File size: 25.3 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
7#FIXME: Move polygon stuff out
8
9def angle(v):
10    """Compute angle between e1 (the unit vector in the x-direction)
11    and the specified vector
12    """
13
14    from math import acos, pi, sqrt
15    from Numeric import sum, array
16
17    l = sqrt( sum (array(v)**2))
18    v1 = v[0]/l
19    v2 = v[1]/l
20
21
22    theta = acos(v1)
23   
24    #try:
25    #   theta = acos(v1)
26    #except ValueError, e:
27    #    print 'WARNING (util.py): Angle acos(%s) failed: %s' %(str(v1), e)
28    #
29    #    #FIXME, hack to avoid acos(1.0) Value error
30    #    # why is it happening?
31    #    # is it catching something we should avoid?
32    #    #FIXME (Ole): When did this happen? We need a unit test to
33    #    #reveal this condition or otherwise remove the hack
34    #   
35    #    s = 1e-6
36    #    if (v1+s >  1.0) and (v1-s < 1.0) :
37    #        theta = 0.0
38    #    elif (v1+s >  -1.0) and (v1-s < -1.0):
39    #        theta = 3.1415926535897931
40    #    print 'WARNING (util.py): angle v1 is %f, setting acos to %f '\
41    #          %(v1, theta)
42
43    if v2 < 0:
44        #Quadrant 3 or 4
45        theta = 2*pi-theta
46
47    return theta
48
49
50def anglediff(v0, v1):
51    """Compute difference between angle of vector x0, y0 and x1, y1.
52    This is used for determining the ordering of vertices,
53    e.g. for checking if they are counter clockwise.
54
55    Always return a positive value
56    """
57
58    from math import pi
59
60    a0 = angle(v0)
61    a1 = angle(v1)
62
63    #Ensure that difference will be positive
64    if a0 < a1:
65        a0 += 2*pi
66
67    return a0-a1
68
69
70def mean(x):
71    from Numeric import sum
72    return sum(x)/len(x)
73
74
75def point_on_line(x, y, x0, y0, x1, y1):
76    """Determine whether a point is on a line segment
77
78    Input: x, y, x0, x0, x1, y1: where
79        point is given by x, y
80        line is given by (x0, y0) and (x1, y1)
81
82    """
83
84    from Numeric import array, dot, allclose
85    from math import sqrt
86
87    a = array([x - x0, y - y0])
88    a_normal = array([a[1], -a[0]])
89
90    b = array([x1 - x0, y1 - y0])
91
92    if dot(a_normal, b) == 0:
93        #Point is somewhere on the infinite extension of the line
94
95        len_a = sqrt(sum(a**2))
96        len_b = sqrt(sum(b**2))
97        if dot(a, b) >= 0 and len_a <= len_b:
98           return True
99        else:
100           return False
101    else:
102      return False
103
104def ensure_numeric(A, typecode = None):
105    """Ensure that sequence is a Numeric array.
106    Inputs:
107        A: Sequance. If A is already a Numeric array it will be returned
108                     unaltered
109                     If not, an attempt is made to convert it to a Numeric
110                     array
111        typecode: Numeric type. If specified, use this in the conversion.
112                                If not, let Numeric decide
113
114    This function is necessary as array(A) can cause memory overflow.
115    """
116
117    from Numeric import ArrayType, array
118
119    if typecode is None:
120        if type(A) == ArrayType:
121            return A
122        else:
123            return array(A)
124    else:
125        if type(A) == ArrayType:
126            if A.typecode == typecode:
127                return array(A)  #FIXME: Shouldn't this be just return A?
128            else:
129                return A.astype(typecode)
130        else:
131            return array(A).astype(typecode)
132
133
134
135def file_function(filename,
136                  domain = None,
137                  quantities = None,
138                  interpolation_points = None, verbose = False):
139    """Read time history of spatial data from NetCDF file and return
140    a callable object.
141
142    If the file has extension 'sww' then it is assumed to be spatio-temporal
143    or temporal and the callable object will have the form f(t,x,y) or f(t)
144    depending on whether the file contains spatial data
145
146    If the file has extension 'tms' then it is assumed to be temporal only
147    and the callable object will have the form f(t)
148
149    Either form will return interpolated values based on the input file
150    using the underlying interpolation_function.
151
152    If domain is specified, model time (domain.starttime)
153    will be checked and possibly modified.
154   
155    All times are assumed to be in UTC
156
157    All spatial information is assumed to be in absolute UTM coordinates.
158
159    See Interpolation function for further documentation
160    """
161   
162
163    #FIXME (OLE): Should check origin of domain against that of file
164    #In fact, this is where origin should be converted to that of domain
165    #Also, check that file covers domain fully.
166    #If we use the suggested Point_set class for interpolation points
167    #here it would be easier
168
169    #Take into account:
170    #- domain's georef
171    #- sww file's georef
172    #- interpolation points as absolute UTM coordinates
173
174
175    assert type(filename) == type(''),\
176               'First argument to File_function must be a string'
177
178    try:
179        fid = open(filename)
180    except Exception, e:
181        msg = 'File "%s" could not be opened: Error="%s"'\
182                  %(filename, e)
183        raise msg
184
185    line = fid.readline()
186    fid.close()
187
188    if quantities is None: 
189        if domain is not None:
190            quantities = domain.conserved_quantities
191
192
193
194    if line[:3] == 'CDF':
195        return get_netcdf_file_function(filename, domain, quantities,
196                                        interpolation_points, verbose = verbose)
197    else:
198        raise 'Must be a NetCDF File'
199
200
201
202def get_netcdf_file_function(filename, domain=None, quantity_names=None,
203                             interpolation_points=None, verbose = False):
204    """Read time history of spatial data from NetCDF sww file and
205    return a callable object f(t,x,y)
206    which will return interpolated values based on the input file.
207
208    If domain is specified, model time (domain.starttime)
209    will be checked and possibly modified
210   
211    All times are assumed to be in UTC
212
213    See Interpolation function for further documetation
214   
215
216    """
217   
218    #verbose = True
219   
220    #FIXME: Check that model origin is the same as file's origin
221    #(both in UTM coordinates)
222    #If not - modify those from file to match domain
223    #Take this code from e.g. dem2pts in data_manager.py
224    #FIXME: Use geo_reference to read and write xllcorner...
225       
226
227    import time, calendar, types
228    from config import time_format
229    from Scientific.IO.NetCDF import NetCDFFile
230    from Numeric import array, zeros, Float, alltrue, concatenate, reshape
231    from util import ensure_numeric   
232
233    #Open NetCDF file
234    if verbose: print 'Reading', filename
235    fid = NetCDFFile(filename, 'r')
236
237    if type(quantity_names) == types.StringType:
238        quantity_names = [quantity_names]       
239   
240    if quantity_names is None or len(quantity_names) < 1:
241        #If no quantities are specified get quantities from file
242        #x, y, time are assumed as the independent variables so
243        #they are excluded as potentiol quantities
244        quantity_names = []
245        for name in fid.variables.keys():
246            if name not in ['x', 'y', 'time']:
247                quantity_names.append(name)
248
249    if len(quantity_names) < 1:               
250        msg = 'ERROR: At least one independent value must be specified'
251        raise msg
252
253
254    if interpolation_points is not None:
255        interpolation_points = ensure_numeric(interpolation_points)
256
257
258
259    #Now assert that requested quantitites (and the independent ones)
260    #are present in file
261    missing = []
262    for quantity in ['time'] + quantity_names:
263        if not fid.variables.has_key(quantity):
264            missing.append(quantity)
265
266    if len(missing) > 0:
267        msg = 'Quantities %s could not be found in file %s'\
268              %(str(missing), filename)
269        fid.close()
270        raise msg
271
272    #Decide whether this data has a spatial dimension
273    spatial = True
274    for quantity in ['x', 'y']:
275        if not fid.variables.has_key(quantity):
276            spatial = False
277
278    if filename[-3:] == 'tms' and spatial is True:
279        msg = 'Files of type tms must not contain spatial information'
280        raise msg
281
282    if filename[-3:] == 'sww' and spatial is False:
283        msg = 'Files of type sww must contain spatial information'       
284        raise msg       
285
286    #Get first timestep
287    try:
288        starttime = fid.starttime[0]
289    except ValueError:
290        msg = 'Could not read starttime from file %s' %filename
291        raise msg
292
293    #Get variables
294    if verbose: print 'Get variables'   
295    time = fid.variables['time'][:]   
296
297    if spatial:
298        #Get origin
299        xllcorner = fid.xllcorner[0]
300        yllcorner = fid.yllcorner[0]
301        zone = fid.zone[0]       
302
303        x = fid.variables['x'][:]
304        y = fid.variables['y'][:]
305        triangles = fid.variables['volumes'][:]
306
307        x = reshape(x, (len(x),1))
308        y = reshape(y, (len(y),1))
309        vertex_coordinates = concatenate((x,y), axis=1) #m x 2 array
310
311        if interpolation_points is not None:
312            #Adjust for georef
313            interpolation_points[:,0] -= xllcorner
314            interpolation_points[:,1] -= yllcorner       
315       
316
317
318
319    if domain is not None:
320        #Update domain.startime if it is *earlier* than starttime
321        if starttime > domain.starttime:
322            msg = 'WARNING: Start time as specified in domain (%f)'\
323                  %domain.starttime
324            msg += ' is earlier than the starttime of file %s (%f).'\
325                     %(filename, starttime)
326            msg += ' Modifying domain starttime accordingly.'
327           
328            if verbose: print msg
329            domain.starttime = starttime #Modifying model time
330            if verbose: print 'Domain starttime is now set to %f'\
331               %domain.starttime
332
333
334        #If domain.startime is *later* than starttime,
335        #move time back - relative to domain's time
336        if domain.starttime > starttime:
337            time = time - domain.starttime + starttime
338
339        #FIXME Use method in geo to reconcile
340        #if spatial:
341        #assert domain.geo_reference.xllcorner == xllcorner
342        #assert domain.geo_reference.yllcorner == yllcorner
343        #assert domain.geo_reference.zone == zone       
344       
345    if verbose:
346        print 'File_function data obtained from: %s' %filename
347        print '  References:'
348        #print '    Datum ....' #FIXME
349        if spatial:
350            print '    Lower left corner: [%f, %f]'\
351                  %(xllcorner, yllcorner)
352        print '    Start time:   %f' %starttime               
353       
354   
355    #Produce values for desired data points at
356    #each timestep
357
358    quantities = {}
359    for i, name in enumerate(quantity_names):
360        quantities[name] = fid.variables[name][:]
361    fid.close()
362   
363
364    from least_squares import Interpolation_function
365
366    if not spatial:
367        vertex_coordinates = triangles = interpolation_points = None         
368       
369    return Interpolation_function(time,
370                                  quantities,
371                                  quantity_names,
372                                  vertex_coordinates,
373                                  triangles,
374                                  interpolation_points,
375                                  verbose = verbose)
376   
377
378
379
380
381def read_xya(filename):
382    """Read simple xya file, possibly with a header in the first line, just
383    x y [attributes]
384    separated by whitespace
385
386    Format can be either ASCII or NetCDF
387
388    Return list of points, list of attributes and
389    attribute names if present otherwise None
390    """
391    print "read_xya is obsolete.  Use import_points_file from load_mesh.loadASCII"
392    #FIXME: Probably obsoleted by similar function in load_ASCII
393    #FIXME: Name read_data_points (or see 'load' in pylab)
394
395   
396    from load_mesh.loadASCII import import_points_file
397
398    points_dict = import_points_file(filename)
399    return points_dict['pointlist'], points_dict['attributelist']
400
401
402#####################################
403#POLYGON STUFF
404#
405#FIXME: All these should be put into new module polygon.py
406
407
408
409#These have been redefined to use separate_by_polygon which will
410#put all inside indices in the first part of the indices array and
411#outside indices in the last
412
413def inside_polygon(points, polygon, closed = True, verbose = False):
414    """See separate_points_by_polygon for documentation
415    """
416
417    from Numeric import array, Float, reshape
418
419    if verbose: print 'Checking input to inside_polygon'
420    try:
421        points = ensure_numeric(points, Float)
422    except:
423        msg = 'Points could not be converted to Numeric array'
424        raise msg
425
426    try:
427        polygon = ensure_numeric(polygon, Float)
428    except:
429        msg = 'Polygon could not be converted to Numeric array'
430        raise msg
431
432
433
434    if len(points.shape) == 1:
435        one_point = True
436        points = reshape(points, (1,2))
437    else:
438        one_point = False
439
440    indices, count = separate_points_by_polygon(points, polygon,
441                                                closed, verbose)
442
443    if one_point:
444        return count == 1
445    else:
446        return indices[:count]
447
448def outside_polygon(points, polygon, closed = True, verbose = False):
449    """See separate_points_by_polygon for documentation
450    """
451
452    from Numeric import array, Float, reshape
453
454    if verbose: print 'Checking input to outside_polygon'
455    try:
456        points = ensure_numeric(points, Float)
457    except:
458        msg = 'Points could not be converted to Numeric array'
459        raise msg
460
461    try:
462        polygon = ensure_numeric(polygon, Float)
463    except:
464        msg = 'Polygon could not be converted to Numeric array'
465        raise msg
466
467
468
469    if len(points.shape) == 1:
470        one_point = True
471        points = reshape(points, (1,2))
472    else:
473        one_point = False
474
475    indices, count = separate_points_by_polygon(points, polygon,
476                                                closed, verbose)
477
478    if one_point:
479        return count != 1
480    else:
481        return indices[count:][::-1]  #return reversed
482
483
484def separate_points_by_polygon(points, polygon,
485                               closed = True, verbose = False):
486    """Determine whether points are inside or outside a polygon
487
488    Input:
489       points - Tuple of (x, y) coordinates, or list of tuples
490       polygon - list of vertices of polygon
491       closed - (optional) determine whether points on boundary should be
492       regarded as belonging to the polygon (closed = True)
493       or not (closed = False)
494
495    Outputs:
496       indices: array of same length as points with indices of points falling
497       inside the polygon listed from the beginning and indices of points
498       falling outside listed from the end.
499
500       count: count of points falling inside the polygon
501
502       The indices of points inside are obtained as indices[:count]
503       The indices of points outside are obtained as indices[count:]
504
505
506    Examples:
507       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square
508
509       separate_points_by_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)
510       will return the indices [0, 2, 1] and count == 2 as only the first
511       and the last point are inside the unit square
512
513    Remarks:
514       The vertices may be listed clockwise or counterclockwise and
515       the first point may optionally be repeated.
516       Polygons do not need to be convex.
517       Polygons can have holes in them and points inside a hole is
518       regarded as being outside the polygon.
519
520    Algorithm is based on work by Darel Finley,
521    http://www.alienryderflex.com/polygon/
522
523    """
524
525    from Numeric import array, Float, reshape, Int, zeros
526
527
528    #Input checks
529    try:
530        points = ensure_numeric(points, Float)
531    except:
532        msg = 'Points could not be converted to Numeric array'
533        raise msg
534
535    try:
536        polygon = ensure_numeric(polygon, Float)
537    except:
538        msg = 'Polygon could not be converted to Numeric array'
539        raise msg
540
541    assert len(polygon.shape) == 2,\
542       'Polygon array must be a 2d array of vertices'
543
544    assert polygon.shape[1] == 2,\
545       'Polygon array must have two columns'
546
547    assert len(points.shape) == 2,\
548       'Points array must be a 2d array'
549
550    assert points.shape[1] == 2,\
551       'Points array must have two columns'
552
553    N = polygon.shape[0] #Number of vertices in polygon
554    M = points.shape[0]  #Number of points
555
556    px = polygon[:,0]
557    py = polygon[:,1]
558
559    #Used for an optimisation when points are far away from polygon
560    minpx = min(px); maxpx = max(px)
561    minpy = min(py); maxpy = max(py)
562
563
564    #Begin main loop
565    indices = zeros(M, Int)
566
567    inside_index = 0    #Keep track of points inside
568    outside_index = M-1 #Keep track of points outside (starting from end)
569
570    for k in range(M):
571
572        if verbose:
573            if k %((M+10)/10)==0: print 'Doing %d of %d' %(k, M)
574
575        #for each point
576        x = points[k, 0]
577        y = points[k, 1]
578
579        inside = False
580
581        if not x > maxpx or x < minpx or y > maxpy or y < minpy:
582            #Check polygon
583            for i in range(N):
584                j = (i+1)%N
585
586                #Check for case where point is contained in line segment
587                if point_on_line(x, y, px[i], py[i], px[j], py[j]):
588                    if closed:
589                        inside = True
590                    else:
591                        inside = False
592                    break
593                else:
594                    #Check if truly inside polygon
595                    if py[i] < y and py[j] >= y or\
596                       py[j] < y and py[i] >= y:
597                        if px[i] + (y-py[i])/(py[j]-py[i])*(px[j]-px[i]) < x:
598                            inside = not inside
599
600        if inside:
601            indices[inside_index] = k
602            inside_index += 1
603        else:
604            indices[outside_index] = k
605            outside_index -= 1
606
607    return indices, inside_index
608
609
610def separate_points_by_polygon_c(points, polygon,
611                                 closed = True, verbose = False):
612    """Determine whether points are inside or outside a polygon
613
614    C-wrapper
615    """
616
617    from Numeric import array, Float, reshape, zeros, Int
618
619
620    if verbose: print 'Checking input to separate_points_by_polygon'
621    #Input checks
622    try:
623        points = ensure_numeric(points, Float)
624    except:
625        msg = 'Points could not be converted to Numeric array'
626        raise msg
627
628    #if verbose: print 'Checking input to separate_points_by_polygon 2'
629    try:
630        polygon = ensure_numeric(polygon, Float)
631    except:
632        msg = 'Polygon could not be converted to Numeric array'
633        raise msg
634
635    if verbose: print 'check'
636
637    assert len(polygon.shape) == 2,\
638       'Polygon array must be a 2d array of vertices'
639
640    assert polygon.shape[1] == 2,\
641       'Polygon array must have two columns'
642
643    assert len(points.shape) == 2,\
644       'Points array must be a 2d array'
645
646    assert points.shape[1] == 2,\
647       'Points array must have two columns'
648
649    N = polygon.shape[0] #Number of vertices in polygon
650    M = points.shape[0]  #Number of points
651
652    from util_ext import separate_points_by_polygon
653
654    if verbose: print 'Allocating array for indices'
655
656    indices = zeros( M, Int )
657
658    if verbose: print 'Calling C-version of inside poly'
659    count = separate_points_by_polygon(points, polygon, indices,
660                                       int(closed), int(verbose))
661
662    return indices, count
663
664
665
666class Polygon_function:
667    """Create callable object f: x,y -> z, where a,y,z are vectors and
668    where f will return different values depending on whether x,y belongs
669    to specified polygons.
670
671    To instantiate:
672
673       Polygon_function(polygons)
674
675    where polygons is a list of tuples of the form
676
677      [ (P0, v0), (P1, v1), ...]
678
679      with Pi being lists of vertices defining polygons and vi either
680      constants or functions of x,y to be applied to points with the polygon.
681
682    The function takes an optional argument, default which is the value
683    (or function) to used for points not belonging to any polygon.
684    For example:
685
686       Polygon_function(polygons, default = 0.03)
687
688    If omitted the default value will be 0.0
689
690    Note: If two polygons overlap, the one last in the list takes precedence
691
692    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???
693
694    """
695
696    def __init__(self, regions, default = 0.0):
697
698        try:
699            len(regions)
700        except:
701            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons
702            raise msg
703
704
705        T = regions[0]
706        try:
707            a = len(T)
708        except:
709            msg = 'Polygon_function takes a list of pairs (polygon, value). Got %s' %polygons
710            raise msg
711
712        assert a == 2, 'Must have two component each: %s' %T
713
714        self.regions = regions
715        self.default = default
716
717
718    def __call__(self, x, y):
719        from util import inside_polygon
720        from Numeric import ones, Float, concatenate, array, reshape, choose
721
722        x = array(x).astype(Float)
723        y = array(y).astype(Float)
724
725        N = len(x)
726        assert len(y) == N
727
728        points = concatenate( (reshape(x, (N, 1)),
729                               reshape(y, (N, 1))), axis=1 )
730
731        if callable(self.default):
732            z = self.default(x,y)
733        else:
734            z = ones(N, Float) * self.default
735
736        for polygon, value in self.regions:
737            indices = inside_polygon(points, polygon)
738
739            #FIXME: This needs to be vectorised
740            if callable(value):
741                for i in indices:
742                    xx = array([x[i]])
743                    yy = array([y[i]])
744                    z[i] = value(xx, yy)[0]
745            else:
746                for i in indices:
747                    z[i] = value
748
749        return z
750
751def read_polygon(filename):
752    """Read points assumed to form a polygon
753       There must be exactly two numbers in each line
754    """
755
756    #Get polygon
757    fid = open(filename)
758    lines = fid.readlines()
759    fid.close()
760    polygon = []
761    for line in lines:
762        fields = line.split(',')
763        polygon.append( [float(fields[0]), float(fields[1])] )
764
765    return polygon
766
767def populate_polygon(polygon, number_of_points, seed = None, exclude = None):
768    """Populate given polygon with uniformly distributed points.
769
770    Input:
771       polygon - list of vertices of polygon
772       number_of_points - (optional) number of points
773       seed - seed for random number generator (default=None)
774       exclude - list of polygons (inside main polygon) from where points should be excluded
775
776    Output:
777       points - list of points inside polygon
778
779    Examples:
780       populate_polygon( [[0,0], [1,0], [1,1], [0,1]], 5 )
781       will return five randomly selected points inside the unit square
782    """
783
784    from random import uniform, seed
785
786    seed(seed)
787
788    points = []
789
790    #Find outer extent of polygon
791    max_x = min_x = polygon[0][0]
792    max_y = min_y = polygon[0][1]
793    for point in polygon[1:]:
794        x = point[0]
795        if x > max_x: max_x = x
796        if x < min_x: min_x = x
797        y = point[1]
798        if y > max_y: max_y = y
799        if y < min_y: min_y = y
800
801
802    while len(points) < number_of_points:
803        x = uniform(min_x, max_x)
804        y = uniform(min_y, max_y)
805
806        append = False
807        if inside_polygon( [x,y], polygon ):
808
809            append = True
810           
811            #Check exclusions
812            if exclude is not None:
813                for ex_poly in exclude:
814                    if inside_polygon( [x,y], ex_poly ):
815                        append = False
816                       
817                   
818        if append is True:               
819            points.append([x,y])               
820
821    return points
822
823
824
825def tsh2sww(filename, verbose=True):
826    """
827    to check if a tsh/msh file 'looks' good.
828    """
829
830    #FIXME Move to data_manager
831    from shallow_water import Domain
832    from pmesh2domain import pmesh_to_domain_instance
833    import time, os
834    from data_manager import get_dataobject
835    from os import sep, path
836    #from util import mean
837
838    if verbose == True:print 'Creating domain from', filename
839    domain = pmesh_to_domain_instance(filename, Domain)
840    if verbose == True:print "Number of triangles = ", len(domain)
841
842    domain.smooth = True
843    domain.format = 'sww'   #Native netcdf visualisation format
844    file_path, filename = path.split(filename)
845    filename, ext = path.splitext(filename)
846    domain.filename = filename
847    domain.reduction = mean
848    if verbose == True:print "file_path",file_path
849    if file_path == "":file_path = "."
850    domain.set_datadir(file_path)
851
852    if verbose == True:
853        print "Output written to " + domain.get_datadir() + sep + \
854              domain.filename + "." + domain.format
855    sww = get_dataobject(domain)
856    sww.store_connectivity()
857    sww.store_timestep('stage')
858
859####################################################################
860#Python versions of function that are also implemented in util_gateway.c
861#
862
863def gradient_python(x0, y0, x1, y1, x2, y2, q0, q1, q2):
864    """
865    """
866
867    det = (y2-y0)*(x1-x0) - (y1-y0)*(x2-x0)
868    a = (y2-y0)*(q1-q0) - (y1-y0)*(q2-q0)
869    a /= det
870
871    b = (x1-x0)*(q2-q0) - (x2-x0)*(q1-q0)
872    b /= det
873
874    return a, b
875
876
877def gradient2_python(x0, y0, x1, y1, q0, q1):
878    """Compute radient based on two points and enforce zero gradient
879    in the direction orthogonal to (x1-x0), (y1-y0)
880    """
881
882    #Old code
883    #det = x0*y1 - x1*y0
884    #if det != 0.0:
885    #    a = (y1*q0 - y0*q1)/det
886    #    b = (x0*q1 - x1*q0)/det
887
888    #Correct code (ON)
889    det = (x1-x0)**2 + (y1-y0)**2
890    if det != 0.0:
891        a = (x1-x0)*(q1-q0)/det
892        b = (y1-y0)*(q1-q0)/det
893       
894    return a, b       
895
896
897##############################################
898#Initialise module
899
900import compile
901if compile.can_use_C_extension('util_ext.c'):
902    from util_ext import gradient, gradient2, point_on_line
903    separate_points_by_polygon = separate_points_by_polygon_c
904else:
905    gradient = gradient_python
906    gradient2 = gradient2_python   
907
908
909if __name__ == "__main__":
910    pass
911
912
913
914
Note: See TracBrowser for help on using the repository browser.