Skip to content

Shafts and shortest path finding

More about shafts and shortest path finding

In this notebook, we will first place the agents in an optimal place by loading in previously computed csv's. Next, we will manually place shafts to make sure that there will be a shaft on the highest floor levels. Finally, we will compute shortest paths between: 1. an agent and a shaft 2. a shaft and another shaft on the ground floor 3. a shaft and another shaft on a higher floor level

The inputs of this notebook are the final voxelized envelope, the full voxelized envelope and the immediate context. We need the full voxelized envelope without the valley for computing shafts on a higher floor level. The immediate context is only used for visualisation

0. Initialization

0.1. Load required libraries

import os
import topogenesis as tg
import pyvista as pv
import trimesh as tm
import numpy as np
import networkx as nx
from sklearn.cluster import KMeans
np.random.seed(0)
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import cm
import pandas as pd
import copy
import scipy as sp
# extra import function
def lattice_from_csv(file_path):
    # read metadata
    meta_df = pd.read_csv(file_path, nrows=3)

    shape = np.array(meta_df['shape'])
    unit = np.array(meta_df['unit'])
    minbound = np.array(meta_df['minbound'])

    # read lattice
    lattice_df = pd.read_csv(file_path, skiprows=5)

    # create the buffer
    buffer = np.array(lattice_df['value']).reshape(shape)

    # create the lattice
    l = tg.to_lattice(buffer, minbound=minbound, unit=unit)

    return l

0.2. Define the Neighborhood (Stencil)

# creating neighborhood definition
stencil = tg.create_stencil("von_neumann", 1, 1)
# setting the center to zero
stencil.set_index([0,0,0], 0)

# creating neighborhood definition
stencil2 = tg.create_stencil("von_neumann", 1, 1)
# setting the center to zero
stencil2.set_index([0, 0, 0], 0)
stencil2.set_index([0, 0, 1], 0)
stencil2.set_index([0, 0, -1], 0)

# creating neighborhood definition - center with 0 neighbours
s_z = tg.create_stencil("von_neumann", 0, 1)
# setting the center to zero
s_z.set_index([0, 0, 0], 0)
# setting z neighbours to 1
s_z.set_index([0, 0,-1], 1)
s_z.set_index([0, 0, 1], 1)

# creating neighborhood definition - center with 1x neighbours
s_xy = tg.create_stencil("von_neumann", 1, 1)
# setting the center to zero
s_xy.set_index([0, 0, 0], 0)
# setting z neighbours to 0
s_xy.set_index([0, 0, 1], 0)
s_xy.set_index([0, 0, -1], 0)

0.3. Load the envelope lattice as the avialbility lattice

# loading the envelope lattice from csv
lattice_path = os.path.relpath('../data/final_envelope_new.csv')
avail_lattice = lattice_from_csv(lattice_path) 
init_avail_lattice = tg.to_lattice(np.copy(avail_lattice), avail_lattice) 

# loading the lattice full lattice from csv
lattice_path = os.path.relpath('../data/envelope_highres_new.csv')
full_lattice = lattice_from_csv(lattice_path) 
init_full_lattice = tg.to_lattice(np.copy(full_lattice), full_lattice) 

#loading the immediate context as obj
immediate_context = os.path.relpath('../data/immediate_context.obj')
immediate_context_mesh = tm.load(immediate_context)

0.4. Load Agents Information

# loading program (agents information) from CSV
prgm_path = os.path.relpath('../data/program.csv')
agn_info = np.genfromtxt(prgm_path, delimiter=',')[1:, 1:]
# extract agent ids
agn_ids = agn_info[:, 0]
# extract agent preferences
agn_prefs = agn_info[:, 1:]
# extract agent preference to expand in the z-direction
agn_expandz = agn_info[:, 38]
# extract maximum voxels of each agent agent. This represents the maximu8m area & volume
agn_vox_req = agn_info[:, 39]
agn_silent_level = agn_info[:, 43]
agn_noise_repel = agn_info[:, 44]
agn_info_df = pd.read_csv('../data/program.csv') 
#print (agn_info_df["student_housing_acc"])
# loading the lattice from csv
sun_acc_path = os.path.relpath('../data/sun_access_highres.csv')
sun_acc_lattice = lattice_from_csv(sun_acc_path)

