Example #1
0
def clip_lines_by_polygon(lines, polygon,
                          closed=True,
                          check_input=True):
    """Clip multiple lines by polygon

    Input:
       lines: Sequence of polylines: [[p0, p1, ...], [q0, q1, ...], ...]
              where pi and qi are point coordinates (x, y).

       polygon: list or Nx2 array of polygon vertices

       closed: (optional) determine whether points on boundary should be
       regarded as belonging to the polygon (closed = True)
       or not (closed = False). False is not recommended here.
       This parameter can also be None in which case it is undefined
       whether a line on the boundary will fall inside or outside.
       This will make the algorithm about 20% faster.

       check_input: Allows faster execution if set to False

    Output:
       inside_lines: Dictionary of lines that are inside polygon
       outside_lines: Dictionary of lines that are outside polygon

       Elements in output dictionaries can be a list of multiple lines.
       One line is a numpy array of vertices.

       Both output dictionaries use the indices of the original line as keys.
       This makes it possible to track which line the new clipped lines
       come from, if one e.g. wants to assign the original attribute values
       to clipped lines.

    This is a wrapper around clip_line_by_polygon
    """

    if check_input:
        # Input checks
        msg = 'Keyword argument "closed" must be boolean'
        if not isinstance(closed, bool):
            raise RuntimeError(msg)

        for i in range(len(lines)):
            try:
                lines[i] = ensure_numeric(lines[i], numpy.float)
            except Exception, e:
                msg = ('Line could not be converted to numeric array: %s'
                       % str(e))
                raise Exception(msg)

            msg = ('Lines must be 2d array of vertices: '
                   'I got %d dimensions' % len(lines[i].shape))
            if not len(lines[i].shape) == 2:
                raise RuntimeError(msg)

        try:
            polygon = ensure_numeric(polygon, numpy.float)
        except Exception, e:
            msg = ('Polygon could not be converted to numeric array: %s'
                   % str(e))
            raise Exception(msg)
Example #2
0
def clip_lines_by_polygon(lines, polygon,
                          closed=True,
                          check_input=True):
    """Clip multiple lines by polygon

    Input:
       lines: Sequence of polylines: [[p0, p1, ...], [q0, q1, ...], ...]
              where pi and qi are point coordinates (x, y).

       polygon: list or Nx2 array of polygon vertices

       closed: (optional) determine whether points on boundary should be
       regarded as belonging to the polygon (closed = True)
       or not (closed = False). False is not recommended here.
       This parameter can also be None in which case it is undefined
       whether a line on the boundary will fall inside or outside.
       This will make the algorithm about 20% faster.

       check_input: Allows faster execution if set to False

    Output:
       inside_lines: Dictionary of lines that are inside polygon
       outside_lines: Dictionary of lines that are outside polygon

       Elements in output dictionaries can be a list of multiple lines.
       One line is a numpy array of vertices.

       Both output dictionaries use the indices of the original line as keys.
       This makes it possible to track which line the new clipped lines
       come from, if one e.g. wants to assign the original attribute values
       to clipped lines.

    This is a wrapper around clip_line_by_polygon
    """

    if check_input:
        # Input checks
        msg = 'Keyword argument "closed" must be boolean'
        if not isinstance(closed, bool):
            raise RuntimeError(msg)

        for i in range(len(lines)):
            try:
                lines[i] = ensure_numeric(lines[i], numpy.float)
            except Exception, e:
                msg = ('Line could not be converted to numeric array: %s'
                       % str(e))
                raise Exception(msg)

            msg = ('Lines must be 2d array of vertices: '
                   'I got %d dimensions' % len(lines[i].shape))
            if not len(lines[i].shape) == 2:
                raise RuntimeError(msg)

        try:
            polygon = ensure_numeric(polygon, numpy.float)
        except Exception, e:
            msg = ('Polygon could not be converted to numeric array: %s'
                   % str(e))
            raise Exception(msg)
Example #3
0
def array_to_wkt(A, geom_type="POLYGON"):
    """Convert coordinates to wkt format

    :param A: Nx2 Array of coordinates representing either a polygon or a line.
        A can be either a numpy array or a list of coordinates.
    :type A: numpy.array

    :param geom_type: Determines output keyword 'POLYGON' or 'LINESTRING'
    :type geom_type: str

    :returns: wkt: geometry in the format known to ogr: Examples

    Note:
        POLYGON((1020 1030,1020 1045,1050 1045,1050 1030,1020 1030))
        LINESTRING(1000 1000, 1100 1050)
    """

    try:
        A = ensure_numeric(A, numpy.float)
    except Exception, e:
        msg = "Array (%s) could not be converted to numeric array. " "I got type %s. Error message: %s" % (
            geom_type,
            str(type(A)),
            e,
        )
        raise Exception(msg)
Example #4
0
def array_to_line(A, geometry_type=ogr.wkbLinearRing):
    """Convert coordinates to linear_ring

    :param A: Nx2 Array of coordinates representing either a polygon or a line.
        A can be either a numpy array or a list of coordinates.
    :type A: numpy.ndarray, list

    :param geometry_type: A valid OGR geometry type.
        Default type ogr.wkbLinearRing
    :type geometry_type: ogr.wkbLinearRing, include ogr.wkbLineString

    Returns:
        * ring: OGR line geometry

    Note:
    Based on http://www.packtpub.com/article/working-geospatial-data-python
    """

    try:
        A = ensure_numeric(A, numpy.float)
    except Exception, e:
        msg = ('Array (%s) could not be converted to numeric array. '
               'I got type %s. Error message: %s'
               % (A, str(type(A)), e))
        raise Exception(msg)
