Exemple #1
0
def _one_tangent(points, k1, k2, k3, weight):
    v1 = points[k2] - points[k1]
    v2 = points[k3] - points[k2]
    l1 = vu.naive_length(v1)
    l2 = vu.naive_length(v2)
    if weight == 'square':
        return vu.unit_vector(v1 / (l1 * l1) + v2 / (l2 * l2))
    elif weight == 'cube':
        return vu.unit_vector(v1 / (l1 * l1 * l1) + v2 / (l2 * l2 * l2))
    else:  # linear weight mode
        return vu.unit_vector(v1 / l1 + v2 / l2)
Exemple #2
0
def tangents(points, weight='linear', closed=False):
    """Returns a numpy array of tangent unit vectors for an ordered list of points.

    arguments:
        points (numpy float array of shape (N, 3)): points defining a line
        weight (string, default 'linear'): one of 'linear', 'square' or 'cube', giving
            increased weight to relatively shorter of 2 line segments at each knot
        closed (boolean, default False): if True, the points are treated as a closed
            polyline with regard to end point tangents, otherwise as an open line

    returns:
        numpy float array of the same shape as points, containing a unit length tangent
        vector for each knot (point)

    note:
        if two neighbouring points are identical, a divide by zero will occur
    """

    assert points.ndim == 2 and points.shape[1] == 3
    assert weight in ['linear', 'square', 'cube']

    knot_count = len(points)
    assert knot_count > 1
    tangent_vectors = np.empty((knot_count, 3))

    for knot in range(1, knot_count - 1):
        tangent_vectors[knot] = _one_tangent(points, knot - 1, knot, knot + 1,
                                             weight)
    if closed:
        assert knot_count > 2, 'closed poly line must contain at least 3 knots for tangent generation'
        tangent_vectors[0] = _one_tangent(points, -1, 0, 1, weight)
        tangent_vectors[-1] = _one_tangent(points, -2, -1, 0, weight)
    else:
        tangent_vectors[0] = vu.unit_vector(points[1] - points[0])
        tangent_vectors[-1] = vu.unit_vector(points[-1] - points[-2])

    return tangent_vectors
Exemple #3
0
    def segment_normal(self, segment_index):
        """For a closed polyline return a unit vector giving the 2D (xy) direction of an outward facing normal to a segment."""

        successor = self._successor(segment_index)
        segment_vector = self.coordinates[successor, :2] - self.coordinates[
            segment_index, :2]
        segment_vector = vu.unit_vector(segment_vector)
        normal_vector = np.zeros(3)
        normal_vector[0] = -segment_vector[1]
        normal_vector[1] = segment_vector[0]
        cw = self.is_clockwise()
        assert cw is not None, 'polyline is straight'
        if not cw:
            normal_vector = -normal_vector
        return normal_vector
Exemple #4
0
def test_unit_vectors():
    v_set = np.array([(3.0, 4.0, 0.0), (3.7, -3.7, 3.7), (0.0, 0.0, 1.0),
                      (0.0, 0.0, 0.0)])
    one_over_root_three = 1.0 / maths.sqrt(3.0)
    expected = np.array([(3.0 / 5.0, 4.0 / 5.0, 0.0),
                         (one_over_root_three,
                          -one_over_root_three, one_over_root_three),
                         (0.0, 0.0, 1.0), (0.0, 0.0, 0.0)])
    for v, e in zip(v_set, expected):
        assert_array_almost_equal(vec.unit_vector(v), e)
    assert_array_almost_equal(vec.unit_vectors(v_set), expected)
    azi = [0.0, -90.0, 120.0, 180.0, 270.0, 360.0]
    expected = np.array([(0.0, 1.0, 0.0), (-1.0, 0.0, 0.0),
                         (maths.cos(maths.pi / 6), -0.5, 0.0),
                         (0.0, -1.0, 0.0), (-1.0, 0.0, 0.0), (0.0, 1.0, 0.0)])
    for a, e in zip(azi, expected):
        assert_array_almost_equal(vec.unit_vector_from_azimuth(a), e)