# loading the lattice from csv
ent1_acc_path = os.path.relpath('../data/ent1_access.csv')
ent1_acc_lattice = lattice_from_csv(ent1_acc_path)

# loading the lattice from csv
ent2_acc_path = os.path.relpath('../data/ent2_access.csv')
ent2_acc_lattice = lattice_from_csv(ent2_acc_path)

# loading the lattice from csv
ent3_1_acc_path = os.path.relpath('../data/ent3.1_access.csv')
ent3_1_acc_lattice = lattice_from_csv(ent3_1_acc_path)

# loading the lattice from csv
ent3_2_acc_path = os.path.relpath('../data/ent3.2_access.csv')
ent3_2_acc_lattice = lattice_from_csv(ent3_2_acc_path)

# loading the lattice from csv
ent3_3_acc_path = os.path.relpath('../data/ent3.3_access.csv')
ent3_3_acc_lattice = lattice_from_csv(ent3_3_acc_path)

# loading the lattice from csv
ent4_acc_path = os.path.relpath('../data/ent4_access.csv')
ent4_acc_lattice = lattice_from_csv(ent4_acc_path)

# loading the lattice from csv
ent5_acc_path = os.path.relpath('../data/ent5_access.csv')
ent5_acc_lattice = lattice_from_csv(ent5_acc_path)

# loading the lattice from csv
ent6_acc_path = os.path.relpath('../data/ent6_access.csv')
ent6_acc_lattice = lattice_from_csv(ent6_acc_path)

# loading the lattice from csv
ent7_acc_path = os.path.relpath('../data/ent7_access.csv')
ent7_acc_lattice = lattice_from_csv(ent7_acc_path)

# loading the lattice from csv
noise_acc_path = os.path.relpath('../data/HeerBokelweg_noise.csv')
noise_acc_lattice = lattice_from_csv(noise_acc_path)

# loading the lattice from csv
public_green_acc_path = os.path.relpath('../data/public_greenery_highres.csv')
public_green_acc_lattice = lattice_from_csv(public_green_acc_path)

# loading the lattice from csv
intern_green_acc_path = os.path.relpath('../data/green_openings_3.6.csv')
intern_green_acc_lattice = lattice_from_csv(intern_green_acc_path)

# loading the lattice from csv
intern_facade_acc_path = os.path.relpath('../data/inner_facade_acces.csv')
intern_facade_acc_lattice = lattice_from_csv(intern_facade_acc_path)

# loading the lattice from csv
ent6test_acc_path = os.path.relpath('../data/ent6test1.csv')
ent6test_acc_lattice = lattice_from_csv(ent6test_acc_path)

env_info_dict = {
    #"ent1_acces": "" ,
    #"ent2_acces": "" ,
    #"ent3_acces": "" ,
    #"ent4_acces": "" ,
    #"ent5_acces": "" ,
    #"ent6_acces": "" ,
    #"ent7_acces": "" ,
    #"student_housing_acc": "" ,
    #"assisted_living_acc": "" ,
    #"starter_housing_acc": "" ,
    #"restaurant_acc": "" ,
    #"shop_acc": "" ,
    #"cocooking_acc": "" ,
    #"pub_acc": "" ,
    #"gym_acc": "" ,
    #"arcade_acc": "" ,
    #"cinema_acc": "" ,
    #"office_acc": "" ,
    #"cowork_acc": "" ,
    #"library_acc": "" ,
    #"fablabs_acc": "" ,
    #"catering_acc": "" ,
    #"catering2_acc": "" ,
    #"catering3_acc": "" ,
    #"coffeehub_acc": "" ,
    #"expandz": "" ,
    #"vox_req": "" ,
    "noise_acc": noise_acc_lattice ,
    #"public_green_acc": public_green_acc_lattice ,
    #"intern_green_acc": intern_green_acc_lattice ,
    #"inner_facade_acc": intern_facade_acc_lattice ,

}
env_info_list = []
env_info_dict_copy = env_info_dict. copy()
env_info_list.append(env_info_dict_copy)
env_info_base = copy.deepcopy(env_info_list)

