source: anuga_core/source/anuga/utilities/polygon.py @ 6189

Last change on this file since 6189 was 6189, checked in by ole, 15 years ago

Implemented interpolate_polyline in C. Hopefully, the STS file boundary will now run much faster.

File size: 34.3 KB
Line 
1#!/usr/bin/env python
2"""Polygon manipulations
3"""
4
5import Numeric as num
6
7from math import sqrt
8from anuga.utilities.numerical_tools import ensure_numeric
9from anuga.geospatial_data.geospatial_data import ensure_absolute, Geospatial_data
10
11
12def point_on_line(point, line, rtol=1.0e-5, atol=1.0e-8):
13    """Determine whether a point is on a line segment
14
15    Input:
16        point is given by [x, y]
17        line is given by [x0, y0], [x1, y1]] or
18        the equivalent 2x2 Numeric array with each row corresponding to a point.
19
20    Output:
21
22    Note: Line can be degenerate and function still works to discern coinciding points from non-coinciding.
23    """
24
25    point = ensure_numeric(point)
26    line = ensure_numeric(line)
27
28    res = _point_on_line(point[0], point[1],
29                         line[0,0], line[0,1],
30                         line[1,0], line[1,1],
31                         rtol, atol)
32   
33    return bool(res)
34
35
36######
37# Result functions used in intersection() below for collinear lines.
38# (p0,p1) defines line 0, (p2,p3) defines line 1.
39######
40
41# result functions for possible states
42def lines_dont_coincide(p0,p1,p2,p3):               return (3, None)
43def lines_0_fully_included_in_1(p0,p1,p2,p3):       return (2, num.array([p0,p1]))
44def lines_1_fully_included_in_0(p0,p1,p2,p3):       return (2, num.array([p2,p3]))
45def lines_overlap_same_direction(p0,p1,p2,p3):      return (2, num.array([p0,p3]))
46def lines_overlap_same_direction2(p0,p1,p2,p3):     return (2, num.array([p2,p1]))
47def lines_overlap_opposite_direction(p0,p1,p2,p3):  return (2, num.array([p0,p2]))
48def lines_overlap_opposite_direction2(p0,p1,p2,p3): return (2, num.array([p3,p1]))
49
50# this function called when an impossible state is found
51def lines_error(p1, p2, p3, p4): raise RuntimeError, ("INTERNAL ERROR: p1=%s, p2=%s, p3=%s, p4=%s" %
52                                                      (str(p1), str(p2), str(p3), str(p4)))
53
54#                     0s1    0e1    1s0    1e0   # line 0 starts on 1, 0 ends 1, 1 starts 0, 1 ends 0
55collinear_result = { (False, False, False, False): lines_dont_coincide,
56                     (False, False, False, True ): lines_error,
57                     (False, False, True,  False): lines_error,
58                     (False, False, True,  True ): lines_1_fully_included_in_0,
59                     (False, True,  False, False): lines_error,
60                     (False, True,  False, True ): lines_overlap_opposite_direction2,
61                     (False, True,  True,  False): lines_overlap_same_direction2,
62                     (False, True,  True,  True ): lines_1_fully_included_in_0,
63                     (True,  False, False, False): lines_error,
64                     (True,  False, False, True ): lines_overlap_same_direction,
65                     (True,  False, True,  False): lines_overlap_opposite_direction,
66                     (True,  False, True,  True ): lines_1_fully_included_in_0,
67                     (True,  True,  False, False): lines_0_fully_included_in_1,
68                     (True,  True,  False, True ): lines_0_fully_included_in_1,
69                     (True,  True,  True,  False): lines_0_fully_included_in_1,
70                     (True,  True,  True,  True ): lines_0_fully_included_in_1
71                   }
72
73def intersection(line0, line1, rtol=1.0e-5, atol=1.0e-8):
74    """Returns intersecting point between two line segments or None
75    (if parallel or no intersection is found).
76
77    However, if parallel lines coincide partly (i.e. shara a common segment,
78    the line segment where lines coincide is returned
79   
80
81    Inputs:
82        line0, line1: Each defined by two end points as in: [[x0, y0], [x1, y1]]
83                      A line can also be a 2x2 numpy array with each row
84                      corresponding to a point.
85
86
87    Output:
88        status, value
89
90        where status and value is interpreted as follows
91       
92        status == 0: no intersection, value set to None.
93        status == 1: intersection point found and returned in value as [x,y].
94        status == 2: Collinear overlapping lines found. Value takes the form [[x0,y0], [x1,y1]].
95        status == 3: Collinear non-overlapping lines. Value set to None.
96        status == 4: Lines are parallel with a fixed distance apart. Value set to None.
97   
98    """
99
100    # FIXME (Ole): Write this in C
101
102    line0 = ensure_numeric(line0, num.Float)
103    line1 = ensure_numeric(line1, num.Float)   
104
105    x0 = line0[0,0]; y0 = line0[0,1]
106    x1 = line0[1,0]; y1 = line0[1,1]
107
108    x2 = line1[0,0]; y2 = line1[0,1]
109    x3 = line1[1,0]; y3 = line1[1,1]
110
111    denom = (y3-y2)*(x1-x0) - (x3-x2)*(y1-y0)
112    u0 = (x3-x2)*(y0-y2) - (y3-y2)*(x0-x2)
113    u1 = (x2-x0)*(y1-y0) - (y2-y0)*(x1-x0)
114       
115    if num.allclose(denom, 0.0, rtol=rtol, atol=atol):
116        # Lines are parallel - check if they are collinear
117        if num.allclose([u0, u1], 0.0, rtol=rtol, atol=atol):
118            # We now know that the lines are collinear
119            state_tuple = (point_on_line([x0, y0], line1, rtol=rtol, atol=atol),
120                           point_on_line([x1, y1], line1, rtol=rtol, atol=atol),
121                           point_on_line([x2, y2], line0, rtol=rtol, atol=atol),
122                           point_on_line([x3, y3], line0, rtol=rtol, atol=atol))
123
124            return collinear_result[state_tuple]([x0,y0],[x1,y1],[x2,y2],[x3,y3])
125        else:
126            # Lines are parallel but aren't collinear
127            return 4, None #FIXME (Ole): Add distance here instead of None
128    else:
129        # Lines are not parallel, check if they intersect
130        u0 = u0/denom
131        u1 = u1/denom       
132
133        x = x0 + u0*(x1-x0)
134        y = y0 + u0*(y1-y0)
135
136        # Sanity check - can be removed to speed up if needed
137        assert num.allclose(x, x2 + u1*(x3-x2), rtol=rtol, atol=atol)
138        assert num.allclose(y, y2 + u1*(y3-y2), rtol=rtol, atol=atol)       
139
140        # Check if point found lies within given line segments
141        if 0.0 <= u0 <= 1.0 and 0.0 <= u1 <= 1.0: 
142            # We have intersection
143            return 1, num.array([x, y])
144        else:
145            # No intersection
146            return 0, None
147
148
149def NEW_C_intersection(line0, line1):
150    #FIXME(Ole): To write in C
151    """Returns intersecting point between two line segments or None
152    (if parallel or no intersection is found).
153
154    However, if parallel lines coincide partly (i.e. shara a common segment,
155    the line segment where lines coincide is returned
156   
157
158    Inputs:
159        line0, line1: Each defined by two end points as in: [[x0, y0], [x1, y1]]
160                      A line can also be a 2x2 numeric array with each row
161                      corresponding to a point.
162
163
164    Output:
165        status, value
166
167        where status is interpreted as follows
168       
169        status == 0: no intersection with value set to None
170        status == 1: One intersection point found and returned in value as [x,y]
171        status == 2: Coinciding line segment found. Value taks the form [[x0,y0], [x1,y1]]
172        status == 3: Lines would coincide but only if extended. Value set to None
173        status == 4: Lines are parallel with a fixed distance apart. Value set to None.
174   
175    """
176
177
178    line0 = ensure_numeric(line0, num.Float)
179    line1 = ensure_numeric(line1, num.Float)   
180
181    status, value = _intersection(line0[0,0], line0[0,1],
182                                  line0[1,0], line0[1,1],
183                                  line1[0,0], line1[0,1],
184                                  line1[1,0], line1[1,1])
185
186    return status, value
187
188
189
190
191def is_inside_polygon(point, polygon, closed=True, verbose=False):
192    """Determine if one point is inside a polygon
193
194    See inside_polygon for more details
195    """
196
197    indices = inside_polygon(point, polygon, closed, verbose)
198
199    if indices.shape[0] == 1:
200        return True
201    elif indices.shape[0] == 0:
202        return False
203    else:
204        msg = 'is_inside_polygon must be invoked with one point only'
205        raise msg
206   
207
208def inside_polygon(points, polygon, closed=True, verbose=False):
209    """Determine points inside a polygon
210
211       Functions inside_polygon and outside_polygon have been defined in
212       terms af separate_by_polygon which will put all inside indices in
213       the first part of the indices array and outside indices in the last
214
215       See separate_points_by_polygon for documentation
216
217       points and polygon can be a geospatial instance,
218       a list or a numeric array
219    """
220
221    #if verbose: print 'Checking input to inside_polygon'
222
223    try:
224        points = ensure_absolute(points)
225    except NameError, e:
226        raise NameError, e
227    except:
228        # If this fails it is going to be because the points can't be
229        # converted to a numeric array.
230        msg = 'Points could not be converted to Numeric array' 
231        raise msg
232
233    try:
234        polygon = ensure_absolute(polygon)
235    except NameError, e:
236        raise NameError, e
237    except:
238        # If this fails it is going to be because the points can't be
239        # converted to a numeric array.
240        msg = 'Polygon %s could not be converted to Numeric array' %(str(polygon))
241        raise msg
242
243    if len(points.shape) == 1:
244        # Only one point was passed in. Convert to array of points
245        points = num.reshape(points, (1,2))
246
247    indices, count = separate_points_by_polygon(points, polygon,
248                                                closed=closed,
249                                                verbose=verbose)
250
251    # Return indices of points inside polygon
252    return indices[:count]
253
254
255
256def is_outside_polygon(point, polygon, closed=True, verbose=False,
257                       points_geo_ref=None, polygon_geo_ref=None):
258    """Determine if one point is outside a polygon
259
260    See outside_polygon for more details
261    """
262
263    indices = outside_polygon(point, polygon, closed, verbose)
264                              #points_geo_ref, polygon_geo_ref)
265
266    if indices.shape[0] == 1:
267        return True
268    elif indices.shape[0] == 0:
269        return False
270    else:
271        msg = 'is_outside_polygon must be invoked with one point only'
272        raise msg
273   
274
275def outside_polygon(points, polygon, closed = True, verbose = False):
276    """Determine points outside a polygon
277
278       Functions inside_polygon and outside_polygon have been defined in
279       terms af separate_by_polygon which will put all inside indices in
280       the first part of the indices array and outside indices in the last
281
282       See separate_points_by_polygon for documentation
283    """
284
285    #if verbose: print 'Checking input to outside_polygon'
286    try:
287        points = ensure_numeric(points, num.Float)
288    except NameError, e:
289        raise NameError, e
290    except:
291        msg = 'Points could not be converted to Numeric array'
292        raise msg
293
294    try:
295        polygon = ensure_numeric(polygon, num.Float)
296    except NameError, e:
297        raise NameError, e
298    except:
299        msg = 'Polygon could not be converted to Numeric array'
300        raise msg
301
302
303    if len(points.shape) == 1:
304        # Only one point was passed in. Convert to array of points
305        points = num.reshape(points, (1,2))
306
307    indices, count = separate_points_by_polygon(points, polygon,
308                                                closed=closed,
309                                                verbose=verbose)
310
311    # Return indices of points outside polygon
312    if count == len(indices):
313        # No points are outside
314        return num.array([])
315    else:
316        return indices[count:][::-1]  #return reversed
317       
318
319def in_and_outside_polygon(points, polygon, closed = True, verbose = False):
320    """Determine points inside and outside a polygon
321
322       See separate_points_by_polygon for documentation
323
324       Returns an array of points inside and an array of points outside the polygon
325    """
326
327    #if verbose: print 'Checking input to outside_polygon'
328    try:
329        points = ensure_numeric(points, num.Float)
330    except NameError, e:
331        raise NameError, e
332    except:
333        msg = 'Points could not be converted to Numeric array'
334        raise msg
335
336    try:
337        polygon = ensure_numeric(polygon, num.Float)
338    except NameError, e:
339        raise NameError, e
340    except:
341        msg = 'Polygon could not be converted to Numeric array'
342        raise msg
343
344    if len(points.shape) == 1:
345        # Only one point was passed in. Convert to array of points
346        points = num.reshape(points, (1,2))
347
348
349    indices, count = separate_points_by_polygon(points, polygon,
350                                                closed=closed,
351                                                verbose=verbose)
352   
353    # Returns indices of points inside and indices of points outside
354    # the polygon
355
356    if count == len(indices):
357        # No points are outside
358        return indices[:count],[]
359    else:
360        return  indices[:count], indices[count:][::-1]  #return reversed
361
362
363def separate_points_by_polygon(points, polygon,
364                               closed = True, verbose = False):
365    """Determine whether points are inside or outside a polygon
366
367    Input:
368       points - Tuple of (x, y) coordinates, or list of tuples
369       polygon - list of vertices of polygon
370       closed - (optional) determine whether points on boundary should be
371       regarded as belonging to the polygon (closed = True)
372       or not (closed = False)
373
374    Outputs:
375       indices: array of same length as points with indices of points falling
376       inside the polygon listed from the beginning and indices of points
377       falling outside listed from the end.
378
379       count: count of points falling inside the polygon
380
381       The indices of points inside are obtained as indices[:count]
382       The indices of points outside are obtained as indices[count:]
383
384
385    Examples:
386       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square
387
388       separate_points_by_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)
389       will return the indices [0, 2, 1] and count == 2 as only the first
390       and the last point are inside the unit square
391
392    Remarks:
393       The vertices may be listed clockwise or counterclockwise and
394       the first point may optionally be repeated.
395       Polygons do not need to be convex.
396       Polygons can have holes in them and points inside a hole is
397       regarded as being outside the polygon.
398
399    Algorithm is based on work by Darel Finley,
400    http://www.alienryderflex.com/polygon/
401
402    Uses underlying C-implementation in polygon_ext.c
403    """
404
405
406    #if verbose: print 'Checking input to separate_points_by_polygon'
407
408
409    #Input checks
410
411    assert isinstance(closed, bool), 'Keyword argument "closed" must be boolean'
412    assert isinstance(verbose, bool), 'Keyword argument "verbose" must be boolean'
413
414
415    try:
416        points = ensure_numeric(points, num.Float)
417    except NameError, e:
418        raise NameError, e
419    except:
420        msg = 'Points could not be converted to Numeric array'
421        raise msg
422
423    #if verbose: print 'Checking input to separate_points_by_polygon 2'
424    try:
425        polygon = ensure_numeric(polygon, num.Float)
426    except NameError, e:
427        raise NameError, e
428    except:
429        msg = 'Polygon could not be converted to Numeric array'
430        raise msg
431
432    msg = 'Polygon array must be a 2d array of vertices'
433    assert len(polygon.shape) == 2, msg
434
435    msg = 'Polygon array must have two columns' 
436    assert polygon.shape[1] == 2, msg
437
438
439    msg = 'Points array must be 1 or 2 dimensional.'
440    msg += ' I got %d dimensions' %len(points.shape)
441    assert 0 < len(points.shape) < 3, msg
442
443
444    if len(points.shape) == 1:
445        # Only one point was passed in.
446        # Convert to array of points
447        points = num.reshape(points, (1,2))
448
449   
450    msg = 'Point array must have two columns (x,y), '
451    msg += 'I got points.shape[1] == %d' %points.shape[0]
452    assert points.shape[1] == 2, msg
453
454       
455    msg = 'Points array must be a 2d array. I got %s' %str(points[:30])
456    assert len(points.shape) == 2, msg
457
458    msg = 'Points array must have two columns'
459    assert points.shape[1] == 2, msg
460
461
462    N = polygon.shape[0] #Number of vertices in polygon
463    M = points.shape[0]  #Number of points
464
465
466    indices = num.zeros( M, num.Int )
467
468    count = _separate_points_by_polygon(points, polygon, indices,
469                                        int(closed), int(verbose))
470
471    if verbose: print 'Found %d points (out of %d) inside polygon'\
472       %(count, M)
473    return indices, count
474
475
476def polygon_area(input_polygon):
477    """ Determin area of arbitrary polygon
478    Reference
479    http://mathworld.wolfram.com/PolygonArea.html
480    """
481   
482    # Move polygon to origin (0,0) to avoid rounding errors
483    # This makes a copy of the polygon to avoid destroying it
484    input_polygon = ensure_numeric(input_polygon)
485    min_x = min(input_polygon[:,0])
486    min_y = min(input_polygon[:,1])   
487    polygon = input_polygon - [min_x, min_y]
488
489    # Compute area   
490    n = len(polygon)
491    poly_area = 0.0
492
493    for i in range(n):
494        pti = polygon[i]
495        if i == n-1:
496            pt1 = polygon[0]
497        else:
498            pt1 = polygon[i+1]
499        xi = pti[0]
500        yi1 = pt1[1]
501        xi1 = pt1[0]
502        yi = pti[1]
503        poly_area += xi*yi1 - xi1*yi
504       
505    return abs(poly_area/2)
506
507def plot_polygons(polygons_points, style=None, 
508                  figname=None, label=None, verbose=False):
509   
510    """ Take list of polygons and plot.
511
512    Inputs:
513
514    polygons         - list of polygons
515
516    style            - style list corresponding to each polygon
517                     - for a polygon, use 'line'
518                     - for points falling outside a polygon, use 'outside'
519                       
520    figname          - name to save figure to
521
522    label            - title for plot
523
524    Outputs:
525
526    - list of min and max of x and y coordinates
527    - plot of polygons
528    """ 
529
530    from pylab import ion, hold, plot, axis, figure, legend, savefig, xlabel, ylabel, title, close, title
531
532    assert type(polygons_points) == list,\
533               'input must be a list of polygons and/or points'
534               
535    ion()
536    hold(True)
537
538    minx = 1e10
539    maxx = 0.0
540    miny = 1e10
541    maxy = 0.0
542
543    if label is None: label = ''
544
545    n = len(polygons_points)
546    colour = []
547    if style is None:
548        style_type = 'line' 
549        style = []
550        for i in range(n):
551            style.append(style_type)
552            colour.append('b-')
553    else:
554        for s in style:
555            if s == 'line': colour.append('b-')           
556            if s == 'outside': colour.append('r.')
557            if s <> 'line':
558                if s <> 'outside':
559                    colour.append('g.')
560           
561    for i, item in enumerate(polygons_points):
562        x, y = poly_xy(item)
563        if min(x) < minx: minx = min(x)
564        if max(x) > maxx: maxx = max(x)
565        if min(y) < miny: miny = min(y)
566        if max(y) > maxy: maxy = max(y)
567        plot(x,y,colour[i])
568        xlabel('x')
569        ylabel('y')
570        title(label)
571
572    #raw_input('wait 1')
573    #FIXME(Ole): This makes for some strange scalings sometimes.
574    #if minx <> 0:
575    #    axis([minx*0.9,maxx*1.1,miny*0.9,maxy*1.1])
576    #else:
577    #    if miny == 0:
578    #        axis([-maxx*.01,maxx*1.1,-maxy*0.01,maxy*1.1])
579    #    else:
580    #        axis([-maxx*.01,maxx*1.1,miny*0.9,maxy*1.1])
581
582    if figname is not None:
583        savefig(figname)
584    else:
585        savefig('test_image')
586
587    close('all')
588
589    vec = [minx,maxx,miny,maxy]
590
591    return vec
592
593def poly_xy(polygon, verbose=False):
594    """ this is used within plot_polygons so need to duplicate
595        the first point so can have closed polygon in plot
596    """
597
598    #if verbose: print 'Checking input to poly_xy'
599
600    try:
601        polygon = ensure_numeric(polygon, num.Float)
602    except NameError, e:
603        raise NameError, e
604    except:
605        msg = 'Polygon %s could not be converted to Numeric array' %(str(polygon))
606        raise msg
607
608    x = polygon[:,0]
609    y = polygon[:,1]
610    x = num.concatenate((x, [polygon[0,0]]), axis = 0)
611    y = num.concatenate((y, [polygon[0,1]]), axis = 0)
612   
613    return x, y
614   
615#    x = []
616#    y = []
617#    n = len(poly)
618#    firstpt = poly[0]
619#    for i in range(n):
620#        thispt = poly[i]
621#        x.append(thispt[0])
622#        y.append(thispt[1])
623
624#    x.append(firstpt[0])
625#    y.append(firstpt[1])
626   
627#    return x, y
628
629class Polygon_function:
630    """Create callable object f: x,y -> z, where a,y,z are vectors and
631    where f will return different values depending on whether x,y belongs
632    to specified polygons.
633
634    To instantiate:
635
636       Polygon_function(polygons)
637
638    where polygons is a list of tuples of the form
639
640      [ (P0, v0), (P1, v1), ...]
641
642      with Pi being lists of vertices defining polygons and vi either
643      constants or functions of x,y to be applied to points with the polygon.
644
645    The function takes an optional argument, default which is the value
646    (or function) to used for points not belonging to any polygon.
647    For example:
648
649       Polygon_function(polygons, default = 0.03)
650
651    If omitted the default value will be 0.0
652
653    Note: If two polygons overlap, the one last in the list takes precedence
654
655    Coordinates specified in the call are assumed to be relative to the
656    origin (georeference) e.g. used by domain.
657    By specifying the optional argument georeference,
658    all points are made relative.
659
660    FIXME: This should really work with geo_spatial point sets.
661    """
662
663    def __init__(self, regions, default=0.0, geo_reference=None):
664
665        try:
666            len(regions)
667        except:
668            msg = 'Polygon_function takes a list of pairs (polygon, value).'
669            msg += 'Got %s' %regions
670            raise msg
671
672
673        T = regions[0]
674
675        if isinstance(T, basestring):
676            msg = 'You passed in a list of text values into polygon_function'
677            msg += ' instead of a list of pairs (polygon, value): "%s"' %T
678           
679            raise Exception, msg
680       
681        try:
682            a = len(T)
683        except:
684            msg = 'Polygon_function takes a list of pairs (polygon, value).'
685            msg += 'Got %s' %str(T)
686            raise msg
687
688        msg = 'Each entry in regions have two components: (polygon, value).'
689        msg +='I got %s' %str(T)
690        assert a == 2, msg
691
692
693        if geo_reference is None:
694            from anuga.coordinate_transforms.geo_reference import Geo_reference
695            geo_reference = Geo_reference()
696
697
698        self.default = default
699
700        # Make points in polygons relative to geo_reference
701        self.regions = []
702        for polygon, value in regions:
703            P = geo_reference.change_points_geo_ref(polygon)
704            self.regions.append( (P, value) )
705
706
707
708
709    def __call__(self, x, y):
710        x = num.array(x, num.Float)
711        y = num.array(y, num.Float)
712
713        N = len(x)
714        assert len(y) == N
715
716        points = num.concatenate( (num.reshape(x, (N, 1)),
717                                   num.reshape(y, (N, 1))), axis=1 )
718
719        if callable(self.default):
720            z = self.default(x,y)
721        else:
722            z = num.ones(N, num.Float) * self.default
723
724        for polygon, value in self.regions:
725            indices = inside_polygon(points, polygon)
726
727            # FIXME: This needs to be vectorised
728            if callable(value):
729                for i in indices:
730                    xx = num.array([x[i]])
731                    yy = num.array([y[i]])
732                    z[i] = value(xx, yy)[0]
733            else:
734                for i in indices:
735                    z[i] = value
736
737        return z
738
739
740       
741# Functions to read and write polygon information       
742def read_polygon(filename, split=','):
743    """Read points assumed to form a polygon.
744       There must be exactly two numbers in each line separated by a comma.
745       No header.
746    """
747
748    fid = open(filename)
749    lines = fid.readlines()
750    fid.close()
751    polygon = []
752    for line in lines:
753        fields = line.split(split)
754        polygon.append( [float(fields[0]), float(fields[1])] )
755
756    return polygon
757
758
759def write_polygon(polygon, filename=None):
760    """Write polygon to csv file.
761       There will be exactly two numbers, easting and northing,
762       in each line separated by a comma.
763       
764       No header.   
765    """
766
767    fid = open(filename, 'w')
768    for point in polygon:
769        fid.write('%f, %f\n' %point)
770    fid.close()
771   
772
773def read_tagged_polygons(filename):
774    """
775    """
776    pass
777   
778def populate_polygon(polygon, number_of_points, seed=None, exclude=None):
779    """Populate given polygon with uniformly distributed points.
780
781    Input:
782       polygon - list of vertices of polygon
783       number_of_points - (optional) number of points
784       seed - seed for random number generator (default=None)
785       exclude - list of polygons (inside main polygon) from where points should be excluded
786
787    Output:
788       points - list of points inside polygon
789
790    Examples:
791       populate_polygon( [[0,0], [1,0], [1,1], [0,1]], 5 )
792       will return five randomly selected points inside the unit square
793    """
794
795    from random import uniform, seed as seed_function
796
797    seed_function(seed)
798
799    points = []
800
801    # Find outer extent of polygon
802    max_x = min_x = polygon[0][0]
803    max_y = min_y = polygon[0][1]
804    for point in polygon[1:]:
805        x = point[0]
806        if x > max_x: max_x = x
807        if x < min_x: min_x = x
808        y = point[1]
809        if y > max_y: max_y = y
810        if y < min_y: min_y = y
811
812
813    while len(points) < number_of_points:
814        x = uniform(min_x, max_x)
815        y = uniform(min_y, max_y)
816
817        append = False
818        if is_inside_polygon([x,y], polygon):
819
820            append = True
821
822            #Check exclusions
823            if exclude is not None:
824                for ex_poly in exclude:
825                    if is_inside_polygon([x,y], ex_poly):
826                        append = False
827
828
829        if append is True:
830            points.append([x,y])
831
832    return points
833
834
835def point_in_polygon(polygon, delta=1e-8):
836    """Return a point inside a given polygon which will be close to the
837    polygon edge.
838
839    Input:
840       polygon - list of vertices of polygon
841       delta - the square root of 2 * delta is the maximum distance from the
842       polygon points and the returned point.
843    Output:
844       points - a point inside polygon
845
846       searches in all diagonals and up and down (not left and right)
847    """
848    import exceptions
849    class Found(exceptions.Exception): pass
850
851    point_in = False
852    while not point_in:
853        try:
854            for poly_point in polygon: #[1:]:
855                for x_mult in range (-1,2):
856                    for y_mult in range (-1,2):
857                        x = poly_point[0]
858                        y = poly_point[1]
859                        if x == 0:
860                            x_delta = x_mult*delta
861                        else:
862                            x_delta = x+x_mult*x*delta
863
864                        if y == 0:
865                            y_delta = y_mult*delta
866                        else:
867                            y_delta = y+y_mult*y*delta
868
869                        point = [x_delta, y_delta]
870                        #print "point",point
871                        if is_inside_polygon(point, polygon, closed=False):
872                            raise Found
873        except Found:
874            point_in = True
875        else:
876            delta = delta*0.1
877    return point
878
879
880def number_mesh_triangles(interior_regions, bounding_poly, remainder_res):
881    """Calculate the approximate number of triangles inside the
882    bounding polygon and the other interior regions
883
884    Polygon areas are converted to square Kms
885
886    FIXME: Add tests for this function
887    """
888   
889    from anuga.utilities.polygon import polygon_area
890
891
892    # TO DO check if any of the regions fall inside one another
893
894    print '----------------------------------------------------------------------------'
895    print 'Polygon   Max triangle area (m^2)   Total area (km^2)   Estimated #triangles'
896    print '----------------------------------------------------------------------------'   
897       
898    no_triangles = 0.0
899    area = polygon_area(bounding_poly)
900   
901    for poly, resolution in interior_regions:
902        this_area = polygon_area(poly)
903        this_triangles = this_area/resolution
904        no_triangles += this_triangles
905        area -= this_area
906       
907        print 'Interior ',
908        print ('%.0f' %resolution).ljust(25),
909        print ('%.2f' %(this_area/1000000)).ljust(19),
910        print '%d' %(this_triangles)
911       
912    bound_triangles = area/remainder_res
913    no_triangles += bound_triangles
914
915    print 'Bounding ',
916    print ('%.0f' %remainder_res).ljust(25),
917    print ('%.2f' %(area/1000000)).ljust(19),
918    print '%d' %(bound_triangles)   
919
920    total_number_of_triangles = no_triangles/0.7
921
922    print 'Estimated total number of triangles: %d' %total_number_of_triangles
923    print 'Note: This is generally about 20% less than the final amount'   
924
925    return int(total_number_of_triangles)
926
927
928def decimate_polygon(polygon, factor=10):
929    """Reduce number of points in polygon by the specified
930    factor (default=10, hence the name of the function) such that
931    the extrema in both axes are preserved.
932
933    Return reduced polygon
934    """
935
936    # FIXME(Ole): This doesn't work at present,
937    # but it isn't critical either
938
939    # Find outer extent of polygon
940    num_polygon = ensure_numeric(polygon)
941    max_x = max(num_polygon[:,0])
942    max_y = max(num_polygon[:,1])
943    min_x = min(num_polygon[:,0])
944    min_y = min(num_polygon[:,1])       
945
946    # Keep only some points making sure extrema are kept
947    reduced_polygon = []   
948    for i, point in enumerate(polygon):
949        x = point[0]
950        y = point[1]       
951        if x in [min_x, max_x] and y in [min_y, max_y]:
952            # Keep
953            reduced_polygon.append(point)
954        else:
955            if len(reduced_polygon)*factor < i:
956                reduced_polygon.append(point)               
957
958    return reduced_polygon
959   
960   
961       
962
963##
964# @brief Interpolate linearly from polyline nodes to midpoints of triangles.
965# @param data The data on the polyline nodes.
966# @param polyline_nodes ??
967# @param gauge_neighbour_id ??  FIXME(Ole): I want to get rid of this
968# @param point_coordinates ??
969# @param verbose True if this function is to be verbose.
970def interpolate_polyline(data,
971                         polyline_nodes,
972                         gauge_neighbour_id,
973                         interpolation_points=None,
974                         rtol=1.0e-6,
975                         atol=1.0e-8,
976                         verbose=False):
977    """Interpolate linearly between values data on polyline nodes
978    of a polyline to list of interpolation points.
979
980    data is the data on the polyline nodes.
981
982
983    Inputs:
984      data: Vector or array of data at the polyline nodes.
985      polyline_nodes: Location of nodes where data is available.     
986      gauge_neighbour_id: ?
987      interpolation_points: Interpolate polyline data to these positions.
988          List of coordinate pairs [x, y] of
989          data points or an nx2 Numeric array or a Geospatial_data object
990      rtol, atol: Used to determine whether a point is on the polyline or not. See point_on_line.
991
992    Output:
993      Interpolated values at interpolation points
994    """
995   
996    if isinstance(interpolation_points, Geospatial_data):
997        interpolation_points = interpolation_points.get_data_points(absolute=True)
998
999    interpolated_values = num.zeros(len(interpolation_points), num.Float)
1000
1001    data = ensure_numeric(data, num.Float)
1002    polyline_nodes = ensure_numeric(polyline_nodes, num.Float)
1003    interpolation_points = ensure_numeric(interpolation_points, num.Float)
1004    gauge_neighbour_id = ensure_numeric(gauge_neighbour_id, num.Int)
1005
1006    n = polyline_nodes.shape[0] # Number of nodes in polyline       
1007    # Input sanity check
1008    msg = 'interpolation_points are not given (interpolate.py)'
1009    assert interpolation_points is not None, msg
1010    msg = 'function value must be specified at every interpolation node'
1011    assert data.shape[0]==polyline_nodes.shape[0], msg
1012    msg = 'Must define function value at one or more nodes'
1013    assert data.shape[0]>0, msg
1014
1015    if n == 1:
1016        msg = 'Polyline contained only one point. I need more. ' + str(data)
1017        raise Exception, msg
1018    elif n > 1:
1019        _interpolate_polyline(data,
1020                              polyline_nodes,
1021                              gauge_neighbour_id,
1022                              interpolation_points,                               
1023                              interpolated_values,
1024                              rtol,
1025                              atol)
1026       
1027    return interpolated_values
1028
1029       
1030def _interpolate_polyline(data,
1031                          polyline_nodes, 
1032                          gauge_neighbour_id, 
1033                          interpolation_points, 
1034                          interpolated_values,
1035                          rtol=1.0e-6,
1036                          atol=1.0e-8):
1037    """Auxiliary function used by interpolate_polyline
1038   
1039    NOTE: OBSOLETED BY C-EXTENSION
1040    """
1041   
1042    number_of_nodes = len(polyline_nodes)               
1043    number_of_points = len(interpolation_points)
1044   
1045    for j in range(number_of_nodes):               
1046        neighbour_id = gauge_neighbour_id[j]
1047       
1048        # FIXME(Ole): I am convinced that gauge_neighbour_id can be discarded, but need to check with John J.
1049        # Keep it for now (17 Jan 2009)
1050        # When gone, we can simply interpolate between neighbouring nodes, i.e. neighbour_id = j+1.
1051        # and the test below becomes something like: if j < number_of_nodes... 
1052       
1053        if neighbour_id >= 0:
1054            x0, y0 = polyline_nodes[j,:]
1055            x1, y1 = polyline_nodes[neighbour_id,:]
1056           
1057            segment_len = sqrt((x1-x0)**2 + (y1-y0)**2)
1058            segment_delta = data[neighbour_id] - data[j]           
1059            slope = segment_delta/segment_len
1060           
1061               
1062            for i in range(number_of_points):               
1063               
1064                x, y = interpolation_points[i,:]
1065                if point_on_line([x, y], 
1066                                 [[x0, y0], [x1, y1]], 
1067                                 rtol=rtol,
1068                                 atol=atol):
1069                                 
1070
1071                    dist = sqrt((x-x0)**2 + (y-y0)**2)
1072                    interpolated_values[i] = slope*dist + data[j]
1073     
1074
1075   
1076
1077##############################################
1078#Initialise module
1079
1080from anuga.utilities import compile
1081if compile.can_use_C_extension('polygon_ext.c'):
1082    # Underlying C implementations can be accessed
1083    from polygon_ext import _point_on_line
1084    from polygon_ext import _separate_points_by_polygon
1085    from polygon_ext import _interpolate_polyline   
1086    #from polygon_ext import _intersection
1087
1088else:
1089    msg = 'C implementations could not be accessed by %s.\n ' %__file__
1090    msg += 'Make sure compile_all.py has been run as described in '
1091    msg += 'the ANUGA installation guide.'
1092    raise Exception, msg
1093
1094
1095if __name__ == "__main__":
1096    pass
Note: See TracBrowser for help on using the repository browser.