Example #5
0
def array_to_line(A, geometry_type=ogr.wkbLinearRing):
    """Convert coordinates to linear_ring

    :param A: Nx2 Array of coordinates representing either a polygon or a line.
        A can be either a numpy array or a list of coordinates.
    :type A: numpy.ndarray, list

    :param geometry_type: A valid OGR geometry type.
        Default type ogr.wkbLinearRing
    :type geometry_type: ogr.wkbLinearRing, include ogr.wkbLineString

    Returns:
        * ring: OGR line geometry

    Note:
    Based on http://www.packtpub.com/article/working-geospatial-data-python
    """

    try:
        A = ensure_numeric(A, numpy.float)
    except Exception, e:
        msg = ('Array (%s) could not be converted to numeric array. '
               'I got type %s. Error message: %s'
               % (A, str(type(A)), e))
        raise Exception(msg)
Example #6
0
def clip_lines_by_polygons(lines, polygons, check_input=True, closed=True):
    """Clip multiple lines by multiple polygons

    Args:
        * lines: Sequence of polylines: [[p0, p1, ...], [q0, q1, ...], ...]
            where pi and qi are point coordinates (x, y).
        * polygons: list of polygons, each an array of vertices
        * closed: optional parameter to determine whether lines that fall on
            an polygon boundary should be considered to be inside
            (closed=True), outside (closed=False) or
            undetermined (closed=None). The latter case will speed the
            algorithm up but lines on boundaries may or may not be
            deemed to fall inside the polygon and so will be
            indeterministic.

    Returns:
        lines_covered: List of polylines inside a polygon -o ne per input
        polygon.


    .. note:: If multiple polygons overlap, the one first encountered will be
        used.
    """

    if check_input:
        for i in range(len(lines)):
            try:
                lines[i] = ensure_numeric(lines[i], numpy.float)
            except Exception, e:
                msg = ('Line could not be converted to numeric array: %s'
                       % str(e))
                raise Exception(msg)

            msg = 'Lines must be 2d array of vertices'
            if not len(lines[i].shape) == 2:
                raise RuntimeError(msg)

        for i in range(len(polygons)):
            try:
                polygons[i] = ensure_numeric(polygons[i], numpy.float)
            except Exception, e:
                msg = ('Polygon could not be converted to numeric array: %s'
                       % str(e))
                raise Exception(msg)
Example #7
0
def clip_lines_by_polygons(lines, polygons, check_input=True, closed=True):
    """Clip multiple lines by multiple polygons

    Args:
        * lines: Sequence of polylines: [[p0, p1, ...], [q0, q1, ...], ...]
            where pi and qi are point coordinates (x, y).
        * polygons: list of polygons, each an array of vertices
        * closed: optional parameter to determine whether lines that fall on
            an polygon boundary should be considered to be inside
            (closed=True), outside (closed=False) or
            undetermined (closed=None). The latter case will speed the
            algorithm up but lines on boundaries may or may not be
            deemed to fall inside the polygon and so will be
            indeterministic.

    Returns:
        lines_covered: List of polylines inside a polygon -o ne per input
        polygon.


    .. note:: If multiple polygons overlap, the one first encountered will be
        used.
    """

    if check_input:
        for i in range(len(lines)):
            try:
                lines[i] = ensure_numeric(lines[i], numpy.float)
            except Exception, e:
                msg = ('Line could not be converted to numeric array: %s' %
                       str(e))
                raise Exception(msg)

            msg = 'Lines must be 2d array of vertices'
            if not len(lines[i].shape) == 2:
                raise RuntimeError(msg)

        for i in range(len(polygons)):
            try:
                polygons[i] = ensure_numeric(polygons[i], numpy.float)
            except Exception, e:
                msg = ('Polygon could not be converted to numeric array: %s' %
                       str(e))
                raise Exception(msg)
Example #8
0
def populate_polygon(polygon, number_of_points, seed=None, exclude=None):
    """Populate given polygon with uniformly distributed points.

    Input:
       polygon - list of vertices of polygon

       number_of_points - (optional) number of points

       seed - seed for random number generator (default=None)

       exclude - list of polygons (inside main polygon) from where points
       should be excluded

    Output:
       points - list of points inside polygon

    Examples:
       populate_polygon( [[0,0], [1,0], [1,1], [0,1]], 5 )
       will return five randomly selected points inside the unit square
    """

    polygon = ensure_numeric(polygon)

    # Find outer extent of polygon
    minpx = min(polygon[:, 0])
    maxpx = max(polygon[:, 0])
    minpy = min(polygon[:, 1])
    maxpy = max(polygon[:, 1])

    # Generate random points until enough are in polygon
    seed_function(seed)
    points = []
    while len(points) < number_of_points:
        x = uniform(minpx, maxpx)
        y = uniform(minpy, maxpy)

        append = False
        if is_inside_polygon([x, y], polygon):
            append = True

            # Check exclusions
            if exclude is not None:
                for ex_poly in exclude:
                    if is_inside_polygon([x, y], ex_poly):
                        append = False

        if append is True:
            points.append([x, y])

    return points