0.5. Extract the connectivity graph

# find the neighbours in the lattice
def find_neighbours_masked(lattice, stencil, loc):
    neigh_locs = np.argwhere(stencil) - stencil.origin + loc
    neigh_filter = np.all(neigh_locs > -1, axis=1) * np.all(neigh_locs < np.array(lattice.shape), axis=1)
    neigh_3d = neigh_locs[neigh_filter]
    neigh_1d = [np.ravel_multi_index(n_loc, avail_lattice.shape) for n_loc in neigh_3d]
    return(neigh_1d)
# find the number of all voxels
vox_count = avail_lattice.size 

# initialize the adjacency matrix
adj_mtrx = np.zeros((vox_count,vox_count))

# Finding the index of the available voxels in avail_lattice
avail_index = np.array(np.where(avail_lattice == 1)).T

# fill the adjacency matrix using the list of all neighbours
for vox_loc in avail_index:
    # find the 1D id
    vox_id = np.ravel_multi_index(vox_loc, avail_lattice.shape)
    # retrieve the list of neighbours of the voxel based on the stencil
    vox_neighs = find_neighbours_masked(avail_lattice, stencil, loc = vox_loc)
    # iterating over the neighbours
    for neigh in vox_neighs:
        # setting the entry to one
        adj_mtrx[vox_id, neigh] = 1.0

# construct the graph 
g = nx.from_numpy_array(adj_mtrx)
# import the adj matrix
import pickle
dist_mtrx_loaded = pickle.load( open( "../data/dist_mtrx.p", "rb" ) )

1. Give the agents an optimal location

1.1. Agent initialization

# initialize the occupation lattice
occ_lattice = avail_lattice * 0 - 1

# Finding the index of the available voxels in avail_lattice
avail_flat = avail_lattice.flatten()
avail_index = np.array(np.where(avail_lattice == 1)).T

# count the number of spaces (rows) and intiialize an agent for each space
agn_num = len(agn_info)

# adding the origins to the agents locations
agn_locs = []

# retrieving the entrance access value of the free neighbours
for a_id in agn_ids:    
    voxel_vals = []
    pot_voxels = []
    # retrieve agent preferences;
    a_pref = agn_prefs[int(a_id)]
    a_pref_dict = agn_info_df.loc[a_id]
    # Voxel Evaluation Loop
    for pot_vox in avail_index:
        if avail_lattice[tuple(pot_vox)]:

            global_vox_value = 1.0
            # for every lattice in the environment informations
            for info_key, info_lattice in env_info_dict.items():
                vox_val = info_lattice[tuple(pot_vox)]
                print(vox_val)
                # agn_vox_val = np.power(vox_val, a_pref[i])
                agn_vox_val = np.power(vox_val, a_pref_dict[info_key])
                global_vox_value *= agn_vox_val
            # add the neighbour value to the list of values
            voxel_vals.append(global_vox_value)
            pot_voxels.append(pot_vox)

    # convert to numpy array
    voxel_vals = np.array(voxel_vals)
    # convert to numpy array
    pot_voxels = np.array(pot_voxels)
    # select the neighbour with highest value 
    selected_int = np.argmax(voxel_vals) 
    # find 3D intiger index of selected neighbour
    selected_neigh_3d_id = tuple(pot_voxels[selected_int].T)
    # find the location of the newly selected neighbour
    selected_neigh_loc = np.array(selected_neigh_3d_id).flatten()

    # add the newly selected neighbour location to agent locations
    agn_locs.append([selected_neigh_loc])
    # set the newly selected neighbour as UNavailable (0) in the availability lattice
    avail_lattice[selected_neigh_3d_id] = 0
    # set the newly selected neighbour as OCCUPIED by current agent 
    occ_lattice[selected_neigh_3d_id] = a_id