Exemple #5
0
def _pillar_vector(grid, p_index):
    # return a unit vector for direction of pillar, in direction of increasing k
    if np.all(np.isnan(grid.points_cached[:, p_index])):
        return None
    k_top = 0
    while np.any(np.isnan(grid.points_cached[k_top, p_index])):
        k_top += 1
    k_bot = grid.nk_plus_k_gaps - 1
    while np.any(np.isnan(grid.points_cached[k_bot, p_index])):
        k_bot -= 1
    if k_bot == k_top:  # following coded to treat None directions as downwards
        if grid.k_direction_is_down is False:
            if grid.z_inc_down() is False:
                return (0.0, 0.0, 1.0)
            else:
                return (0.0, 0.0, -1.0)
        else:
            if grid.z_inc_down() is False:
                return (0.0, 0.0, -1.0)
            else:
                return (0.0, 0.0, 1.0)
    else:
        return vec.unit_vector(grid.points_cached[k_bot, p_index] -
                               grid.points_cached[k_top, p_index])
Exemple #6
0
def voronoi(p, t, b, aoi: rql.Polyline):
    """Returns dual Voronoi diagram for a Delauney triangulation.

    arguments:
       p (numpy float array of shape (N, 2)): seed points used in the Delauney triangulation
       t (numpy int array of shape (M, 3)): the Delauney triangulation of p as returned by dt()
       b (numpy int array of shape (B,)): clockwise sorted list of indices into p of the boundary
          points of the triangulation t
       aoi (lines.Polyline): area of interest; a closed clockwise polyline that must strictly contain
          all p (no points exactly on or outside the polyline)

    returns:
       c, v where: c is a numpy float array of shape (M+E, 2) being the circumcircle centres of
       the M triangles and E boundary points from the aoi polygon line; and v is a list of
       N Voronoi cell lists of clockwise ints, each int being an index into c

    notes:
       the aoi polyline forms the outer boundary for the Voronoi polygons for points on the
       outer edge of the triangulation; all points p must lie strictly within the aoi, which
       must be convex; the triangulation t, of points p, must also have a convex hull; note
       that the dt() function can produce a triangulation with slight concavities on the hull,
       especially for smaller values of its container_size_factor argument
    """

    # this code assumes that the Voronoi polygon for a seed point visits the circumcentres of
    # all the triangles that make use of the point – currently understood to be always the case
    # for a Delauney triangulation

    def __aoi_intervening_nodes(aoi_count, c_count, seg_a, seg_c):
        nodes = []
        seg = seg_a
        while seg != seg_c:
            seg = (seg + 1) % aoi_count
            nodes.append(c_count + seg)
        return nodes

    def __shorter_sides_p_i(p3):
        max_length = -1.0
        max_i = None
        for i in range(3):
            opp_length = vec.naive_length(p3[i - 1] - p3[i - 2])
            if opp_length > max_length:
                max_length = opp_length
                max_i = i
        return max_i

    def __azi_between(a, c, t):
        if c < a:
            c += 360.0
        return (a <= t <= c) or (a <= t + 360.0 <= c)

    def __seg_for_ci(ci):  # returns hull segment for a boundary index
        nonlocal ca_count, cah_count, caho_count, cahon_count, wing_hull_segments
        if ci < ca_count:
            return None
        if ci < cah_count:  # hull edge intersection
            return ci - ca_count
        if ci < caho_count:  # wings
            oi, wing = divmod(ci - cah_count, 2)
            return wing_hull_segments[oi, wing]
        if ci < cahon_count:  # virtual centre for hull edge
            return ci - caho_count
        # else virtual centre for hull point; arbitrarily pick clockwise segment
        return ci - cahon_count

    def __intervening_aoi_indices(aoi_count, aoi_intersect_segments, c_count,
                                  ca_count, cah_count, ci_for_p,
                                  out_pair_intersect_segments):
        # build list of intervening aoi boundary point indices and append to list
        aoi_nodes = []
        r = [0] if len(ci_for_p) == 2 else range(len(ci_for_p))
        just_done_pair = False
        for cii in r:
            cip = ci_for_p[cii]
            ci = ci_for_p[(cii + 1) % len(ci_for_p)]
            if cip >= c_count and ci >= c_count and not just_done_pair:
                # identify aoi segments
                if cip < cah_count:
                    aoi_seg_a = aoi_intersect_segments[cip - ca_count]
                else:
                    aoi_seg_a = out_pair_intersect_segments[divmod(
                        cip - cah_count, 2)]
                if ci < cah_count:
                    aoi_seg_c = aoi_intersect_segments[ci - ca_count]
                else:
                    aoi_seg_c = out_pair_intersect_segments[divmod(
                        ci - cah_count, 2)]
                aoi_nodes += __aoi_intervening_nodes(aoi_count, c_count,
                                                     aoi_seg_a, aoi_seg_c)
                just_done_pair = True
            else:
                just_done_pair = False
        return aoi_nodes

    def __ci_non_hull(b_i, c, c_count, ci_for_p, p, p_i):
        trimmed_ci = []
        cii = 0
        finish_at = len(ci_for_p)
        while cii < finish_at:
            if ci_for_p[cii] < c_count:
                trimmed_ci.append(ci_for_p[cii])
                cii += 1
                continue
            start_cii = cii
            cii_seg = __seg_for_ci(ci_for_p[cii])
            while cii == 0 and ci_for_p[start_cii -
                                        1] >= c_count and __seg_for_ci(
                                            ci_for_p[start_cii -
                                                     1]) == cii_seg:
                start_cii -= 1
            start_cii = start_cii % len(ci_for_p)
            end_cii = cii + 1
            while end_cii < len(ci_for_p) and ci_for_p[
                    end_cii] >= c_count and __seg_for_ci(
                        ci_for_p[end_cii]) == cii_seg:
                end_cii += 1
            end_cii -= 1  # unpythonesque: end element included in scan
            if end_cii == start_cii:
                trimmed_ci.append(ci_for_p[cii])
                cii += 1
                continue
            if end_cii < start_cii:
                finish_at = start_cii
            if end_cii == (start_cii + 1) % len(ci_for_p):
                trimmed_ci.append(ci_for_p[start_cii])
                trimmed_ci.append(ci_for_p[end_cii])
                cii = end_cii + 1
                continue
            start_azi = vec.azimuth(c[ci_for_p[start_cii]] - p[p_i, :2])
            end_azi = vec.azimuth(c[ci_for_p[end_cii]] - p[p_i, :2])
            scan_cii = start_cii
            while True:
                if scan_cii == start_cii:
                    trimmed_ci.append(ci_for_p[scan_cii])
                elif scan_cii == end_cii:
                    trimmed_ci.append(ci_for_p[scan_cii])
                    break
                else:
                    # if point is around a hull corner from previous, then include (?)
                    next_cii = (scan_cii + 1) % len(ci_for_p)
                    if b_i is None and __seg_for_ci(
                            ci_for_p[scan_cii]) != __seg_for_ci(
                                ci_for_p[next_cii]):
                        trimmed_ci.append(ci_for_p[scan_cii])
                        trimmed_ci.append(ci_for_p[next_cii])
                        scan_cii = next_cii
                    elif b_i is None and __seg_for_ci(
                            trimmed_ci[-1]) != __seg_for_ci(
                                ci_for_p[scan_cii]):
                        trimmed_ci.append(ci_for_p[scan_cii])
                    else:
                        azi = vec.azimuth(c[ci_for_p[scan_cii]] - p[p_i, :2])
                        if __azi_between(start_azi, end_azi, azi):
                            trimmed_ci.append(ci_for_p[scan_cii])
                if scan_cii == end_cii:
                    break
                scan_cii = (scan_cii + 1) % len(ci_for_p)
            cii = end_cii + 1
        return trimmed_ci

    def __closest_to_seed(c, c_count, ci_for_p, hull_node_azi, p, p_i):
        best_a_i = None
        best_c_i = None
        best_a_azi = -181.0
        best_c_azi = 181.0
        for cii, val in enumerate(ci_for_p):
            if val < c_count:
                continue
            azi = vec.azimuth(c[val] - p[p_i, :2]) - hull_node_azi
            if azi > 180.0:
                azi -= 360.0
            elif azi < -180.0:
                azi += 360.0
            if 0.0 > azi > best_a_azi:
                best_a_azi = azi
                best_a_i = cii
            elif 0.0 <= azi < best_c_azi:
                best_c_azi = azi
                best_c_i = cii
        assert best_a_i is not None and best_c_i is not None
        trimmed_ci = []
        for cii, val in enumerate(ci_for_p):
            if val < c_count or cii == best_a_i or cii == best_c_i:
                trimmed_ci.append(val)
        return trimmed_ci

    def __ci_replace(c_count, ca_count, cah_count, caho_count, cahon_count,
                     ci_for_p, p, p_i, t, tc_outwith_aoi):
        # where circumcirle (or virtual) centre is outwith aoi, replace with a point on aoi boundary
        # virtual centres related to hull points (not hull edges) can be discarded
        trimmed_ci = []
        for ci in ci_for_p:
            if ci < c_count:  # genuine triangle
                if ci in tc_outwith_aoi:  # replace with one or two wing normal intersection points
                    oi = tc_outwith_aoi.index(ci)
                    wing_i = cah_count + 2 * oi
                    shorter_t_i = __shorter_sides_p_i(p[t[ci]])
                    if t[ci, shorter_t_i] == p_i:
                        trimmed_ci += [wing_i, wing_i + 1]
                    elif t[ci, shorter_t_i - 1] == p_i:
                        trimmed_ci.append(wing_i)
                    else:
                        trimmed_ci.append(wing_i + 1)
                else:
                    trimmed_ci.append(ci)
            elif ci < cahon_count:
                # extended virtual centre for a hull edge (discard hull point virtual centres)
                # replace with index for intersection point on aoi boundary
                trimmed_ci.append(ca_count + ci - caho_count)
        return trimmed_ci

    def __veroni_cells(aoi_count, aoi_intersect_segments, b, c, c_count,
                       ca_count, cah_count, caho_count, cahon_count,
                       hull_count, out_pair_intersect_segments, p, t,
                       tc_outwith_aoi):
        # list of voronoi cells (each a numpy list of node indices into c extended with aoi points etc)
        v = []
        # for each seed point build the voronoi cell
        for p_i in range(len(p)):

            # find triangles making use of that point
            ci_for_p = list(np.where(t == p_i)[0])

            # if seed point is on hull boundary, introduce three extended virtual centres
            b_i = None
            if p_i in b:
                b_i = np.where(b == p_i)[0][0]  # index into hull coordinates
                p_b_i = (
                    b_i - 1
                ) % hull_count  # predecessor, ie. anti-clockwise boundary point
                ci_for_p += [
                    caho_count + p_b_i, cahon_count + b_i, caho_count + b_i
                ]

            # find azimuths of vectors from seed point to circumcircle centres (and virtual centres)
            azi = [
                vec.azimuth(centre - p[p_i, :2]) for centre in c[ci_for_p, :2]
            ]
            # if this is a hull seed point, make a note of azimuth to virtual centre
            hull_node_azi = None if b_i is None else azi[-2]
            # sort triangle indices for seed point into clockwise order of circumcircle (and virtual) centres
            ci_for_p = [ti for (_, ti) in sorted(zip(azi, ci_for_p))]

            ci_for_p = __ci_replace(c_count, ca_count, cah_count, caho_count,
                                    cahon_count, ci_for_p, p, p_i, t,
                                    tc_outwith_aoi)

            # if this is a hull seed point, classify aoi boundary points into anti-clockwise or clockwise, and find
            # closest to seed
            if b_i is not None:
                ci_for_p = __closest_to_seed(c, c_count, ci_for_p,
                                             hull_node_azi, p, p_i)

            # for sequences on aoi boundary, just keep those between the first and last (?)

            #      elif any([ci < c_count for ci in ci_for_p]):
            else:
                ci_for_p = __ci_non_hull(b_i, c, c_count, ci_for_p, p, p_i)

            # reverse points if needed for pair of aoi points only
            assert len(ci_for_p) >= 2
            if len(ci_for_p) == 2:
                seg_0 = __seg_for_ci(ci_for_p[0])
                seg_1 = __seg_for_ci(ci_for_p[1])
                if seg_0 is not None and seg_1 is not None and seg_0 == (
                        seg_1 + 1) % hull_count:
                    ci_for_p.reverse()

            ci_for_p += __intervening_aoi_indices(aoi_count,
                                                  aoi_intersect_segments,
                                                  c_count, ca_count, cah_count,
                                                  ci_for_p,
                                                  out_pair_intersect_segments)

            #  remove circumcircle centres that are outwith area of interest
            ci_for_p = np.array([
                ti
                for ti in ci_for_p if ti >= c_count or ti not in tc_outwith_aoi
            ],
                                dtype=int)

            # find azimuths of vectors from seed point to circumcircle centres and aoi boundary points
            azi = [
                vec.azimuth(centre - p[p_i, :2]) for centre in c[ci_for_p, :2]
            ]

            # re-sort triangle indices for seed point into clockwise order of circumcircle centres and boundary points
            ordered_ci = [ti for (_, ti) in sorted(zip(azi, ci_for_p))]

            v.append(ordered_ci)
        return v

    # log.debug(f'\n\nVoronoi: nt: {len(p)}; nt: {len(t)}; hull: {len(b)}; aoi: {len(aoi.coordinates)}')
    # todo: allow aoi to be None in which case create an aoi as hull with border

    assert p.ndim == 2 and p.shape[0] > 2 and p.shape[1] >= 2
    assert t.ndim == 2 and t.shape[1] == 3
    assert b.ndim == 1 and b.shape[0] > 2
    assert len(aoi.coordinates) >= 3
    assert aoi.isclosed
    assert aoi.is_clockwise()

    # create temporary polyline for hull of triangulation
    hull = rql.Polyline(
        aoi.model,
        set_bool=True,  # polyline is closed
        set_coord=p[b],
        set_crs=aoi.crs_uuid,
        title='triangulation hull')
    hull_count = len(b)

    # check for concavities in hull
    if not hull.is_convex():
        log.warning(
            'Delauney triangulation is not convex; Voronoi diagram construction might fail'
        )

    # compute circumcircle centres
    c = np.zeros((t.shape[0], 2))
    for ti in range(len(t)):
        c[ti] = ccc(p[t[ti, 0]], p[t[ti, 1]], p[t[ti, 2]])
    c_count = len(c)

    # make list of triangle indices whose circumcircle centres are outwith the area of interest
    tc_outwith_aoi = [
        ti for ti in range(c_count) if not aoi.point_is_inside_xy(c[ti])
    ]
    o_count = len(tc_outwith_aoi)

    # make space for combined points data needed for all voronoi cell nodes:
    # 1. circumcircle centres for triangles in delauney triangulation
    # 2. nodes defining area of interest polygon
    # 3. intersection of normals to triangulation hull edges with aoi polygon
    # 4. extra intersections for normals to other two (non-hull) triangle edges, with aoi,
    #    where circumcircle centre is outside the area of interest
    # 5. extended ccc for hull edge normals
    # 6. extended ccc for hull points
    # (5 & 6 only used during construction)
    c = np.concatenate(
        (c, aoi.coordinates[:, :2], np.zeros((hull_count, 2), dtype=float),
         np.zeros((2 * o_count, 2),
                  dtype=float), np.zeros((hull_count, 2), dtype=float),
         np.zeros((hull_count, 2), dtype=float)))
    aoi_count = len(aoi.coordinates)
    ca_count = c_count + aoi_count
    cah_count = ca_count + hull_count
    caho_count = cah_count + 2 * o_count
    cahon_count = caho_count + hull_count
    assert cahon_count + hull_count == len(c)

    #  compute intersection points between hull edge normals and aoi polyline
    # also extended virtual centres for hull edges
    extension_scaling = 1000.0 * np.sum((np.max(aoi.coordinates, axis=0) -
                                         np.min(aoi.coordinates, axis=0))[:2])
    aoi_intersect_segments = np.empty((hull_count, ), dtype=int)
    for ei in range(hull_count):
        # use segment midpoint and normal methods of hull to project out
        m = hull.segment_midpoint(ei)[:2]  # midpoint
        norm_vec = hull.segment_normal(ei)[:2]
        n = m + norm_vec  # point on normal
        # use first intersection method of aoi to intersect projected normal from triangulation hull
        aoi_seg, aoi_x, aoi_y = aoi.first_line_intersection(m[0],
                                                            m[1],
                                                            n[0],
                                                            n[1],
                                                            half_segment=True)
        assert aoi_seg is not None
        # inject intersection points to extension area of c and take note of aoi segment of intersection
        c[ca_count + ei] = (aoi_x, aoi_y)
        aoi_intersect_segments[ei] = aoi_seg
        # inject extended virtual circle centres for hull edges, a long way out
        c[caho_count + ei] = c[ca_count + ei] + extension_scaling * norm_vec

    # compute extended virtual centres for hull nodes
    for ei in range(hull_count):
        pei = (ei - 1) % hull_count
        vector = vec.unit_vector(
            hull.segment_normal(pei)[:2] + hull.segment_normal(ei)[:2])
        c[cahon_count +
          ei] = hull.coordinates[ei, :2] + extension_scaling * vector

    # where cicrumcircle centres are outwith aoi, compute intersections of normals of wing edges with aoi
    out_pair_intersect_segments = np.empty((o_count, 2), dtype=int)
    wing_hull_segments = np.empty((o_count, 2), dtype=int)
    for oi, ti in enumerate(tc_outwith_aoi):
        tpi = __shorter_sides_p_i(p[t[ti]])
        for wing in range(2):
            # note: triangle nodes are anticlockwise
            m = 0.5 * (p[t[ti, tpi - 1]] +
                       p[t[ti, tpi]])[:2]  # triangle edge midpoint
            edge_v = p[t[ti, tpi]] - p[t[ti, tpi - 1]]
            n = m + np.array(
                (-edge_v[1], edge_v[0]
                 ))  # point on perpendicular bisector of triangle edge
            o_seg, o_x, o_y = aoi.first_line_intersection(m[0],
                                                          m[1],
                                                          n[0],
                                                          n[1],
                                                          half_segment=True)
            c[cah_count + 2 * oi + wing] = (o_x, o_y)
            out_pair_intersect_segments[oi, wing] = o_seg
            wing_hull_segments[oi, wing], _, _ = hull.first_line_intersection(
                m[0], m[1], n[0], n[1], half_segment=True)
            tpi = (tpi + 1) % 3

    v = __veroni_cells(aoi_count, aoi_intersect_segments, b, c, c_count,
                       ca_count, cah_count, caho_count, cahon_count,
                       hull_count, out_pair_intersect_segments, p, t,
                       tc_outwith_aoi)

    return c[:caho_count], v