Example #9
0
def populate_polygon(polygon, number_of_points, seed=None, exclude=None):
    """Populate given polygon with uniformly distributed points.

    Input:
       polygon - list of vertices of polygon

       number_of_points - (optional) number of points

       seed - seed for random number generator (default=None)

       exclude - list of polygons (inside main polygon) from where points
       should be excluded

    Output:
       points - list of points inside polygon

    Examples:
       populate_polygon( [[0,0], [1,0], [1,1], [0,1]], 5 )
       will return five randomly selected points inside the unit square
    """

    polygon = ensure_numeric(polygon)

    # Find outer extent of polygon
    minpx = min(polygon[:, 0])
    maxpx = max(polygon[:, 0])
    minpy = min(polygon[:, 1])
    maxpy = max(polygon[:, 1])

    # Generate random points until enough are in polygon
    seed_function(seed)
    points = []
    while len(points) < number_of_points:
        x = uniform(minpx, maxpx)
        y = uniform(minpy, maxpy)

        append = False
        if is_inside_polygon([x, y], polygon):
            append = True

            # Check exclusions
            if exclude is not None:
                for ex_poly in exclude:
                    if is_inside_polygon([x, y], ex_poly):
                        append = False

        if append is True:
            points.append([x, y])

    return points
Example #10
0
def rings_equal(x, y, rtol=1.0e-6, atol=1.0e-8):
    """Compares to linear rings as numpy arrays

    :param x: A 2d array of the first ring
    :type x: numpy.ndarray

    :param y: A 2d array of the second ring
    :type y: numpy.ndarray

    :param rtol: The relative tolerance parameter
    :type rtol: float

    :param atol: The relative tolerance parameter
    :type rtol: float

    Returns:
        * True if x == y or x' == y (up to the specified tolerance)

        where x' is x reversed in the first dimension. This corresponds to
        linear rings being seen as equal irrespective of whether they are
        organised in clock wise or counter clock wise order
    """

    x = ensure_numeric(x, numpy.float)
    y = ensure_numeric(y, numpy.float)

    msg = 'Arrays must a 2d arrays of vertices. I got %s and %s' % (x, y)
    verify(len(x.shape) == 2 and len(y.shape) == 2, msg)

    msg = 'Arrays must have two columns. I got %s and %s' % (x, y)
    verify(x.shape[1] == 2 and y.shape[1] == 2, msg)

    if (numpy.allclose(x, y, rtol=rtol, atol=atol)
            or numpy.allclose(x, y[::-1], rtol=rtol, atol=atol)):
        return True
    else:
        return False
Example #11
0
def rings_equal(x, y, rtol=1.0e-6, atol=1.0e-8):
    """Compares to linear rings as numpy arrays

    :param x: A 2d array of the first ring
    :type x: numpy.ndarray

    :param y: A 2d array of the second ring
    :type y: numpy.ndarray

    :param rtol: The relative tolerance parameter
    :type rtol: float

    :param atol: The relative tolerance parameter
    :type rtol: float

    Returns:
        * True if x == y or x' == y (up to the specified tolerance)

        where x' is x reversed in the first dimension. This corresponds to
        linear rings being seen as equal irrespective of whether they are
        organised in clock wise or counter clock wise order
    """

    x = ensure_numeric(x, numpy.float)
    y = ensure_numeric(y, numpy.float)

    msg = 'Arrays must a 2d arrays of vertices. I got %s and %s' % (x, y)
    verify(len(x.shape) == 2 and len(y.shape) == 2, msg)

    msg = 'Arrays must have two columns. I got %s and %s' % (x, y)
    verify(x.shape[1] == 2 and y.shape[1] == 2, msg)

    if (numpy.allclose(x, y, rtol=rtol, atol=atol) or
            numpy.allclose(x, y[::-1], rtol=rtol, atol=atol)):
        return True
    else:
        return False
Example #12
0
def polygon2segments(polygon):
    """Convert polygon to segments structure suitable for use in intersection

    Args:
        polygon: Nx2 array of polygon vertices

    Returns:
        A collection of line segments (x0, y0) -> (x1, y1) vectorised
        following the format::

           line[0, 0, :] = x0
           line[0, 1, :] = y0
           line[1, 0, :] = x1
           line[1, 1, :] = y1
    """

    try:
        polygon = ensure_numeric(polygon, numpy.float)
    except Exception, e:
        msg = ('Polygon could not be converted to numeric array: %s' % str(e))
        raise Exception(msg)
Example #13
0
def polygon2segments(polygon):
    """Convert polygon to segments structure suitable for use in intersection

    Args:
        polygon: Nx2 array of polygon vertices

    Returns:
        A collection of line segments (x0, y0) -> (x1, y1) vectorised
        following the format::

           line[0, 0, :] = x0
           line[0, 1, :] = y0
           line[1, 0, :] = x1
           line[1, 1, :] = y1
    """

    try:
        polygon = ensure_numeric(polygon, numpy.float)
    except Exception, e:
        msg = ('Polygon could not be converted to numeric array: %s'
               % str(e))
        raise Exception(msg)
Example #14
0
def array_to_wkt(A, geom_type='POLYGON'):
    """Convert coordinates to wkt format

    :param A: Nx2 Array of coordinates representing either a polygon or a line.
        A can be either a numpy array or a list of coordinates.
    :type A: numpy.array

    :param geom_type: Determines output keyword 'POLYGON' or 'LINESTRING'
    :type geom_type: str

    :returns: wkt: geometry in the format known to ogr: Examples

    Note:
        POLYGON((1020 1030,1020 1045,1050 1045,1050 1030,1020 1030))
        LINESTRING(1000 1000, 1100 1050)
    """

    try:
        A = ensure_numeric(A, numpy.float)
    except Exception, e:
        msg = ('Array (%s) could not be converted to numeric array. '
               'I got type %s. Error message: %s' %
               (geom_type, str(type(A)), e))
        raise Exception(msg)