def dynamic_noise_lattice(agn_locs, avail_lattice):

    # define the noise range
    noise_range = [10.0, 60.0]

    # initialize noise sources
    noise_src_points = []
    noise_src_levels = []

    # iterate over agents
    for a_id in range(len(agn_locs)):
        # extract agent locations
        a_locs = agn_locs[a_id]
        # retrieve the silent level of the agent
        a_noise_level_mapped = 1 - agn_data.loc[a_id]["silent_level"]
        # mapping the [0,1] values to noise level (db)
        a_noise_level = a_noise_level_mapped * (noise_range[1] - noise_range[0]) + noise_range[0]

        # for each agent location
        for a_loc in a_locs:
            # append the noise source information
            noise_src_points.append(a_loc)
            noise_src_levels.append(a_noise_level)

    # convert to numpy array
    noise_src_points = np.array(noise_src_points)

    # create full lattice
    full_lattice = avail_lattice * 0 + 1

    # extract the coordiantes of the centroid of all voxel
    vox_centroids = full_lattice.centroids

    # extract voxel indices of all voxels
    vox_indices = np.array(np.where(full_lattice==1)).T

    # initializing the sum lattice of noise
    sum_noise_lats = avail_lattice * 0.0

    # for each source of noise
    for src_point, src_level in zip(noise_src_points,noise_src_levels):
        # initialize the occupation lattice
        dist_latice = avail_lattice * 0.0

        for cen, ind in zip(vox_centroids, vox_indices):
            # compute the euclidian distance
            dist_latice[tuple(ind)] = sp.spatial.distance.euclidean(cen, src_point)

        # computing the noise lattice from dist lattice
        noise_latice = src_level - 20 * np.log10(dist_latice) - 8

        # summing
        sum_noise_lats += np.power(10, noise_latice / 10.0)

    # computing the final aggregation
    agg_noise_lats = 10 * np.log10(sum_noise_lats)

    # normalizing the noise values
    normalized_silence_lattice = 1 - (agg_noise_lats - np.min(agg_noise_lats)) / (np.max(agg_noise_lats) - np.min(agg_noise_lats))

    return normalized_silence_lattice

1.2. Visualizing the agents seeds

p = pv.Plotter(notebook=True)

base_lattice = occ_lattice

# Set the grid dimensions: shape + 1 because we want to inject our values on the CELL data
grid = pv.UniformGrid()
grid.dimensions = np.array(base_lattice.shape) + 1
# The bottom left corner of the data set
grid.origin = base_lattice.minbound - base_lattice.unit * 0.5
# These are the cell sizes along each axis
grid.spacing = base_lattice.unit 

# adding the avilability lattice
init_avail_lattice.fast_vis(p)


# convert mesh to pv_mesh
def tri_to_pv(tri_mesh):
    faces = np.pad(tri_mesh.faces, ((0, 0),(1,0)), 'constant', constant_values=3)
    pv_mesh = pv.PolyData(tri_mesh.vertices, faces)
    return pv_mesh


# adding the meshes
p.add_mesh(tri_to_pv(immediate_context_mesh), color='#aaaaaa')

# Add the data values to the cell data
grid.cell_arrays["Agents"] = base_lattice.flatten(order="F").astype(int)  # Flatten the array!
# filtering the voxels
threshed = grid.threshold([-0.1, agn_num - 0.9])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False)


# p.add_slider_widget(create_mesh, [0, n_frames], title='Time', value=0, event_type="always", style="classic", pointa=(0.1, 0.1), pointb=(0.9, 0.1))
p.show(use_ipyvtk=True)

2. Creation of vertical shaft

2.1. Manually set cluster center

# manually set cluster centers
cluster_center_manual = { 
    0: [[10,12,1]],    
    1: [[23,18,1]],    
    2: [[35,10,1]],    
    3: [[13,6,1]],
    4: [[21,6,1]], 
    5: [[19,12,1]],
    6: [[35,4,1]],
    7: [[35,17,1]],

}

