source: anuga_core/source/anuga/geometry/polygon.py @ 7721

Last change on this file since 7721 was 7711, checked in by James Hudson, 15 years ago

Refactored geometry classes to live in their own folder.

File size: 49.2 KB
RevLine 
[5897]1#!/usr/bin/env python
2
[7276]3"""Polygon manipulations"""
[5897]4
[7276]5import numpy as num
6
[5897]7from math import sqrt
8from anuga.utilities.numerical_tools import ensure_numeric
[6189]9from anuga.geospatial_data.geospatial_data import ensure_absolute, Geospatial_data
[7276]10from anuga.config import netcdf_float
[7317]11import anuga.utilities.log as log
[5897]12
13
[7276]14##
15# @brief Determine whether a point is on a line segment.
16# @param point (x, y) of point in question (tuple, list or array).
17# @param line ((x1,y1), (x2,y2)) for line (tuple, list or array).
18# @param rtol Relative error for 'close'.
19# @param atol Absolute error for 'close'.
20# @return True or False.
[5932]21def point_on_line(point, line, rtol=1.0e-5, atol=1.0e-8):
[5897]22    """Determine whether a point is on a line segment
23
[7276]24    Input:
[5897]25        point is given by [x, y]
[7276]26        line is given by [x0, y0], [x1, y1]] or
27        the equivalent 2x2 numeric array with each row corresponding to a point.
[5897]28
29    Output:
30
[7276]31    Note: Line can be degenerate and function still works to discern coinciding
32          points from non-coinciding.
[5897]33    """
34
35    point = ensure_numeric(point)
36    line = ensure_numeric(line)
37
38    res = _point_on_line(point[0], point[1],
39                         line[0,0], line[0,1],
40                         line[1,0], line[1,1],
41                         rtol, atol)
[7276]42
[5897]43    return bool(res)
44
45
[5942]46######
47# Result functions used in intersection() below for collinear lines.
48# (p0,p1) defines line 0, (p2,p3) defines line 1.
49######
[5897]50
[5942]51# result functions for possible states
52def lines_dont_coincide(p0,p1,p2,p3):               return (3, None)
[7276]53def lines_0_fully_included_in_1(p0,p1,p2,p3):       return (2,
54                                                            num.array([p0,p1]))
55def lines_1_fully_included_in_0(p0,p1,p2,p3):       return (2,
56                                                            num.array([p2,p3]))
57def lines_overlap_same_direction(p0,p1,p2,p3):      return (2,
58                                                            num.array([p0,p3]))
59def lines_overlap_same_direction2(p0,p1,p2,p3):     return (2,
60                                                            num.array([p2,p1]))
61def lines_overlap_opposite_direction(p0,p1,p2,p3):  return (2,
62                                                            num.array([p0,p2]))
63def lines_overlap_opposite_direction2(p0,p1,p2,p3): return (2,
64                                                            num.array([p3,p1]))
[5897]65
[5942]66# this function called when an impossible state is found
[7276]67def lines_error(p1, p2, p3, p4):
68    raise RuntimeError, ('INTERNAL ERROR: p1=%s, p2=%s, p3=%s, p4=%s'
69                         % (str(p1), str(p2), str(p3), str(p4)))
[5897]70
[5942]71#                     0s1    0e1    1s0    1e0   # line 0 starts on 1, 0 ends 1, 1 starts 0, 1 ends 0
72collinear_result = { (False, False, False, False): lines_dont_coincide,
73                     (False, False, False, True ): lines_error,
74                     (False, False, True,  False): lines_error,
75                     (False, False, True,  True ): lines_1_fully_included_in_0,
76                     (False, True,  False, False): lines_error,
77                     (False, True,  False, True ): lines_overlap_opposite_direction2,
78                     (False, True,  True,  False): lines_overlap_same_direction2,
79                     (False, True,  True,  True ): lines_1_fully_included_in_0,
80                     (True,  False, False, False): lines_error,
81                     (True,  False, False, True ): lines_overlap_same_direction,
82                     (True,  False, True,  False): lines_overlap_opposite_direction,
83                     (True,  False, True,  True ): lines_1_fully_included_in_0,
84                     (True,  True,  False, False): lines_0_fully_included_in_1,
85                     (True,  True,  False, True ): lines_0_fully_included_in_1,
86                     (True,  True,  True,  False): lines_0_fully_included_in_1,
87                     (True,  True,  True,  True ): lines_0_fully_included_in_1
88                   }
89
[7276]90##
91# @brief Finds intersection point of two line segments.
92# @param line0 First line ((x1,y1), (x2,y2)).
93# @param line1 Second line ((x1,y1), (x2,y2)).
94# @param rtol Relative error for 'close'.
95# @param atol Absolute error for 'close'.
96# @return (status, value) where:
97#             status = 0 - no intersection, value set to None
98#                      1 - intersection found, value=(x,y)
99#                      2 - lines collienar, overlap, value=overlap segment
100#                      3 - lines collinear, no overlap, value is None
101#                      4 - lines parallel, value is None
[5932]102def intersection(line0, line1, rtol=1.0e-5, atol=1.0e-8):
[7276]103    """Returns intersecting point between two line segments.
[5897]104
[7276]105    However, if parallel lines coincide partly (i.e. share a common segment),
[5897]106    the line segment where lines coincide is returned
107
108    Inputs:
109        line0, line1: Each defined by two end points as in: [[x0, y0], [x1, y1]]
[5942]110                      A line can also be a 2x2 numpy array with each row
[5897]111                      corresponding to a point.
112
113    Output:
[7276]114        status, value - where status and value is interpreted as follows:
[5942]115        status == 0: no intersection, value set to None.
116        status == 1: intersection point found and returned in value as [x,y].
[7276]117        status == 2: Collinear overlapping lines found.
118                     Value takes the form [[x0,y0], [x1,y1]].
[5942]119        status == 3: Collinear non-overlapping lines. Value set to None.
[7276]120        status == 4: Lines are parallel. Value set to None.
[5897]121    """
122
123    # FIXME (Ole): Write this in C
124
[7276]125    line0 = ensure_numeric(line0, num.float)
126    line1 = ensure_numeric(line1, num.float)
[5897]127
128    x0 = line0[0,0]; y0 = line0[0,1]
129    x1 = line0[1,0]; y1 = line0[1,1]
130
131    x2 = line1[0,0]; y2 = line1[0,1]
132    x3 = line1[1,0]; y3 = line1[1,1]
133
134    denom = (y3-y2)*(x1-x0) - (x3-x2)*(y1-y0)
135    u0 = (x3-x2)*(y0-y2) - (y3-y2)*(x0-x2)
136    u1 = (x2-x0)*(y1-y0) - (y2-y0)*(x1-x0)
[7276]137
[6158]138    if num.allclose(denom, 0.0, rtol=rtol, atol=atol):
[5942]139        # Lines are parallel - check if they are collinear
[6158]140        if num.allclose([u0, u1], 0.0, rtol=rtol, atol=atol):
[5942]141            # We now know that the lines are collinear
142            state_tuple = (point_on_line([x0, y0], line1, rtol=rtol, atol=atol),
143                           point_on_line([x1, y1], line1, rtol=rtol, atol=atol),
144                           point_on_line([x2, y2], line0, rtol=rtol, atol=atol),
145                           point_on_line([x3, y3], line0, rtol=rtol, atol=atol))
[5897]146
[7276]147            return collinear_result[state_tuple]([x0,y0], [x1,y1],
148                                                 [x2,y2], [x3,y3])
[5897]149        else:
[5942]150            # Lines are parallel but aren't collinear
[7276]151            return 4, None #FIXME (Ole): Add distance here instead of None
[5897]152    else:
[5942]153        # Lines are not parallel, check if they intersect
[5897]154        u0 = u0/denom
[7276]155        u1 = u1/denom
[5897]156
157        x = x0 + u0*(x1-x0)
158        y = y0 + u0*(y1-y0)
159
160        # Sanity check - can be removed to speed up if needed
[6158]161        assert num.allclose(x, x2 + u1*(x3-x2), rtol=rtol, atol=atol)
[7276]162        assert num.allclose(y, y2 + u1*(y3-y2), rtol=rtol, atol=atol)
[5897]163
164        # Check if point found lies within given line segments
[7276]165        if 0.0 <= u0 <= 1.0 and 0.0 <= u1 <= 1.0:
[5897]166            # We have intersection
[6158]167            return 1, num.array([x, y])
[5897]168        else:
169            # No intersection
170            return 0, None
171
[7276]172##
173# @brief Finds intersection point of two line segments.
174# @param line0 First line ((x1,y1), (x2,y2)).
175# @param line1 Second line ((x1,y1), (x2,y2)).
176# @return (status, value) where:
177#             status = 0 - no intersection, value set to None
178#                      1 - intersection found, value=(x,y)
179#                      2 - lines collienar, overlap, value=overlap segment
180#                      3 - lines collinear, no overlap, value is None
181#                      4 - lines parallel, value is None
182# @note Wrapper for C function.
[5897]183def NEW_C_intersection(line0, line1):
[7276]184    """Returns intersecting point between two line segments.
[5897]185
[7276]186    However, if parallel lines coincide partly (i.e. share a common segment),
[5897]187    the line segment where lines coincide is returned
188
189    Inputs:
190        line0, line1: Each defined by two end points as in: [[x0, y0], [x1, y1]]
[7276]191                      A line can also be a 2x2 numpy array with each row
[5897]192                      corresponding to a point.
193
194    Output:
[7276]195        status, value - where status and value is interpreted as follows:
196        status == 0: no intersection, value set to None.
197        status == 1: intersection point found and returned in value as [x,y].
198        status == 2: Collinear overlapping lines found.
199                     Value takes the form [[x0,y0], [x1,y1]].
200        status == 3: Collinear non-overlapping lines. Value set to None.
201        status == 4: Lines are parallel. Value set to None.
[5897]202    """
203
[7276]204    line0 = ensure_numeric(line0, num.float)
205    line1 = ensure_numeric(line1, num.float)
[5897]206
207    status, value = _intersection(line0[0,0], line0[0,1],
208                                  line0[1,0], line0[1,1],
209                                  line1[0,0], line1[0,1],
210                                  line1[1,0], line1[1,1])
211
212    return status, value
213
[6534]214def is_inside_triangle(point, triangle, 
215                       closed=True, 
[6535]216                       rtol=1.0e-12,
217                       atol=1.0e-12,                     
[6534]218                       check_inputs=True, 
219                       verbose=False):
220    """Determine if one point is inside a triangle
221   
222    This uses the barycentric method:
223   
224    Triangle is A, B, C
225    Point P can then be written as
226   
227    P = A + alpha * (C-A) + beta * (B-A)
228    or if we let
229    v=P-A, v0=C-A, v1=B-A   
230   
231    v = alpha*v0 + beta*v1
[5897]232
[6534]233    Dot this equation by v0 and v1 to get two:
234   
235    dot(v0, v) = alpha*dot(v0, v0) + beta*dot(v0, v1)
236    dot(v1, v) = alpha*dot(v1, v0) + beta*dot(v1, v1)   
237   
238    or if a_ij = dot(v_i, v_j) and b_i = dot(v_i, v)
239    the matrix equation:
240   
241    a_00 a_01   alpha     b_0
242                       =
243    a_10 a_11   beta      b_1
244   
245    Solving for alpha and beta yields:
246   
247    alpha = (b_0*a_11 - b_1*a_01)/denom
248    beta =  (b_1*a_00 - b_0*a_10)/denom
249   
250    with denom = a_11*a_00 - a_10*a_01
251   
252    The point is in the triangle whenever
253    alpha and beta and their sums are in the unit interval.
254   
255    rtol and atol will determine how close the point has to be to the edge
[6535]256    before it is deemed to be on the edge.
[6534]257   
258    """
[5897]259
[6544]260    triangle = ensure_numeric(triangle)       
[7276]261    point = ensure_numeric(point, num.float)   
[6544]262   
[6534]263    if check_inputs is True:
264        msg = 'is_inside_triangle must be invoked with one point only'
265        assert num.allclose(point.shape, [2]), msg
266   
[6544]267   
[6541]268    # Use C-implementation
[6535]269    return bool(_is_inside_triangle(point, triangle, int(closed), rtol, atol))
[6534]270   
[6535]271   
272
273    # FIXME (Ole): The rest of this function has been made
[6541]274    # obsolete by the C extension.
[6544]275   
276    # Quickly reject points that are clearly outside
277    if point[0] < min(triangle[:,0]): return False 
278    if point[0] > max(triangle[:,0]): return False   
279    if point[1] < min(triangle[:,1]): return False
280    if point[1] > max(triangle[:,1]): return False       
[7276]281
282
[6534]283    # Start search   
284    A = triangle[0, :]
285    B = triangle[1, :]
286    C = triangle[2, :]
287   
288    # Now check if point lies wholly inside triangle
289    v0 = C-A
290    v1 = B-A       
291       
[7276]292    a00 = num.inner(v0, v0)
293    a10 = a01 = num.inner(v0, v1)
294    a11 = num.inner(v1, v1)
[6534]295   
296    denom = a11*a00 - a01*a10
297   
298    if abs(denom) > 0.0:
299        v = point-A
[7276]300        b0 = num.inner(v0, v)       
301        b1 = num.inner(v1, v)           
[6534]302   
303        alpha = (b0*a11 - b1*a01)/denom
304        beta = (b1*a00 - b0*a10)/denom       
305
306        if (alpha > 0.0) and (beta > 0.0) and (alpha+beta < 1.0):
307            return True 
308
309
310    if closed is True:
311        # Check if point lies on one of the edges
312       
313        for X, Y in [[A,B], [B,C], [C,A]]:
314            res = _point_on_line(point[0], point[1],
315                                 X[0], X[1],
316                                 Y[0], Y[1],
317                                 rtol, atol)
[6535]318           
[6534]319            if res:
320                return True
321               
[6535]322    return False
[6534]323
[7690]324   
[7687]325def is_complex(polygon, verbose=False):
[7690]326    """Check if a polygon is complex (self-intersecting).
327       Uses a sweep algorithm that is O(n^2) in the worst case, but
328       for most normal looking polygons it'll be O(n log n).
329
330       polygon is a list of points that define a closed polygon.
331       verbose will print a list of the intersection points if true
332       
333       Return True if polygon is complex.
334    """           
335           
336    def key_xpos(item):
337        return (item[0][0])
[7686]338   
[7690]339    def segments_joined(seg0, seg1):
340        for i in seg0:
341            for j in seg1:   
342                if i == j: return True
343        return False
344       
[7686]345    polygon = ensure_numeric(polygon, num.float)
346
[7690]347    # build a list of discrete segments from the polygon
348    unsorted_segs = []
[7686]349    for i in range(0, len(polygon)-1):
[7690]350        unsorted_segs.append([list(polygon[i]), list(polygon[i+1])])
351    unsorted_segs.append([list(polygon[0]), list(polygon[-1])])
352   
353    # all segments must point in same direction
354    for val in unsorted_segs:
355        if val[0][0] > val[1][0]:
356            val[0], val[1] = val[1], val[0]   
357           
358    l_x = sorted(unsorted_segs, key=key_xpos)
[7686]359
[7690]360    comparisons = 0
361   
362    # loop through, only comparing lines that partially overlap in x
363    for index, leftmost in enumerate(l_x):
364        cmp = index+1
365        while cmp < len(l_x) and leftmost[1][0] > l_x[cmp][0][0]:
366            if not segments_joined(leftmost, l_x[cmp]):
367                (type, point) = intersection(leftmost, l_x[cmp])
368                comparisons += 1
369                if type != 0 and type != 4 or (type == 2 and list(point[0]) != list(point[1])):
[7687]370                    if verbose:
[7690]371                        print 'Self-intersecting polygon found, type ', type, ' point', point,
372                        print 'vertices: ', leftmost, ' - ', l_x[cmp]               
373                    return True           
374            cmp += 1
[7686]375       
376    return False
377   
[7690]378   
[6534]379def is_inside_polygon_quick(point, polygon, closed=True, verbose=False):
380    """Determine if one point is inside a polygon
381    Both point and polygon are assumed to be numeric arrays or lists and
382    no georeferencing etc or other checks will take place.
383   
384    As such it is faster than is_inside_polygon
385    """
386
387    # FIXME(Ole): This function isn't being used
[7276]388    polygon = ensure_numeric(polygon, num.float)
389    points = ensure_numeric(point, num.float) # Convert point to array of points
390    points = num.ascontiguousarray(points[num.newaxis, :])
391    msg = ('is_inside_polygon() must be invoked with one point only.\n'
392           'I got %s and converted to %s' % (str(point), str(points.shape)))
[6534]393    assert points.shape[0] == 1 and points.shape[1] == 2, msg
394   
[7276]395    indices = num.zeros(1, num.int)
[6534]396
397    count = _separate_points_by_polygon(points, polygon, indices,
398                                        int(closed), int(verbose))
399
[7276]400    return count > 0
[6534]401
[7276]402
[5897]403def is_inside_polygon(point, polygon, closed=True, verbose=False):
404    """Determine if one point is inside a polygon
405
406    See inside_polygon for more details
407    """
408
409    indices = inside_polygon(point, polygon, closed, verbose)
410
411    if indices.shape[0] == 1:
412        return True
413    elif indices.shape[0] == 0:
414        return False
415    else:
416        msg = 'is_inside_polygon must be invoked with one point only'
417        raise msg
418
[7276]419##
420# @brief Determine which of a set of points are inside a polygon.
421# @param points A set of points (tuple, list or array).
422# @param polygon A set of points defining a polygon (tuple, list or array).
423# @param closed True if points on boundary are considered 'inside' polygon.
424# @param verbose True if this function is to be verbose.
425# @return A list of indices of points inside the polygon.
[5897]426def inside_polygon(points, polygon, closed=True, verbose=False):
427    """Determine points inside a polygon
428
429       Functions inside_polygon and outside_polygon have been defined in
[7276]430       terms of separate_by_polygon which will put all inside indices in
[5897]431       the first part of the indices array and outside indices in the last
432
433       See separate_points_by_polygon for documentation
434
435       points and polygon can be a geospatial instance,
436       a list or a numeric array
437    """
438
439    try:
440        points = ensure_absolute(points)
441    except NameError, e:
442        raise NameError, e
443    except:
444        # If this fails it is going to be because the points can't be
445        # converted to a numeric array.
[7276]446        msg = 'Points could not be converted to numeric array' 
447        raise Exception, msg
[5897]448
[6534]449    polygon = ensure_absolute(polygon)       
[5897]450    try:
451        polygon = ensure_absolute(polygon)
452    except NameError, e:
453        raise NameError, e
454    except:
455        # If this fails it is going to be because the points can't be
456        # converted to a numeric array.
[7276]457        msg = ('Polygon %s could not be converted to numeric array'
458               % (str(polygon)))
459        raise Exception, msg
[5897]460
461    if len(points.shape) == 1:
462        # Only one point was passed in. Convert to array of points
[7276]463        points = num.reshape(points, (1,2))
[5897]464
465    indices, count = separate_points_by_polygon(points, polygon,
466                                                closed=closed,
467                                                verbose=verbose)
468
469    # Return indices of points inside polygon
470    return indices[:count]
471
[7276]472##
473# @brief Determine if one point is outside a polygon.
474# @param point The point of interest.
475# @param polygon The polygon to test inclusion in.
476# @param closed True if points on boundary are considered 'inside' polygon.
477# @param verbose True if this function is to be verbose.
478# @return True if point is outside the polygon.
479# @note Uses inside_polygon() to do the work.
[5897]480def is_outside_polygon(point, polygon, closed=True, verbose=False,
481                       points_geo_ref=None, polygon_geo_ref=None):
482    """Determine if one point is outside a polygon
483
484    See outside_polygon for more details
485    """
486
487    indices = outside_polygon(point, polygon, closed, verbose)
488
489    if indices.shape[0] == 1:
490        return True
491    elif indices.shape[0] == 0:
492        return False
493    else:
494        msg = 'is_outside_polygon must be invoked with one point only'
[7276]495        raise Exception, msg
[5897]496
[7276]497##
498# @brief Determine which of a set of points are outside a polygon.
499# @param points A set of points (tuple, list or array).
500# @param polygon A set of points defining a polygon (tuple, list or array).
501# @param closed True if points on boundary are considered 'inside' polygon.
502# @param verbose True if this function is to be verbose.
503# @return A list of indices of points outside the polygon.
[5897]504def outside_polygon(points, polygon, closed = True, verbose = False):
505    """Determine points outside a polygon
506
507       Functions inside_polygon and outside_polygon have been defined in
[7276]508       terms of separate_by_polygon which will put all inside indices in
[5897]509       the first part of the indices array and outside indices in the last
510
511       See separate_points_by_polygon for documentation
512    """
513
514    try:
[7276]515        points = ensure_numeric(points, num.float)
[5897]516    except NameError, e:
517        raise NameError, e
518    except:
[7276]519        msg = 'Points could not be converted to numeric array'
520        raise Exception, msg
[5897]521
522    try:
[7276]523        polygon = ensure_numeric(polygon, num.float)
[5897]524    except NameError, e:
525        raise NameError, e
526    except:
[7276]527        msg = 'Polygon could not be converted to numeric array'
528        raise Exception, msg
[5897]529
530    if len(points.shape) == 1:
531        # Only one point was passed in. Convert to array of points
[7276]532        points = num.reshape(points, (1,2))
[5897]533
534    indices, count = separate_points_by_polygon(points, polygon,
535                                                closed=closed,
536                                                verbose=verbose)
537
538    # Return indices of points outside polygon
539    if count == len(indices):
540        # No points are outside
[6158]541        return num.array([])
[5897]542    else:
543        return indices[count:][::-1]  #return reversed
544
[7276]545##
546# @brief Separate a list of points into two sets inside+outside a polygon.
547# @param points A set of points (tuple, list or array).
548# @param polygon A set of points defining a polygon (tuple, list or array).
549# @param closed True if points on boundary are considered 'inside' polygon.
550# @param verbose True if this function is to be verbose.
551# @return A tuple (in, out) of point indices for poinst inside amd outside.
[6534]552def in_and_outside_polygon(points, polygon, closed=True, verbose=False):
[5897]553    """Determine points inside and outside a polygon
554
555       See separate_points_by_polygon for documentation
556
[7276]557       Returns an array of points inside and array of points outside the polygon
[5897]558    """
559
560    try:
[7276]561        points = ensure_numeric(points, num.float)
[5897]562    except NameError, e:
563        raise NameError, e
564    except:
[7276]565        msg = 'Points could not be converted to numeric array'
566        raise Exception, msg
[5897]567
568    try:
[7276]569        polygon = ensure_numeric(polygon, num.float)
[5897]570    except NameError, e:
571        raise NameError, e
572    except:
[7276]573        msg = 'Polygon could not be converted to numeric array'
574        raise Exception, msg
[5897]575
576    if len(points.shape) == 1:
577        # Only one point was passed in. Convert to array of points
[7276]578        points = num.reshape(points, (1,2))
[5897]579
580    indices, count = separate_points_by_polygon(points, polygon,
581                                                closed=closed,
582                                                verbose=verbose)
[7276]583
[5897]584    # Returns indices of points inside and indices of points outside
585    # the polygon
586    if count == len(indices):
587        # No points are outside
588        return indices[:count],[]
589    else:
590        return  indices[:count], indices[count:][::-1]  #return reversed
591
[7276]592##
593# @brief Sort a list of points into contiguous points inside+outside a polygon.
594# @param points A set of points (tuple, list or array).
595# @param polygon A set of points defining a polygon (tuple, list or array).
596# @param closed True if points on boundary are considered 'inside' polygon.
597# @param verbose True if this function is to be verbose.
598# @return (indices, count) where indices are point indices and count is the
599#          delimiter index between point inside (on left) and others.
[5897]600def separate_points_by_polygon(points, polygon,
[6534]601                               closed=True, 
602                               check_input=True,
603                               verbose=False):
[5897]604    """Determine whether points are inside or outside a polygon
605
606    Input:
607       points - Tuple of (x, y) coordinates, or list of tuples
608       polygon - list of vertices of polygon
609       closed - (optional) determine whether points on boundary should be
610       regarded as belonging to the polygon (closed = True)
611       or not (closed = False)
[6534]612       check_input: Allows faster execution if set to False
[5897]613
614    Outputs:
615       indices: array of same length as points with indices of points falling
616       inside the polygon listed from the beginning and indices of points
617       falling outside listed from the end.
618
619       count: count of points falling inside the polygon
620
621       The indices of points inside are obtained as indices[:count]
622       The indices of points outside are obtained as indices[count:]
623
624    Examples:
625       U = [[0,0], [1,0], [1,1], [0,1]] #Unit square
626
627       separate_points_by_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)
628       will return the indices [0, 2, 1] and count == 2 as only the first
629       and the last point are inside the unit square
630
631    Remarks:
632       The vertices may be listed clockwise or counterclockwise and
633       the first point may optionally be repeated.
634       Polygons do not need to be convex.
635       Polygons can have holes in them and points inside a hole is
636       regarded as being outside the polygon.
637
638    Algorithm is based on work by Darel Finley,
639    http://www.alienryderflex.com/polygon/
640
641    Uses underlying C-implementation in polygon_ext.c
642    """
643
[6534]644    if check_input:
645        #Input checks
646        assert isinstance(closed, bool), 'Keyword argument "closed" must be boolean'
647        assert isinstance(verbose, bool), 'Keyword argument "verbose" must be boolean'
[5897]648
[6534]649        try:
[7276]650            points = ensure_numeric(points, num.float)
[6534]651        except NameError, e:
652            raise NameError, e
653        except:
[7276]654            msg = 'Points could not be converted to numeric array'
[6534]655            raise msg
[5897]656
[6534]657        try:
[7276]658            polygon = ensure_numeric(polygon, num.float)
[6534]659        except NameError, e:
660            raise NameError, e
661        except:
[7276]662            msg = 'Polygon could not be converted to numeric array'
[6534]663            raise msg
[5897]664
[6534]665        msg = 'Polygon array must be a 2d array of vertices'
666        assert len(polygon.shape) == 2, msg
[5897]667
[6534]668        msg = 'Polygon array must have two columns' 
[7276]669        assert polygon.shape[1]==2, msg
[5897]670
[7276]671        msg = ('Points array must be 1 or 2 dimensional. '
672               'I got %d dimensions' % len(points.shape))
[6534]673        assert 0 < len(points.shape) < 3, msg
[5897]674
[6534]675        if len(points.shape) == 1:
[7276]676            # Only one point was passed in.  Convert to array of points.
[6534]677            points = num.reshape(points, (1,2))
[5897]678   
[7276]679            msg = ('Point array must have two columns (x,y), '
680                   'I got points.shape[1]=%d' % points.shape[0])
681            assert points.shape[1]==2, msg
[5897]682
683       
[7276]684            msg = ('Points array must be a 2d array. I got %s.'
685                   % str(points[:30]))
686            assert len(points.shape)==2, msg
[5897]687
[6534]688            msg = 'Points array must have two columns'
[7276]689            assert points.shape[1]==2, msg
[5897]690
[6534]691    N = polygon.shape[0] # Number of vertices in polygon
692    M = points.shape[0]  # Number of points
[5897]693
[7276]694    indices = num.zeros(M, num.int)
[5897]695
696    count = _separate_points_by_polygon(points, polygon, indices,
697                                        int(closed), int(verbose))
698
[7276]699    if verbose:
[7317]700        log.critical('Found %d points (out of %d) inside polygon' % (count, M))
[7276]701
[5897]702    return indices, count
703
[7276]704##
705# @brief Determine area of a polygon.
706# @param input_polygon The polygon to get area of.
707# @return A scalar value for the polygon area.
708def polygon_area(input_polygon):
709    """ Determine area of arbitrary polygon.
[5897]710
[7276]711    Reference:     http://mathworld.wolfram.com/PolygonArea.html
[5897]712    """
[7276]713
[6000]714    # Move polygon to origin (0,0) to avoid rounding errors
[6001]715    # This makes a copy of the polygon to avoid destroying it
716    input_polygon = ensure_numeric(input_polygon)
717    min_x = min(input_polygon[:,0])
[7276]718    min_y = min(input_polygon[:,1])
[6001]719    polygon = input_polygon - [min_x, min_y]
[6000]720
[7276]721    # Compute area
[5897]722    n = len(polygon)
723    poly_area = 0.0
724
725    for i in range(n):
726        pti = polygon[i]
727        if i == n-1:
728            pt1 = polygon[0]
729        else:
730            pt1 = polygon[i+1]
731        xi = pti[0]
732        yi1 = pt1[1]
733        xi1 = pt1[0]
734        yi = pti[1]
735        poly_area += xi*yi1 - xi1*yi
[7276]736
[5897]737    return abs(poly_area/2)
738
[7276]739##
740# @brief Plot a set of polygons.
741# @param polygons_points List of polygons to plot.
742# @param style List of styles for each polygon.
743# @param figname Name to save figure to.
744# @param label Title for the plot.
745# @param verbose True if this function is to be verbose.
746# @return A list of min/max x and y values [minx, maxx, miny, maxy].
747# @note A style value is 'line' for polygons, 'outside' for points outside.
748def plot_polygons(polygons_points,
749                  style=None,
750                  figname=None,
751                  label=None,
[7516]752                  alpha=None,
[7276]753                  verbose=False):
[5897]754    """ Take list of polygons and plot.
755
756    Inputs:
757
758    polygons         - list of polygons
759
760    style            - style list corresponding to each polygon
761                     - for a polygon, use 'line'
762                     - for points falling outside a polygon, use 'outside'
[7511]763                     - style can also be user defined as in normal pylab plot.
[7276]764
[5897]765    figname          - name to save figure to
766
[7516]767    label            - title for plotA
[5897]768
[7516]769    alpha            - transparency of polygon fill, 0.0=none, 1.0=solid
770                       if not supplied, no fill.
771
[5897]772    Outputs:
773
774    - list of min and max of x and y coordinates
775    - plot of polygons
[7276]776    """
[5897]777
[7276]778    from pylab import ion, hold, plot, axis, figure, legend, savefig, xlabel, \
[7516]779                      ylabel, title, close, title, fill
[5897]780
[7276]781    assert type(polygons_points) == list, \
782                'input must be a list of polygons and/or points'
783
[5897]784    ion()
785    hold(True)
786
787    minx = 1e10
788    maxx = 0.0
789    miny = 1e10
790    maxy = 0.0
791
[7276]792    if label is None:
793        label = ''
[5897]794
[7516]795    # clamp alpha to sensible range
796    if alpha:
797        try:
798            alpha = float(alpha)
799        except ValueError:
800            alpha = None
801        else:
802            if alpha < 0.0:
803                alpha = 0.0
804            if alpha > 1.0:
805                alpha = 1.0
806
[5897]807    n = len(polygons_points)
808    colour = []
809    if style is None:
[7276]810        style_type = 'line'
[5897]811        style = []
812        for i in range(n):
813            style.append(style_type)
814            colour.append('b-')
815    else:
816        for s in style:
[7276]817            if s == 'line': colour.append('b-')
[5897]818            if s == 'outside': colour.append('r.')
[7553]819            if s == 'point': colour.append('g.')
[5897]820            if s <> 'line':
821                if s <> 'outside':
[7553]822                    if s <> 'point':
823                        colour.append(s)
[7276]824
[5897]825    for i, item in enumerate(polygons_points):
826        x, y = poly_xy(item)
827        if min(x) < minx: minx = min(x)
828        if max(x) > maxx: maxx = max(x)
829        if min(y) < miny: miny = min(y)
830        if max(y) > maxy: maxy = max(y)
831        plot(x,y,colour[i])
[7516]832        if alpha:
833            fill(x, y, colour[i], alpha=alpha)
[5897]834        xlabel('x')
835        ylabel('y')
836        title(label)
837
838    #raw_input('wait 1')
839    #FIXME(Ole): This makes for some strange scalings sometimes.
840    #if minx <> 0:
841    #    axis([minx*0.9,maxx*1.1,miny*0.9,maxy*1.1])
842    #else:
843    #    if miny == 0:
844    #        axis([-maxx*.01,maxx*1.1,-maxy*0.01,maxy*1.1])
845    #    else:
846    #        axis([-maxx*.01,maxx*1.1,miny*0.9,maxy*1.1])
847
848    if figname is not None:
849        savefig(figname)
850    else:
851        savefig('test_image')
852
853    close('all')
854
[7276]855    vec = [minx, maxx, miny, maxy]
[5897]856    return vec
857
[7276]858##
859# @brief
860# @param polygon A set of points defining a polygon.
861# @param verbose True if this function is to be verbose.
862# @return A tuple (x, y) of X and Y coordinates of the polygon.
863# @note We duplicate the first point so can have closed polygon in plot.
[5897]864def poly_xy(polygon, verbose=False):
865    """ this is used within plot_polygons so need to duplicate
866        the first point so can have closed polygon in plot
867    """
868
869    try:
[7276]870        polygon = ensure_numeric(polygon, num.float)
[5897]871    except NameError, e:
872        raise NameError, e
873    except:
[7276]874        msg = ('Polygon %s could not be converted to numeric array'
875               % (str(polygon)))
876        raise Exception, msg
[5897]877
878    x = polygon[:,0]
879    y = polygon[:,1]
[6158]880    x = num.concatenate((x, [polygon[0,0]]), axis = 0)
881    y = num.concatenate((y, [polygon[0,1]]), axis = 0)
[7276]882
[5897]883    return x, y
884
885
[7276]886##
887# @brief Define a class that defines a callable object for a polygon.
888# @note Object created is function: f: x,y -> z
889#       where x, y and z are vectors and z depends on whether x,y belongs
890#       to specified polygons.
[5897]891class Polygon_function:
892    """Create callable object f: x,y -> z, where a,y,z are vectors and
893    where f will return different values depending on whether x,y belongs
894    to specified polygons.
895
896    To instantiate:
897
898       Polygon_function(polygons)
899
900    where polygons is a list of tuples of the form
901
902      [ (P0, v0), (P1, v1), ...]
903
904      with Pi being lists of vertices defining polygons and vi either
905      constants or functions of x,y to be applied to points with the polygon.
906
907    The function takes an optional argument, default which is the value
908    (or function) to used for points not belonging to any polygon.
909    For example:
910
911       Polygon_function(polygons, default = 0.03)
912
913    If omitted the default value will be 0.0
914
915    Note: If two polygons overlap, the one last in the list takes precedence
916
917    Coordinates specified in the call are assumed to be relative to the
918    origin (georeference) e.g. used by domain.
919    By specifying the optional argument georeference,
920    all points are made relative.
921
922    FIXME: This should really work with geo_spatial point sets.
923    """
924
[7276]925    ##
926    # @brief Create instance of a polygon function.
927    # @param regions A list of (x,y) tuples defining a polygon.
928    # @param default Value or function returning value for points outside poly.
929    # @param geo_reference ??
930    def __init__(self, regions, default=0.0, geo_reference=None):
931        try:
932            len(regions)
933        except:
934            msg = ('Polygon_function takes a list of pairs (polygon, value).'
935                   'Got %s' % str(regions))
936            raise Exception, msg
[5897]937
938        T = regions[0]
939
940        if isinstance(T, basestring):
[7276]941            msg = ('You passed in a list of text values into polygon_function '
942                   'instead of a list of pairs (polygon, value): "%s"'
943                   % str(T))
[5897]944            raise Exception, msg
[7276]945
946        try:
[5897]947            a = len(T)
[7276]948        except:
949            msg = ('Polygon_function takes a list of pairs (polygon, value). '
950                   'Got %s' % str(T))
951            raise Exception, msg
[5897]952
[7276]953        msg = ('Each entry in regions have two components: (polygon, value). '
954               'I got %s' % str(T))
955        assert a == 2, msg
[5897]956
957        if geo_reference is None:
958            from anuga.coordinate_transforms.geo_reference import Geo_reference
959            geo_reference = Geo_reference()
960
961        self.default = default
962
963        # Make points in polygons relative to geo_reference
964        self.regions = []
965        for polygon, value in regions:
966            P = geo_reference.change_points_geo_ref(polygon)
[6223]967            self.regions.append((P, value))
[5897]968
[7276]969    ##
970    # @brief Implement the 'callable' property of Polygon_function.
971    # @param x List of x coordinates of points ot interest.
972    # @param y List of y coordinates of points ot interest.
[5897]973    def __call__(self, x, y):
[7276]974        x = num.array(x, num.float)
975        y = num.array(y, num.float)
[5897]976
[7276]977        # x and y must be one-dimensional and same length
978        assert len(x.shape) == 1 and len(y.shape) == 1
979        N = x.shape[0]
980        assert y.shape[0] == N
[5897]981
[7276]982        points = num.ascontiguousarray(num.concatenate((x[:,num.newaxis],
983                                                        y[:,num.newaxis]),
984                                                       axis=1 ))
[5897]985
[7276]986        if callable(self.default):
987            z = self.default(x, y)
988        else:
989            z = num.ones(N, num.float) * self.default
[5897]990
[7276]991        for polygon, value in self.regions:
992            indices = inside_polygon(points, polygon)
[5897]993
[7276]994            # FIXME: This needs to be vectorised
995            if callable(value):
996                for i in indices:
997                    xx = num.array([x[i]])
998                    yy = num.array([y[i]])
[5897]999                    z[i] = value(xx, yy)[0]
[7276]1000            else:
1001                for i in indices:
1002                    z[i] = value
[5897]1003
[6223]1004        if len(z) == 0:
[7276]1005            msg = ('Warning: points provided to Polygon function did not fall '
1006                   'within its regions in [%.2f, %.2f], y in [%.2f, %.2f]'
1007                   % (min(x), max(x), min(y), max(y)))
[7317]1008            log.critical(msg)
[6223]1009
[5897]1010        return z
1011
[7276]1012################################################################################
1013# Functions to read and write polygon information
1014################################################################################
[5897]1015
[7276]1016##
1017# @brief Read polygon data from a file.
1018# @param filename Path to file containing polygon data.
1019# @param delimiter Delimiter to split polygon data with.
1020# @return A list of point data from the polygon file.
1021def read_polygon(filename, delimiter=','):
[5897]1022    """Read points assumed to form a polygon.
[7276]1023
1024    There must be exactly two numbers in each line separated by the delimiter.
1025    No header.
[5897]1026    """
1027
1028    fid = open(filename)
1029    lines = fid.readlines()
1030    fid.close()
1031    polygon = []
1032    for line in lines:
[7276]1033        fields = line.split(delimiter)
1034        polygon.append([float(fields[0]), float(fields[1])])
[7686]1035   
[7690]1036    # check this is a valid polygon.
1037    if is_complex(polygon, verbose=True):   
1038        msg = 'ERROR: Self-intersecting polygon detected in file ' + filename +'. '
1039        msg += 'A complex polygon will not necessarily break the algorithms within ANUGA, '
1040        msg += 'but it usually signifies pathological data. Please fix this file.'
1041        raise Exception, msg
[7686]1042   
[5897]1043    return polygon
1044
[7276]1045##
1046# @brief Write polygon data to a file.
1047# @param polygon Polygon points to write to file.
1048# @param filename Path to file to write.
1049# @note Delimiter is assumed to be a comma.
[5897]1050def write_polygon(polygon, filename=None):
1051    """Write polygon to csv file.
[7276]1052
1053    There will be exactly two numbers, easting and northing, in each line
1054    separated by a comma.
1055
1056    No header.
[5897]1057    """
1058
1059    fid = open(filename, 'w')
1060    for point in polygon:
[7276]1061        fid.write('%f, %f\n' % point)
[5897]1062    fid.close()
1063
[7276]1064##
1065# @brief Unimplemented.
[6116]1066def read_tagged_polygons(filename):
1067    """
1068    """
1069    pass
[7276]1070
1071##
1072# @brief Populate given polygon with uniformly distributed points.
1073# @param polygon Polygon to uniformly fill.
1074# @param number_of_points Number of points required in polygon.
1075# @param seed Seed for random number generator.
1076# @param exclude List of polygons inside main where points should be excluded.
1077# @return List of random points inside input polygon.
1078# @note Delimiter is assumed to be a comma.
[5897]1079def populate_polygon(polygon, number_of_points, seed=None, exclude=None):
1080    """Populate given polygon with uniformly distributed points.
1081
1082    Input:
1083       polygon - list of vertices of polygon
1084       number_of_points - (optional) number of points
1085       seed - seed for random number generator (default=None)
[7276]1086       exclude - list of polygons (inside main polygon) from where points
1087                 should be excluded
[5897]1088
1089    Output:
1090       points - list of points inside polygon
1091
1092    Examples:
1093       populate_polygon( [[0,0], [1,0], [1,1], [0,1]], 5 )
1094       will return five randomly selected points inside the unit square
1095    """
1096
1097    from random import uniform, seed as seed_function
1098
1099    seed_function(seed)
1100
1101    points = []
1102
1103    # Find outer extent of polygon
1104    max_x = min_x = polygon[0][0]
1105    max_y = min_y = polygon[0][1]
1106    for point in polygon[1:]:
1107        x = point[0]
1108        if x > max_x: max_x = x
1109        if x < min_x: min_x = x
1110        y = point[1]
1111        if y > max_y: max_y = y
1112        if y < min_y: min_y = y
1113
1114    while len(points) < number_of_points:
1115        x = uniform(min_x, max_x)
1116        y = uniform(min_y, max_y)
1117
1118        append = False
1119        if is_inside_polygon([x,y], polygon):
1120            append = True
1121
1122            #Check exclusions
1123            if exclude is not None:
1124                for ex_poly in exclude:
1125                    if is_inside_polygon([x,y], ex_poly):
1126                        append = False
1127
1128        if append is True:
1129            points.append([x,y])
1130
1131    return points
1132
[7276]1133##
1134# @brief Get a point inside a polygon that is close to an edge.
1135# @param polygon List  of vertices of polygon.
1136# @param delta Maximum distance from an edge is delta * sqrt(2).
1137# @return A point that is inside polgon and close to the polygon edge.
[5897]1138def point_in_polygon(polygon, delta=1e-8):
1139    """Return a point inside a given polygon which will be close to the
1140    polygon edge.
1141
1142    Input:
1143       polygon - list of vertices of polygon
1144       delta - the square root of 2 * delta is the maximum distance from the
1145       polygon points and the returned point.
1146    Output:
1147       points - a point inside polygon
1148
[7276]1149       searches in all diagonals and up and down (not left and right).
[5897]1150    """
[7276]1151
[5897]1152    import exceptions
[7276]1153
[5897]1154    class Found(exceptions.Exception): pass
1155
[6534]1156    polygon = ensure_numeric(polygon)
1157   
[5897]1158    point_in = False
1159    while not point_in:
1160        try:
[7276]1161            for poly_point in polygon:     # [1:]:
1162                for x_mult in range(-1, 2):
1163                    for y_mult in range(-1, 2):
[5897]1164                        x = poly_point[0]
1165                        y = poly_point[1]
[7276]1166
[5897]1167                        if x == 0:
[7276]1168                            x_delta = x_mult * delta
[5897]1169                        else:
[7276]1170                            x_delta = x + x_mult*x*delta
[5897]1171
1172                        if y == 0:
[7276]1173                            y_delta = y_mult * delta
[5897]1174                        else:
[7276]1175                            y_delta = y + y_mult*y*delta
[5897]1176
1177                        point = [x_delta, y_delta]
[7276]1178
[5897]1179                        if is_inside_polygon(point, polygon, closed=False):
1180                            raise Found
1181        except Found:
1182            point_in = True
1183        else:
[7276]1184            delta = delta * 0.1
1185
[5897]1186    return point
1187
[7276]1188##
1189# @brief Calculate approximate number of triangles inside a bounding polygon.
1190# @param interior_regions
1191# @param bounding_poly
1192# @param remainder_res
1193# @return The number of triangles.
[5897]1194def number_mesh_triangles(interior_regions, bounding_poly, remainder_res):
1195    """Calculate the approximate number of triangles inside the
1196    bounding polygon and the other interior regions
1197
[7276]1198    Polygon areas are converted to square Kms
[5897]1199
1200    FIXME: Add tests for this function
1201    """
[7276]1202
[5897]1203    # TO DO check if any of the regions fall inside one another
1204
[7317]1205    log.critical('-' * 80)
1206    log.critical('Polygon   Max triangle area (m^2)   Total area (km^2) '
1207                 'Estimated #triangles')
1208    log.critical('-' * 80)
[5897]1209       
1210    no_triangles = 0.0
1211    area = polygon_area(bounding_poly)
[7276]1212
[5897]1213    for poly, resolution in interior_regions:
1214        this_area = polygon_area(poly)
1215        this_triangles = this_area/resolution
1216        no_triangles += this_triangles
1217        area -= this_area
[7276]1218
[7317]1219        log.critical('Interior %s%s%d'
1220                     % (('%.0f' % resolution).ljust(25),
1221                        ('%.2f' % (this_area/1000000)).ljust(19), 
1222                        this_triangles))
1223        #print 'Interior ',
1224        #print ('%.0f' % resolution).ljust(25),
1225        #print ('%.2f' % (this_area/1000000)).ljust(19),
1226        #print '%d' % (this_triangles)
[7276]1227
[5897]1228    bound_triangles = area/remainder_res
1229    no_triangles += bound_triangles
1230
[7317]1231    log.critical('Bounding %s%s%d'
1232                 % (('%.0f' % remainder_res).ljust(25),
1233                    ('%.2f' % (area/1000000)).ljust(19),
1234                    bound_triangles))
1235    #print 'Bounding ',
1236    #print ('%.0f' % remainder_res).ljust(25),
1237    #print ('%.2f' % (area/1000000)).ljust(19),
1238    #print '%d' % (bound_triangles)
[5897]1239
1240    total_number_of_triangles = no_triangles/0.7
1241
[7317]1242    log.critical('Estimated total number of triangles: %d'
1243                 % total_number_of_triangles)
1244    log.critical('Note: This is generally about 20%% '
1245                 'less than the final amount')
[5897]1246
1247    return int(total_number_of_triangles)
1248
[7276]1249##
1250# @brief Reduce number of points in polygon by the specified factor.
1251# @param polygon The polygon to reduce.
1252# @param factor The factor to reduce polygon points by (default 10).
1253# @return The reduced polygon points list.
1254# @note The extrema of both axes are preserved.
[5897]1255def decimate_polygon(polygon, factor=10):
1256    """Reduce number of points in polygon by the specified
1257    factor (default=10, hence the name of the function) such that
1258    the extrema in both axes are preserved.
1259
1260    Return reduced polygon
1261    """
1262
1263    # FIXME(Ole): This doesn't work at present,
1264    # but it isn't critical either
1265
1266    # Find outer extent of polygon
1267    num_polygon = ensure_numeric(polygon)
1268    max_x = max(num_polygon[:,0])
1269    max_y = max(num_polygon[:,1])
1270    min_x = min(num_polygon[:,0])
[7276]1271    min_y = min(num_polygon[:,1])
[5897]1272
1273    # Keep only some points making sure extrema are kept
[7276]1274    reduced_polygon = []
[5897]1275    for i, point in enumerate(polygon):
1276        x = point[0]
[7276]1277        y = point[1]
[5897]1278        if x in [min_x, max_x] and y in [min_y, max_y]:
1279            # Keep
1280            reduced_polygon.append(point)
1281        else:
1282            if len(reduced_polygon)*factor < i:
[7276]1283                reduced_polygon.append(point)
[5897]1284
1285    return reduced_polygon
1286
[6189]1287##
1288# @brief Interpolate linearly from polyline nodes to midpoints of triangles.
1289# @param data The data on the polyline nodes.
1290# @param polyline_nodes ??
1291# @param gauge_neighbour_id ??  FIXME(Ole): I want to get rid of this
1292# @param point_coordinates ??
1293# @param verbose True if this function is to be verbose.
1294def interpolate_polyline(data,
1295                         polyline_nodes,
1296                         gauge_neighbour_id,
1297                         interpolation_points=None,
1298                         rtol=1.0e-6,
1299                         atol=1.0e-8,
1300                         verbose=False):
1301    """Interpolate linearly between values data on polyline nodes
[7276]1302    of a polyline to list of interpolation points.
[6189]1303
1304    data is the data on the polyline nodes.
1305
1306    Inputs:
1307      data: Vector or array of data at the polyline nodes.
[7276]1308      polyline_nodes: Location of nodes where data is available.
[6189]1309      gauge_neighbour_id: ?
1310      interpolation_points: Interpolate polyline data to these positions.
1311          List of coordinate pairs [x, y] of
[7276]1312          data points or an nx2 numeric array or a Geospatial_data object
1313      rtol, atol: Used to determine whether a point is on the polyline or not.
1314                  See point_on_line.
[6189]1315
1316    Output:
1317      Interpolated values at interpolation points
1318    """
[7276]1319
[6189]1320    if isinstance(interpolation_points, Geospatial_data):
[7276]1321        interpolation_points = interpolation_points.\
1322                                    get_data_points(absolute=True)
[6189]1323
[7276]1324    interpolated_values = num.zeros(len(interpolation_points), num.float)
[6189]1325
[7276]1326    data = ensure_numeric(data, num.float)
1327    polyline_nodes = ensure_numeric(polyline_nodes, num.float)
1328    interpolation_points = ensure_numeric(interpolation_points, num.float)
1329    gauge_neighbour_id = ensure_numeric(gauge_neighbour_id, num.int)
[6189]1330
[7276]1331    n = polyline_nodes.shape[0]    # Number of nodes in polyline
1332
[6189]1333    # Input sanity check
1334    msg = 'interpolation_points are not given (interpolate.py)'
1335    assert interpolation_points is not None, msg
[7276]1336
[6189]1337    msg = 'function value must be specified at every interpolation node'
[7276]1338    assert data.shape[0] == polyline_nodes.shape[0], msg
1339
[6189]1340    msg = 'Must define function value at one or more nodes'
[7276]1341    assert data.shape[0] > 0, msg
[6189]1342
1343    if n == 1:
1344        msg = 'Polyline contained only one point. I need more. ' + str(data)
1345        raise Exception, msg
1346    elif n > 1:
1347        _interpolate_polyline(data,
1348                              polyline_nodes,
1349                              gauge_neighbour_id,
[7276]1350                              interpolation_points,
[6189]1351                              interpolated_values,
1352                              rtol,
1353                              atol)
[7276]1354
[7699]1355
[6189]1356    return interpolated_values
1357
[7699]1358   
1359def polylist2points_verts(polylist):
1360    """ Convert a list of polygons to discrete points and vertices.
1361    """
1362   
1363    offset = 0
1364    points = []
1365    vertices = []
1366    for poly in polylist:
1367        points.extend(poly)
1368        vertices.extend([[i, i+1] for i in range(offset, offset+len(poly)-1)])
1369        offset += len(poly)
1370               
1371    return points, vertices
[7276]1372##
1373# @brief
1374# @param data
1375# @param polyline_nodes
1376# @param gauge_neighbour_id
1377# @param interpolation_points
1378# @param interpolated_values
1379# @param rtol
1380# @param atol
1381# @return
1382# @note OBSOLETED BY C-EXTENSION
[6189]1383def _interpolate_polyline(data,
[7276]1384                          polyline_nodes,
1385                          gauge_neighbour_id,
1386                          interpolation_points,
[6189]1387                          interpolated_values,
1388                          rtol=1.0e-6,
1389                          atol=1.0e-8):
1390    """Auxiliary function used by interpolate_polyline
[7276]1391
[6189]1392    NOTE: OBSOLETED BY C-EXTENSION
1393    """
[7276]1394
1395    number_of_nodes = len(polyline_nodes)
[6189]1396    number_of_points = len(interpolation_points)
[7276]1397
1398    for j in range(number_of_nodes):
[6189]1399        neighbour_id = gauge_neighbour_id[j]
[7276]1400
1401        # FIXME(Ole): I am convinced that gauge_neighbour_id can be discarded,
1402        #             but need to check with John J.
[6189]1403        # Keep it for now (17 Jan 2009)
[7276]1404        # When gone, we can simply interpolate between neighbouring nodes,
1405        # i.e. neighbour_id = j+1.
1406        # and the test below becomes something like: if j < number_of_nodes...
1407
[6189]1408        if neighbour_id >= 0:
1409            x0, y0 = polyline_nodes[j,:]
1410            x1, y1 = polyline_nodes[neighbour_id,:]
[7276]1411
[6189]1412            segment_len = sqrt((x1-x0)**2 + (y1-y0)**2)
[7276]1413            segment_delta = data[neighbour_id] - data[j]
[6189]1414            slope = segment_delta/segment_len
[7276]1415
1416            for i in range(number_of_points):
[6189]1417                x, y = interpolation_points[i,:]
[7276]1418                if point_on_line([x, y], [[x0, y0], [x1, y1]],
1419                                 rtol=rtol, atol=atol):
[6189]1420                    dist = sqrt((x-x0)**2 + (y-y0)**2)
1421                    interpolated_values[i] = slope*dist + data[j]
1422
1423
[7276]1424################################################################################
1425# Initialise module
1426################################################################################
[5897]1427
[6119]1428from anuga.utilities import compile
1429if compile.can_use_C_extension('polygon_ext.c'):
[5897]1430    # Underlying C implementations can be accessed
1431    from polygon_ext import _point_on_line
1432    from polygon_ext import _separate_points_by_polygon
[6189]1433    from polygon_ext import _interpolate_polyline   
[6535]1434    from polygon_ext import _is_inside_triangle       
[5897]1435    #from polygon_ext import _intersection
1436
1437else:
1438    msg = 'C implementations could not be accessed by %s.\n ' %__file__
1439    msg += 'Make sure compile_all.py has been run as described in '
1440    msg += 'the ANUGA installation guide.'
1441    raise Exception, msg
1442
1443
1444if __name__ == "__main__":
1445    pass
Note: See TracBrowser for help on using the repository browser.