Example #15
0
def interpolate_polygon_points(source, target, layer_name=None):
    """Interpolate from polygon vector layer to point vector data

    Args:
        * source: Vector data set (polygon)
        * target: Vector data set (points)
        * layer_name: Optional name of returned interpolated layer.
              If None the name of target is used for the returned layer.

    Output
        I: Vector data set; points located as target with values interpolated
        from source

    Note
        All attribute names from polygons are transferred to the points
        that are inside them.
    """

    msg = ('Vector layer to interpolate to must be point geometry. '
           'I got OGR geometry type %s' %
           geometry_type_to_string(target.geometry_type))
    verify(target.is_point_data, msg)

    msg = ('Name must be either a string or None. I got %s' %
           (str(type(target)))[1:-1])
    verify(layer_name is None or isinstance(layer_name, basestring), msg)

    attribute_names = source.get_attribute_names()
    target_attribute_names = target.get_attribute_names()

    # Include polygon_id as attribute
    attribute_names.append('polygon_id')
    attribute_names.append(DEFAULT_ATTRIBUTE)

    # Let's ensure that we don't shadow attribute names #2090.
    # Also this ensures shp file character length compliance.
    safe_attribute_name = {}
    used_names = [_ for _ in target_attribute_names]
    for name in attribute_names:
        safe_name = get_non_conflicting_attribute_name(name, used_names)
        used_names.append(safe_name)
        safe_attribute_name[name] = safe_name

    # ----------------
    # Start algorithm
    # ----------------

    # Extract point features
    points = ensure_numeric(target.get_geometry())
    attributes = target.get_data()
    original_geometry = target.get_geometry()  # Geometry for returned data

    # Extract polygon features
    geom = source.get_geometry(as_geometry_objects=True)
    data = source.get_data()
    verify(len(geom) == len(data))

    # Augment point features with empty attributes from polygon
    for a in attributes:
        # Create all attributes that exist in source
        for key in attribute_names:
            safe_key = safe_attribute_name[key]
            a[safe_key] = None

    # Traverse polygons and assign attributes to points that fall inside
    for i, polygon in enumerate(geom):
        # Carry all attributes across from source
        poly_attr = data[i]

        # Assign default attribute to indicate points inside
        poly_attr[DEFAULT_ATTRIBUTE] = True

        # Clip data points by polygons and add polygon attributes
        indices = inside_polygon(points,
                                 polygon.outer_ring,
                                 holes=polygon.inner_rings)

        for k in indices:
            for key in poly_attr:
                # Assign attributes from polygon to points
                safe_key = safe_attribute_name[key]
                attributes[k][safe_key] = poly_attr[key]
            attributes[k]['polygon_id'] = i  # Store id for associated polygon

    # Create new Vector instance and return
    V = Vector(data=attributes,
               projection=target.get_projection(),
               geometry=original_geometry,
               name=layer_name)
    return V
Example #16
0
def interpolate_polygon_points(source, target,
                               layer_name=None):
    """Interpolate from polygon vector layer to point vector data

    Args:
        * source: Vector data set (polygon)
        * target: Vector data set (points)
        * layer_name: Optional name of returned interpolated layer.
              If None the name of target is used for the returned layer.

    Output
        I: Vector data set; points located as target with values interpolated
        from source

    Note
        All attribute names from polygons are transferred to the points
        that are inside them.
    """

    msg = ('Vector layer to interpolate to must be point geometry. '
           'I got OGR geometry type %s'
           % geometry_type_to_string(target.geometry_type))
    verify(target.is_point_data, msg)

    msg = ('Name must be either a string or None. I got %s'
           % (str(type(target)))[1:-1])
    verify(layer_name is None or
           isinstance(layer_name, basestring), msg)

    attribute_names = source.get_attribute_names()

    # ----------------
    # Start algorithm
    # ----------------

    # Extract point features
    points = ensure_numeric(target.get_geometry())
    attributes = target.get_data()
    original_geometry = target.get_geometry()  # Geometry for returned data

    # Extract polygon features
    geom = source.get_geometry(as_geometry_objects=True)
    data = source.get_data()
    verify(len(geom) == len(data))

    # Include polygon_id as attribute
    attribute_names.append('polygon_id')
    attribute_names.append(DEFAULT_ATTRIBUTE)

    # Augment point features with empty attributes from polygon
    for a in attributes:
        # Create all attributes that exist in source
        for key in attribute_names:
            a[key] = None

    # Traverse polygons and assign attributes to points that fall inside
    for i, polygon in enumerate(geom):
        # Carry all attributes across from source
        poly_attr = data[i]

        # Assign default attribute to indicate points inside
        poly_attr[DEFAULT_ATTRIBUTE] = True

        # Clip data points by polygons and add polygon attributes
        indices = inside_polygon(points, polygon.outer_ring,
                                 holes=polygon.inner_rings)

        for k in indices:
            for key in poly_attr:
                # Assign attributes from polygon to points
                attributes[k][key] = poly_attr[key]
            attributes[k]['polygon_id'] = i  # Store id for associated polygon

    # Create new Vector instance and return
    V = Vector(data=attributes,
               projection=target.get_projection(),
               geometry=original_geometry,
               name=layer_name)
    return V