cluster_center_0 = cluster_center_manual[0]
cluster_center_1 = cluster_center_manual[1]
cluster_center_2 = cluster_center_manual[2]
cluster_center_3 = cluster_center_manual[3]
cluster_center_4 = cluster_center_manual[4]
cluster_center_5 = cluster_center_manual[5]
cluster_center_6 = cluster_center_manual[6]
cluster_center_7 = cluster_center_manual[7]

cluster_center = cluster_center_0 + cluster_center_1 + cluster_center_2 + cluster_center_3 + cluster_center_4  + cluster_center_5 + cluster_center_6 + cluster_center_7 #+ cluster_center_8
# let the cluster center go in the z direction
shft_lattice = occ_lattice * 0
for cl_cen in cluster_center:
    shft_lattice[cl_cen[0],cl_cen[1],:] = 1
shft_lattice *= init_avail_lattice

2.2. Visualize Vertical Shafts

base_lattice = shft_lattice

# Set the grid dimensions: shape + 1 because we want to inject our values on the CELL data
grid = pv.UniformGrid()
grid.dimensions = np.array(base_lattice.shape) + 1
# The bottom left corner of the data set
grid.origin = base_lattice.minbound - base_lattice.unit * 0.5
# These are the cell sizes along each axis
grid.spacing = base_lattice.unit 

# adding the avilability lattice
init_avail_lattice.fast_vis(p)

# convert mesh to pv_mesh
def tri_to_pv(tri_mesh):
    faces = np.pad(tri_mesh.faces, ((0, 0),(1,0)), 'constant', constant_values=3)
    pv_mesh = pv.PolyData(tri_mesh.vertices, faces)
    return pv_mesh


# adding the meshes
p.add_mesh(tri_to_pv(immediate_context_mesh), style = 'surface')


# Add the data values to the cell data
grid.cell_arrays["Agents"] = base_lattice.flatten(order="F").astype(int)  # Flatten the array!
# filtering the voxels
threshed = grid.threshold([0.9, 1.1])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False)


p.show(use_ipyvtk=True)

3. Creation of Horizontal Corridors

3.1. Extract the connectivity graph from the lattice based on the horizontal stencil

# Extract the connectivity graph of the envelope lattice
def find_neighbours_masked(lattice, stencil, loc):
    neigh_locs = np.argwhere(stencil) - stencil.origin + loc
    neigh_filter = np.all(neigh_locs > -1, axis=1) * np.all(neigh_locs < np.array(lattice.shape), axis=1)
    neigh_3d = neigh_locs[neigh_filter]
    neigh_1d = [np.ravel_multi_index(n_loc, avail_lattice.shape) for n_loc in neigh_3d]
    return(neigh_1d)
# find the number of all voxels
vox_count = avail_lattice.size 

# initialize the adjacency matrix
adj_mtrx = np.zeros((vox_count,vox_count))

# Finding the index of the available voxels in avail_lattice
avail_index = np.array(np.where(avail_lattice == 1)).T

# fill the adjacency matrix using the list of all neighbours
for vox_loc in avail_index:
    # find the 1D id
    vox_id = np.ravel_multi_index(vox_loc, avail_lattice.shape)
    # retrieve the list of neighbours of the voxel based on the stencil
    vox_neighs = find_neighbours_masked(init_avail_lattice, stencil, loc = vox_loc)
    # iterating over the neighbours
    for neigh in vox_neighs:
        # setting the entry to one
        adj_mtrx[vox_id, neigh] = 1.0

# construct the graph 
g = nx.from_numpy_array(adj_mtrx)
occ_ind = np.array(np.where(occ_lattice > -1)).T

3.2. Find the shortest path between an agent and a shaft and construct the corridor