Example #17
0
def clip_line_by_polygon(line, polygon,
                         closed=True,
                         polygon_bbox=None,
                         check_input=True):
    """Clip line segments by polygon

    Input:
       line: Sequence of line nodes: [[x0, y0], [x1, y1], ...] or
        the equivalent Nx2 numpy array

       polygon: list or Nx2 array of polygon vertices

       closed: (optional) determine whether points on boundary should be
       regarded as belonging to the polygon (closed = True)
       or not (closed = False).False is not recommended here

       polygon_bbox: Provide bounding box around polygon if known.
       This is a small optimisation.

       check_input: Allows faster execution if set to False

    Outputs:
       inside_lines: Clipped lines that are inside polygon
       outside_lines: Clipped lines that are outside polygon

       Both outputs lines take the form of lists of Nx2 numpy arrays,
       i.e. each line is an array of multiple segments

    Example:

        U = [[0,0], [1,0], [1,1], [0,1]]  # Unit square

        # Simple horizontal fully intersecting line
        line = [[-1, 0.5], [2, 0.5]]

        inside_line_segments, outside_line_segments = \
            clip_line_by_polygon(line, polygon)

        print numpy.allclose(inside_line_segments,
                              [[[0, 0.5], [1, 0.5]]])

        print numpy.allclose(outside_line_segments,
                              [[[-1, 0.5], [0, 0.5]],
                               [[1, 0.5], [2, 0.5]]])

    Remarks:
       The assumptions listed in separate_points_by_polygon apply

       Output line segments are listed as separate lines i.e. not joined
    """

    if check_input:
        # Input checks
        msg = 'Keyword argument "closed" must be boolean'
        if not isinstance(closed, bool):
            raise RuntimeError(msg)

        try:
            line = ensure_numeric(line, numpy.float)
        except Exception, e:
            msg = ('Line could not be converted to numeric array: %s'
                   % str(e))
            raise Exception(msg)

        msg = 'Line segment array must be a 2d array of vertices'
        if not len(line.shape) == 2:
            raise RuntimeError(msg)

        msg = 'Line array must have two columns'
        if not line.shape[1] == 2:
            raise RuntimeError(msg)

        try:
            polygon = ensure_numeric(polygon, numpy.float)
        except Exception, e:
            msg = ('Polygon could not be converted to numeric array: %s'
                   % str(e))
            raise Exception(msg)
Example #18
0
def separate_points_by_polygon(
        points,
        polygon,
        polygon_bbox=None,
        closed=True,
        check_input=True,
        use_numpy=True):
    """Determine whether points are inside or outside a polygon.

    Args:
        * points: Tuple of (x, y) coordinates, or list of tuples
        * polygon: list or Nx2 array of polygon vertices
        * polygon_bbox: (optional) bounding box for polygon
        * closed: (optional) determine whether points on boundary should be
              regarded as belonging to the polygon (closed = True)
              or not (closed = False). If None, boundary is left undefined,
              i.e. some points on boundary may be deemed to be inside while
              others may be deemed to be outside. This options makes
              the code faster.
        * check_input: Allows faster execution if set to False
        * use_numpy: Use the fast numpy implementation

    Returns:
        * indices_inside_polygon: array of indices of points
              falling inside the polygon
        * indices_outside_polygon: array of indices of points
              falling outside the polygon

    Raises: A generic Exception is raised for unexpected input.

    Example:

        U = [[0,0], [1,0], [1,1], [0,1]]  # Unit square
        separate_points_by_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)

        will return the indices [0, 2, 1] and count == 2 as only the first
        and the last point are inside the unit square

    Remarks:
        The vertices may be listed clockwise or counterclockwise and
        the first point may optionally be repeated.
        Polygons do not need to be convex.
        Polygons can have holes in them and points inside a hole is
        regarded as being outside the polygon.

    Algorithm is based on work by Darel Finley,
    http://www.alienryderflex.com/polygon/
    """

    # FIXME (Ole): Make sure bounding box here follows same format as
    # those returned by layers. Methinks they don't at the moment
    if check_input:
        # Input checks
        msg = 'Keyword argument "closed" must be boolean or None'
        if not (isinstance(closed, bool) or closed is None):
            raise PolygonInputError(msg)

        try:
            points = ensure_numeric(points, numpy.float)
        except Exception, e:
            msg = ('Points could not be converted to numeric array: %s'
                   % str(e))
            raise PolygonInputError(msg)

        try:
            polygon = ensure_numeric(polygon, numpy.float)
        except Exception, e:
            msg = ('Polygon could not be converted to numeric array: %s'
                   % str(e))
            raise PolygonInputError(msg)
Example #19
0
def point_on_line(points, line, rtol=1.0e-5, atol=1.0e-8,
                  check_input=True):
    """Determine if a point is on a line segment

    Input
        points: Coordinates of either
                * one point given by sequence [x, y]
                * multiple points given by list of points or Nx2 array

        line: Endpoint coordinates [[x0, y0], [x1, y1]] or the equivalent 2x2
        numeric array with each row corresponding to a point.

        rtol: Relative error for how close a point must be to be accepted

        atol: Absolute error for how close a point must be to be accepted

    Output
        True or False

    Notes

    Line can be degenerate and function still works to discern coinciding
    points from non-coinciding.

    Tolerances rtol and atol are used with numpy.allclose()
    """

    one_point = False
    if check_input:
        # Prepare input data
        points = ensure_numeric(points)
        line = ensure_numeric(line)

        if len(points.shape) == 1:
            # One point only - make into 1 x 2 array
            points = points[numpy.newaxis, :]
            one_point = True
        else:
            one_point = False

        msg = 'Argument points must be either [x, y] or an Nx2 array of points'
        if len(points.shape) != 2:
            raise Exception(msg)
        if not points.shape[0] > 0:
            raise Exception(msg)
        if points.shape[1] != 2:
            raise Exception(msg)

    N = points.shape[0]  # Number of points

    x = points[:, 0]
    y = points[:, 1]
    x0, y0 = line[0]
    x1, y1 = line[1]

    # Vector from beginning of line to point
    a0 = x - x0
    a1 = y - y0

    # It's normal vector
    a_normal0 = a1
    a_normal1 = -a0

    # Vector parallel to line
    b0 = x1 - x0
    b1 = y1 - y0

    # Dot product
    nominator = abs(a_normal0 * b0 + a_normal1 * b1)
    denominator = b0 * b0 + b1 * b1

    # Determine if point vector is parallel to line up to a tolerance
    is_parallel = numpy.zeros(N, dtype=numpy.bool)  # All False
    is_parallel[nominator <= atol + rtol * denominator] = True

    # Determine for points parallel to line if they are within end points
    a0p = a0[is_parallel]
    a1p = a1[is_parallel]

    len_a = numpy.sqrt(a0p * a0p + a1p * a1p)
    len_b = numpy.sqrt(b0 * b0 + b1 * b1)
    cross = a0p * b0 + a1p * b1

    # Initialise result to all False
    result = numpy.zeros(N, dtype=numpy.bool)

    # Result is True only if a0 * b0 + a1 * b1 >= 0 and len_a <= len_b
    result[is_parallel] = (cross >= 0) * (len_a <= len_b)

    # Return either boolean scalar or boolean vector
    if one_point:
        return result[0]
    else:
        return result
Example #20
0
def intersection(line0, line1):
    """Returns intersecting point between two line segments.

    If the lines are parallel or coincide partly (i.e. share a common segment),
    they are considered to not intersect.

    Inputs:
        line0: A simple line segment defined by two end points:
              [[x0, y0], [x1, y1]]

        line1: A collection of line segments vectorised following the format
               line[0, 0, :] = x2
               line[0, 1, :] = y2
               line[1, 0, :] = x3
               line[1, 1, :] = y3

    Output:
        intersections: Nx2 array with intersection points or nan
                       (in case of no intersection)
                       If line1 consisted of just one line,
                       None is returned for backwards compatibility


    Notes:

    A vectorised input line can be constructed either as list::

        line1 = [[[0, 24, 0, 15],    # x2
                  [12, 0, 24, 0]],   # y2
                 [[24, 0, 0, 5],     # x3
                  [0, 12, 12, 15]]]  # y3

    or as an array::

        line1 = numpy.zeros(16).reshape(2, 2, 4)  # Four segments
        line1[0, 0, :] = [0, 24, 0, 15]   # x2
        line1[0, 1, :] = [12, 0, 24, 0]   # y2
        line1[1, 0, :] = [24, 0, 0, 5]    # x3
        line1[1, 1, :] = [0, 12, 12, 15]  # y3


    To select array of intersections from result, use the following idiom::

        value = intersection(line0, line1)
        mask = -numpy.isnan(value[:, 0])
        v = value[mask]
    """

    line0 = ensure_numeric(line0, numpy.float)
    line1 = ensure_numeric(line1, numpy.float)

    x0, y0 = line0[0, :]
    x1, y1 = line0[1, :]

    # Special treatment of return value if line1 was non vectorised
    if len(line1.shape) == 2:
        one_point = True
        # Broadcast to vectorised version
        line1 = line1.reshape(2, 2, 1)
    else:
        one_point = False

    # Extract vectorised coordinates
    x2 = line1[0, 0, :]
    y2 = line1[0, 1, :]
    x3 = line1[1, 0, :]
    y3 = line1[1, 1, :]

    # Calculate denominator (lines are parallel if it is 0)
    y3y2 = y3 - y2
    x3x2 = x3 - x2
    x1x0 = x1 - x0
    y1y0 = y1 - y0
    x2x0 = x2 - x0
    y2y0 = y2 - y0
    denominator = y3y2 * x1x0 - x3x2 * y1y0

    # Suppress numpy warnings (as we'll be dividing by zero)
    original_numpy_settings = numpy.seterr(invalid='ignore', divide='ignore')

    u0 = (y3y2 * x2x0 - x3x2 * y2y0) / denominator
    u1 = (x2x0 * y1y0 - y2y0 * x1x0) / denominator

    # Restore numpy warnings
    numpy.seterr(**original_numpy_settings)

    # Only points that lie within given line segments are true intersections
    mask = (0.0 <= u0) * (u0 <= 1.0) * (0.0 <= u1) * (u1 <= 1.0)

    # Calculate intersection points
    x = x0 + u0[mask] * x1x0
    y = y0 + u0[mask] * y1y0

    # Return intersection points as N x 2 array
    N = line1.shape[2]
    result = numpy.zeros((N, 2)) * numpy.nan
    result[mask, 0] = x
    result[mask, 1] = y

    # Special treatment of return value if line1 was non vectorised
    if one_point:
        result = result.reshape(2)
        if numpy.any(numpy.isnan(result)):
            return None
        else:
            return result

    # Normal return of Nx2 array of intersections (or nan)
    return result