# initialize corridor lattice
cor_lattice = shft_lattice * 0
cor_flat = cor_lattice.flatten()
# for each voxel that needs to have access to shafts
for a_vox in occ_ind:

    # slice the corridor lattice horizontally
    # add the previously created corridors as competing destination to shafts 
    cor_floor = shft_lattice[:,:,a_vox[2]] + cor_lattice[:,:,a_vox[2]]
    # find the vertical shaft voxel indices
    shaft_vox_inds = np.array(np.where(cor_floor > 0)).T
    paths = []
    path_lens = []
    for shft_ind in shaft_vox_inds:
        # construct the destination address
        dst_vox = np.array([shft_ind[0],shft_ind[1],a_vox[2]])
        # construct 1-dimensional indices
        src_ind = np.ravel_multi_index(a_vox, shft_lattice.shape)
        dst_ind = np.ravel_multi_index(dst_vox, shft_lattice.shape)



        # find the shortest path
        try:
            path = nx.algorithms.shortest_paths.astar.astar_path(g, src_ind, dst_ind)
            paths.append(path)
            path_lens.append(len(path))
        except:
            print("disconnected:", a_vox, init_avail_lattice[tuple(a_vox)], dst_vox, init_avail_lattice[tuple(dst_vox)])


    path_order = np.array(path_lens).argsort()
    if len(paths) > 0:
        shortest_path = paths[path_order[0]]
        # set the shortest path occupied in the 
        cor_flat[shortest_path] = 1

    # reshape the flat lattice
    cor_lattice = cor_flat.reshape(cor_lattice.shape)

3.3. Find the shortest paths between the shafts on the ground floor

# find the shortest path between them 
cor_lattice2 = shft_lattice * 0

# initialize corridor lattice
cor_flat = cor_lattice2.flatten()

# slice the corridorlattice horizontally (same z value)
cor_floor = shft_lattice[:,:,1] + cor_lattice[:,:,a_vox[2]]
# find the vertical shaft voxel indices
shaft_vox_inds = np.array(np.where(cor_floor > 0)).T

# for each voxel that needs to have access to shafts
for src_shft_ind in shaft_vox_inds:

    #find the distance and the path to every other shaft 
    paths = []
    path_lens = []

    for dst_shft_ind in shaft_vox_inds:
        # construct the destination address
        src_vox = np.array([src_shft_ind[0],src_shft_ind[1],0])
        dst_vox = np.array([dst_shft_ind[0],dst_shft_ind[1],0])
        # construct 1-dimensional indices
        src_ind = np.ravel_multi_index(src_vox, shft_lattice.shape)
        dst_ind = np.ravel_multi_index(dst_vox, shft_lattice.shape)

        try:
            path = nx.algorithms.shortest_paths.astar.astar_path(g, src_ind, dst_ind)
            #regel hieronder 
            if len(path) > 1:
                paths.append(path)
                path_lens.append(len(path))
        except:
            pass

    path_order = np.array(path_lens).argsort()
    if len(paths) > 0:
        shortest_path = paths[path_order[0]]
        cor_flat[shortest_path] = 1

    if len(paths) > 1:
        snd_shortest_path = paths[path_order[1]]
        cor_flat[snd_shortest_path] = 1            

    cor_lattice2 = cor_flat.reshape(cor_lattice2.shape)    

3.4. Find the shortest paths between the shafts on the third floor

3.4.1. Extract the connectivity graph

# Extract the connectivity graph of the full envelope
def find_neighbours_masked(lattice, stencil, loc):
    neigh_locs = np.argwhere(stencil) - stencil.origin + loc
    neigh_filter = np.all(neigh_locs > -1, axis=1) * np.all(neigh_locs < np.array(lattice.shape), axis=1)
    neigh_3d = neigh_locs[neigh_filter]
    neigh_1d = [np.ravel_multi_index(n_loc, full_lattice.shape) for n_loc in neigh_3d]
    return(neigh_1d)
# find the number of all voxels
vox_count = full_lattice.size 

# initialize the adjacency matrix
adj_mtrx = np.zeros((vox_count,vox_count))

# Finding the index of the available voxels in avail_lattice
full_index = np.array(np.where(full_lattice == 1)).T

# fill the adjacency matrix using the list of all neighbours
for vox_loc in full_index:
    # find the 1D id
    vox_id = np.ravel_multi_index(vox_loc, full_lattice.shape)
    # retrieve the list of neighbours of the voxel based on the stencil
    vox_neighs = find_neighbours_masked(init_full_lattice, stencil, loc = vox_loc)
    # iterating over the neighbours
    for neigh in vox_neighs:
        # setting the entry to one
        adj_mtrx[vox_id, neigh] = 1.0