Example #21
0
def separate_points_by_polygon(points,
                               polygon,
                               polygon_bbox=None,
                               closed=True,
                               check_input=True,
                               use_numpy=True):
    """Determine whether points are inside or outside a polygon.

    Args:
        * points: Tuple of (x, y) coordinates, or list of tuples
        * polygon: list or Nx2 array of polygon vertices
        * polygon_bbox: (optional) bounding box for polygon
        * closed: (optional) determine whether points on boundary should be
              regarded as belonging to the polygon (closed = True)
              or not (closed = False). If None, boundary is left undefined,
              i.e. some points on boundary may be deemed to be inside while
              others may be deemed to be outside. This options makes
              the code faster.
        * check_input: Allows faster execution if set to False
        * use_numpy: Use the fast numpy implementation

    Returns:
        * indices_inside_polygon: array of indices of points
              falling inside the polygon
        * indices_outside_polygon: array of indices of points
              falling outside the polygon

    Raises: A generic Exception is raised for unexpected input.

    Example:

        U = [[0,0], [1,0], [1,1], [0,1]]  # Unit square
        separate_points_by_polygon( [[0.5, 0.5], [1, -0.5], [0.3, 0.2]], U)

        will return the indices [0, 2, 1] and count == 2 as only the first
        and the last point are inside the unit square

    Remarks:
        The vertices may be listed clockwise or counterclockwise and
        the first point may optionally be repeated.
        Polygons do not need to be convex.
        Polygons can have holes in them and points inside a hole is
        regarded as being outside the polygon.

    Algorithm is based on work by Darel Finley,
    http://www.alienryderflex.com/polygon/
    """

    # FIXME (Ole): Make sure bounding box here follows same format as
    # those returned by layers. Methinks they don't at the moment
    if check_input:
        # Input checks
        msg = 'Keyword argument "closed" must be boolean or None'
        if not (isinstance(closed, bool) or closed is None):
            raise PolygonInputError(msg)

        try:
            points = ensure_numeric(points, numpy.float)
        except Exception, e:
            msg = ('Points could not be converted to numeric array: %s' %
                   str(e))
            raise PolygonInputError(msg)

        try:
            polygon = ensure_numeric(polygon, numpy.float)
        except Exception, e:
            msg = ('Polygon could not be converted to numeric array: %s' %
                   str(e))
            raise PolygonInputError(msg)
Example #22
0
def intersection(line0, line1):
    """Returns intersecting point between two line segments.

    If the lines are parallel or coincide partly (i.e. share a common segment),
    they are considered to not intersect.

    Inputs:
        line0: A simple line segment defined by two end points:
              [[x0, y0], [x1, y1]]

        line1: A collection of line segments vectorised following the format
               line[0, 0, :] = x2
               line[0, 1, :] = y2
               line[1, 0, :] = x3
               line[1, 1, :] = y3

    Output:
        intersections: Nx2 array with intersection points or nan
                       (in case of no intersection)
                       If line1 consisted of just one line,
                       None is returned for backwards compatibility


    Notes:

    A vectorised input line can be constructed either as list::

        line1 = [[[0, 24, 0, 15],    # x2
                  [12, 0, 24, 0]],   # y2
                 [[24, 0, 0, 5],     # x3
                  [0, 12, 12, 15]]]  # y3

    or as an array::

        line1 = numpy.zeros(16).reshape(2, 2, 4)  # Four segments
        line1[0, 0, :] = [0, 24, 0, 15]   # x2
        line1[0, 1, :] = [12, 0, 24, 0]   # y2
        line1[1, 0, :] = [24, 0, 0, 5]    # x3
        line1[1, 1, :] = [0, 12, 12, 15]  # y3


    To select array of intersections from result, use the following idiom::

        value = intersection(line0, line1)
        mask = -numpy.isnan(value[:, 0])
        v = value[mask]
    """

    line0 = ensure_numeric(line0, numpy.float)
    line1 = ensure_numeric(line1, numpy.float)

    x0, y0 = line0[0, :]
    x1, y1 = line0[1, :]

    # Special treatment of return value if line1 was non vectorised
    if len(line1.shape) == 2:
        one_point = True
        # Broadcast to vectorised version
        line1 = line1.reshape(2, 2, 1)
    else:
        one_point = False

    # Extract vectorised coordinates
    x2 = line1[0, 0, :]
    y2 = line1[0, 1, :]
    x3 = line1[1, 0, :]
    y3 = line1[1, 1, :]

    # Calculate denominator (lines are parallel if it is 0)
    y3y2 = y3 - y2
    x3x2 = x3 - x2
    x1x0 = x1 - x0
    y1y0 = y1 - y0
    x2x0 = x2 - x0
    y2y0 = y2 - y0
    denominator = y3y2 * x1x0 - x3x2 * y1y0

    # Suppress numpy warnings (as we'll be dividing by zero)
    original_numpy_settings = numpy.seterr(invalid='ignore', divide='ignore')

    u0 = (y3y2 * x2x0 - x3x2 * y2y0) / denominator
    u1 = (x2x0 * y1y0 - y2y0 * x1x0) / denominator

    # Restore numpy warnings
    numpy.seterr(**original_numpy_settings)

    # Only points that lie within given line segments are true intersections
    mask = (0.0 <= u0) * (u0 <= 1.0) * (0.0 <= u1) * (u1 <= 1.0)

    # Calculate intersection points
    x = x0 + u0[mask] * x1x0
    y = y0 + u0[mask] * y1y0

    # Return intersection points as N x 2 array
    N = line1.shape[2]
    result = numpy.zeros((N, 2)) * numpy.nan
    result[mask, 0] = x
    result[mask, 1] = y

    # Special treatment of return value if line1 was non vectorised
    if one_point:
        result = result.reshape(2)
        if numpy.any(numpy.isnan(result)):
            return None
        else:
            return result

    # Normal return of Nx2 array of intersections (or nan)
    return result