# construct the graph 
g = nx.from_numpy_array(adj_mtrx)
occ_ind = np.array(np.where(occ_lattice > -1)).T

3.4.2 Find the shortest path and create the corridors

# find the shortest path between them 
cor_lattice3 = shft_lattice * 0

# initialize corridor lattice
cor_flat = cor_lattice3.flatten()

# slice the corridorlattice horizontally (same z value)
cor_floor = shft_lattice[:,:,1] + cor_lattice3[:,:,a_vox[2]]
# find the vertical shaft voxel indices
shaft_vox_inds = np.array(np.where(cor_floor > 0)).T

# for each voxel that needs to have access to shafts
for src_shft_ind in shaft_vox_inds:

    #find the distance and the path to every other shaft 
    paths = []
    path_lens = []

    for dst_shft_ind in shaft_vox_inds:
        # construct the destination address
        src_vox = np.array([src_shft_ind[0],src_shft_ind[1],3])
        dst_vox = np.array([dst_shft_ind[0],dst_shft_ind[1],3])
        # construct 1-dimensional indices
        src_ind = np.ravel_multi_index(src_vox, shft_lattice.shape)
        dst_ind = np.ravel_multi_index(dst_vox, shft_lattice.shape)


        try:
            path = nx.algorithms.shortest_paths.astar.astar_path(g, src_ind, dst_ind)
            #regel hieronder 
            if len(path) > 1:
                paths.append(path)
                path_lens.append(len(path))
        except:
            pass
            #print("disconnected:", a_vox, init_avail_lattice[tuple(a_vox)], dst_vox, init_avail_lattice[tuple(dst_vox)])

    path_order = np.array(path_lens).argsort()
    if len(paths) > 0:
        shortest_path = paths[path_order[0]]
        # set the shortest path occupied in the 
        cor_flat[shortest_path] = 1
    if len(paths) > 1:
        snd_shortest_path = paths[path_order[1]]
        cor_flat[snd_shortest_path] = 1


    cor_lattice3 = cor_flat.reshape(cor_lattice3.shape)
final_lattice = shft_lattice + cor_lattice + cor_lattice2 + cor_lattice3 

3.5. Visualize the accessability lattice

p = pv.Plotter(notebook=True)

base_lattice = final_lattice

# Set the grid dimensions: shape + 1 because we want to inject our values on the CELL data
grid = pv.UniformGrid()
grid.dimensions = np.array(base_lattice.shape) + 1
# The bottom left corner of the data set
grid.origin = base_lattice.minbound - base_lattice.unit * 0.5
# These are the cell sizes along each axis
grid.spacing = base_lattice.unit 


# adding the avilability lattice
#avail_lattice.fast_vis(p)


# convert mesh to pv_mesh
def tri_to_pv(tri_mesh):
    faces = np.pad(tri_mesh.faces, ((0, 0),(1,0)), 'constant', constant_values=3)
    pv_mesh = pv.PolyData(tri_mesh.vertices, faces)
    return pv_mesh


# adding the meshes
p.add_mesh(tri_to_pv(immediate_context_mesh), style = 'surface')


# Add the data values to the cell data
grid.cell_arrays["Agents"] = base_lattice.flatten(order="F").astype(int)  # Flatten the array!

# filtering the voxels
threshed = grid.threshold([0.9, 2.1])

# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False, cmap="tab20")

p.show(use_ipyvtk=True)

3.6. Save as csv

# save the corridor lattice as a csv
csv_path = os.path.relpath('../data/corridors_bridges_new.csv')
final_lattice.to_csv(csv_path)

Credits

__author__ = "Shervin Azadi and Pirouz Nourian"
__changes_made_by__ = "Lotte Zwolsman"
__license__ = "MIT"
__version__ = "1.0"
__url__ = "https://github.com/frankvahstal/spatial_computing_workshops"
__summary__ = "Spatial Computing Design Studio Workshop on Path Finding and Corridorfor Generative Spatial Relations"