Example #23
0
def point_on_line(points, line, rtol=1.0e-5, atol=1.0e-8, check_input=True):
    """Determine if a point is on a line segment

    Input
        points: Coordinates of either
                * one point given by sequence [x, y]
                * multiple points given by list of points or Nx2 array

        line: Endpoint coordinates [[x0, y0], [x1, y1]] or the equivalent 2x2
        numeric array with each row corresponding to a point.

        rtol: Relative error for how close a point must be to be accepted

        atol: Absolute error for how close a point must be to be accepted

    Output
        True or False

    Notes

    Line can be degenerate and function still works to discern coinciding
    points from non-coinciding.

    Tolerances rtol and atol are used with numpy.allclose()
    """

    one_point = False
    if check_input:
        # Prepare input data
        points = ensure_numeric(points)
        line = ensure_numeric(line)

        if len(points.shape) == 1:
            # One point only - make into 1 x 2 array
            points = points[numpy.newaxis, :]
            one_point = True
        else:
            one_point = False

        msg = 'Argument points must be either [x, y] or an Nx2 array of points'
        if len(points.shape) != 2:
            raise Exception(msg)
        if not points.shape[0] > 0:
            raise Exception(msg)
        if points.shape[1] != 2:
            raise Exception(msg)

    N = points.shape[0]  # Number of points

    x = points[:, 0]
    y = points[:, 1]
    x0, y0 = line[0]
    x1, y1 = line[1]

    # Vector from beginning of line to point
    a0 = x - x0
    a1 = y - y0

    # It's normal vector
    a_normal0 = a1
    a_normal1 = -a0

    # Vector parallel to line
    b0 = x1 - x0
    b1 = y1 - y0

    # Dot product
    nominator = abs(a_normal0 * b0 + a_normal1 * b1)
    denominator = b0 * b0 + b1 * b1

    # Determine if point vector is parallel to line up to a tolerance
    is_parallel = numpy.zeros(N, dtype=numpy.bool)  # All False
    is_parallel[nominator <= atol + rtol * denominator] = True

    # Determine for points parallel to line if they are within end points
    a0p = a0[is_parallel]
    a1p = a1[is_parallel]

    len_a = numpy.sqrt(a0p * a0p + a1p * a1p)
    len_b = numpy.sqrt(b0 * b0 + b1 * b1)
    cross = a0p * b0 + a1p * b1

    # Initialise result to all False
    result = numpy.zeros(N, dtype=numpy.bool)

    # Result is True only if a0 * b0 + a1 * b1 >= 0 and len_a <= len_b
    result[is_parallel] = (cross >= 0) * (len_a <= len_b)

    # Return either boolean scalar or boolean vector
    if one_point:
        return result[0]
    else:
        return result
Example #24
0
def clip_line_by_polygon(line,
                         polygon,
                         closed=True,
                         polygon_bbox=None,
                         check_input=True):
    """Clip line segments by polygon

    Input:
       line: Sequence of line nodes: [[x0, y0], [x1, y1], ...] or
        the equivalent Nx2 numpy array

       polygon: list or Nx2 array of polygon vertices

       closed: (optional) determine whether points on boundary should be
       regarded as belonging to the polygon (closed = True)
       or not (closed = False).False is not recommended here

       polygon_bbox: Provide bounding box around polygon if known.
       This is a small optimisation.

       check_input: Allows faster execution if set to False

    Outputs:
       inside_lines: Clipped lines that are inside polygon
       outside_lines: Clipped lines that are outside polygon

       Both outputs lines take the form of lists of Nx2 numpy arrays,
       i.e. each line is an array of multiple segments

    Example:

        U = [[0,0], [1,0], [1,1], [0,1]]  # Unit square

        # Simple horizontal fully intersecting line
        line = [[-1, 0.5], [2, 0.5]]

        inside_line_segments, outside_line_segments = \
            clip_line_by_polygon(line, polygon)

        print numpy.allclose(inside_line_segments,
                              [[[0, 0.5], [1, 0.5]]])

        print numpy.allclose(outside_line_segments,
                              [[[-1, 0.5], [0, 0.5]],
                               [[1, 0.5], [2, 0.5]]])

    Remarks:
       The assumptions listed in separate_points_by_polygon apply

       Output line segments are listed as separate lines i.e. not joined
    """

    if check_input:
        # Input checks
        msg = 'Keyword argument "closed" must be boolean'
        if not isinstance(closed, bool):
            raise RuntimeError(msg)

        try:
            line = ensure_numeric(line, numpy.float)
        except Exception, e:
            msg = ('Line could not be converted to numeric array: %s' % str(e))
            raise Exception(msg)

        msg = 'Line segment array must be a 2d array of vertices'
        if not len(line.shape) == 2:
            raise RuntimeError(msg)

        msg = 'Line array must have two columns'
        if not line.shape[1] == 2:
            raise RuntimeError(msg)

        try:
            polygon = ensure_numeric(polygon, numpy.float)
        except Exception, e:
            msg = ('Polygon could not be converted to numeric array: %s' %
                   str(e))
            raise Exception(msg)