Corridor generation
In this notebook, the location of the corridors is being generated based on the voxel seeds. This for the corridors on the groundfloor, that connect al communal functions and all housing entrances. The public functions have their own entrance on street level. For each function seed and the entrance location a shaft location is generated, and a connection is found to two of their closest shafts to generate a corridornetwork for this floor.
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
import pandas as pd
import scipy as sp
import pickle
from sklearn.cluster import KMeans
np.random.seed(0)
import copy
# 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
s_1 = tg.create_stencil("von_neumann", 1, 1)
# setting the center to zero
s_1.set_index([0,0,0], 0)
# creating neighborhood definition
s_2 = tg.create_stencil("von_neumann", 1, 2)
# setting the center to zero
s_2.set_index([0, 0, 0], 0)
s_2.set_index([0, 0, 1], 0)
s_2.set_index([0, 0, 2], 1)
s_2.set_index([0, 0,-1], 0)
s_2.set_index([0, 0,-2], 1)
# setting the center to zero
s_2.set_index([0,0,0], 0)
# creating neighborhood definition
s_3 = tg.create_stencil("von_neumann", 1, 3)
# setting the center to zero
s_3.set_index([0, 0, 0], 0)
s_3.set_index([0, 0, 1], 0)
s_3.set_index([0, 0, 3], 1)
s_3.set_index([0, 0,-1], 0)
s_3.set_index([0, 0,-3], 1)
# setting the center to zero
s_3.set_index([0,0,0], 0)
# creating neighborhood definition
s_groundfloor = tg.create_stencil("von_neumann", 1, 1)
# setting the center to zero
s_groundfloor.set_index([0, 0, 0], 0)
s_groundfloor.set_index([0, 0, 1], 0)
s_groundfloor.set_index([0, 0,-1], 0)
stencils = [s_1, s_2, s_3, s_groundfloor]
h_stencil = s_groundfloor
0.3. Load the envelope lattice as the avialibility lattice
# loading the lattice from csv
lattice_path = os.path.relpath('../data/highres_envelope.csv')
avail_lattice = lattice_from_csv(lattice_path)
# loading the lattice from csv
lattice_path = os.path.relpath('../data/avail_lattice_good_voxels.csv')
avail_lattice_good_voxels = lattice_from_csv(lattice_path)
init_avail_lattice = tg.to_lattice(np.copy(avail_lattice_good_voxels), avail_lattice_good_voxels)
avail_lattice*= avail_lattice_good_voxels
0.4. Load Agents Information
# loading program (agents information) from CSV
prgm_path = os.path.relpath('../data/program_exported.csv')
agn_info = pd.read_csv('../data/program_exported.csv',delimiter=";")
agn_ids = agn_info["space_id"].values
agn_prefs = agn_info
a_pref = agn_prefs.loc[0]
# Loading program information between agents from CSV
matrix_path = os.path.relpath('../data/program_exported_matrix.csv')
matrix_info = pd.read_csv('../data/program_exported_matrix.csv',delimiter=";")
matrix_ids = matrix_info["space_id"].values
matrix_prefs = matrix_info
0.5. Initialize environment information layers from Sun Access Lattice and Entrance Access Lattice
# 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)
ent_acc_highres_public_path = os.path.relpath('../data/ent_access_highres_public.csv')
ent_acc_public_lattice = lattice_from_csv(ent_acc_highres_public_path)
ent_acc_highres_housing_path = os.path.relpath('../data/ent_access_highres_housing.csv')
ent_acc_housing_lattice = lattice_from_csv(ent_acc_highres_housing_path)
ent_acc_highres_gym_path = os.path.relpath('../data/ent_access_highres_gym.csv')
ent_acc_gym_lattice = lattice_from_csv(ent_acc_highres_gym_path)
ent_acc_highres_parking_path = os.path.relpath('../data/ent_access_highres_parking.csv')
ent_acc_parking_lattice = lattice_from_csv(ent_acc_highres_parking_path)
ent_acc_highres_comcen_path = os.path.relpath('../data/ent_access_highres_comcen.csv')
ent_acc_comcen_lattice = lattice_from_csv(ent_acc_highres_comcen_path)
highres_sky_acc_path = os.path.relpath('../data/sky_access_highres.csv')
sky_acc_lattice = lattice_from_csv(highres_sky_acc_path)
highres_quietness_acc_path = os.path.relpath('../data/quietness_highres.csv')
quietness_acc_lattice = lattice_from_csv(highres_quietness_acc_path)
groundfloor_acc_path = os.path.relpath('../data/ent_access_highres_groundfloor.csv')
groundfloor_acc_lattice = lattice_from_csv(groundfloor_acc_path)
# list the environment information layers (lattices)
env_info = {"sun_acc": sun_acc_lattice + 0.001,
"ent_acc_public": ent_acc_public_lattice + 0.001,
"ent_acc_housing": ent_acc_housing_lattice + 0.001,
"ent_acc_gym": ent_acc_gym_lattice + 0.001,
"ent_acc_parking": ent_acc_parking_lattice + 0.001,
"ent_acc_comcen": ent_acc_comcen_lattice + 0.001,
"sky_acc": sky_acc_lattice + 0.001,
"quietness_acc": quietness_acc_lattice + 0.001,
"ground_floor_acc": groundfloor_acc_lattice + 0.001}
# defining other factors in csv
# defining stencil id
stencil_id = stencils
# area to use in simulation
room_area = []
# generate a list of all communal functions
community = []
print(agn_info["ent_acc_comcen"].values)
for i in agn_ids:
if agn_info["ent_acc_comcen"][i] == 1:
community.append(agn_ids[i])
print(community)
1.1. initial agent localisation
def check_avail(avail_lattice, ind, a_stencil_id):
condition = 1
ind_array = np.array(ind)
for step in range(a_stencil_id + 1):
new_ind_array = ind_array + np.array([0,0,step])
condition *= avail_lattice[tuple(new_ind_array)]
return condition
def eval_voxel(vox, env_info, a_pref):
global_vox_value = 1.0
# for every lattice in the environment informations
for key, info_lattice in env_info.items():
# Here we utilise Fuzzy Logics to be able to compare different layers
# of environmental information and evaluate the voxel for the agent.
# This method is introduced, and generalised in Pirouz Nourian dissertation:
# section 5.7.3, pp. 201-208, eq. 57. You can refer to this section for
# comprehensive mathematical details.
vox_val = info_lattice[tuple(vox)]
agn_vox_val = np.power(vox_val, a_pref[key])
global_vox_value *= agn_vox_val
return global_vox_value
def mult_occupation(selected_vox_3d_address, a_id, a_height, agn_locs, agn_src_locs, occ_lattice, avail_lattice, departure=False):
for step in range(a_height):
new_address = selected_vox_3d_address + np.array([0,0,step])
if new_address[2] < occ_lattice.shape[2]:
# make tuple of the address
selected_vox_3d_id = tuple(new_address)
# find the location of the newly selected voxel
selected_vox_loc = np.array(selected_vox_3d_id).flatten()
if departure==False:
# add the newly selected voxel location to agent locations
agn_locs[a_id].append(selected_vox_loc)
if step ==0:
agn_src_locs[a_id].append(selected_vox_loc)
# set the newly selected voxel as UNavailable (0) in the availability lattice
avail_lattice[selected_vox_3d_id] = 0
# set the newly selected voxel as OCCUPIED by current agent
# (-1 means not-occupied so a_id)
occ_lattice[selected_vox_3d_id] = a_id
else:
# remove the newly selected voxel location to agent locations
a_locs_list = [list(loc) for loc in agn_locs[a_id]]
try:
ind = a_locs_list.index(list(selected_vox_loc))
agn_locs[a_id].pop(ind)
# set the newly selected voxel as UNavailable (0) in the availability lattice
avail_lattice[selected_vox_3d_id] = 1
# set the newly selected voxel as OCCUPIED by current agent
# (-1 means not-occupied so a_id)
occ_lattice[selected_vox_3d_id] = -1
except:
pass
return (agn_locs, agn_src_locs, occ_lattice, avail_lattice)
def find_voxels_masked(lattice, stencil, loc):
vox_locs = np.argwhere(stencil) - stencil.origin + loc
vox_filter = np.all(vox_locs > 0, axis=1) * np.all(vox_locs < np.array(lattice.shape), axis=1)
return(vox_locs[vox_filter])
# 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 and intiialize an agent for each space
agn_num = len(agn_info)
# adding the origins to the agents locations
agn_locs = [[] for a_id in agn_ids]
agn_src_locs = [[] for a_id in agn_ids]
agn_upper = []
# retrieving the initial location of each agent
for a_id in community:
voxel_vals = []
pot_voxels = []
# retrieve agent preferences
a_pref = agn_prefs.loc[a_id]
a_stencil_id = 0
stencil = stencils[a_stencil_id]
# use avail_index voxel Evaluation Loop
for pot_vox in avail_index:
if check_avail(avail_lattice, tuple(pot_vox), a_stencil_id):
# evaluate each voxel
vox_value = eval_voxel(pot_vox, env_info, a_pref)
# add the voxel value to the list of values
voxel_vals.append(vox_value)
pot_voxels.append(pot_vox)
# convert voxel values to numpy array
voxel_vals = np.array(voxel_vals)
# convert potential voxels to numpy array
pot_voxels = np.array(pot_voxels)
# select the voxel with highest value
selected_int = np.argmax(voxel_vals)
# find 3D intiger index of selected voxel
selected_vox_3d_address = tuple(pot_voxels[selected_int].T)
# find the location of the newly selected voxel
agn_origins = np.array(selected_vox_3d_address).flatten()
# Occupy the newly selected voxel in the occupation lattice
occ_lattice[selected_vox_3d_address] = 5
agn_locs, agn_src_locs, occ_lattice, avail_lattice = mult_occupation(selected_vox_3d_address,
a_id,
a_stencil_id + 1,
agn_locs,
agn_src_locs,
occ_lattice,
avail_lattice)
# add entrance voxel to the occ_lattice
occ_lattice[(83, 20, 1)] = 9
1.2. Visualizing the simulation
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 boundingbox wireframe
p.add_mesh(grid.outline(), color="grey", label="Domain")
# adding the avilability lattice
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
agn_num = len(agn_info)
def create_mesh(value):
f = int(value)
lattice = base_lattice
# Add the data values to the cell data
grid.cell_arrays["Agents"] = lattice.flatten(order="F").astype(int) # Flatten the array!
# filtering the voxels
threshed = grid.threshold([-0.1, 20])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False)
return
p.add_slider_widget(create_mesh, [0, 1], title='Time', value=0, event_type="always", style="classic", pointa=(0.1, 0.1), pointb=(0.9, 0.1))
p.show(use_ipyvtk=True)
#saving and plotting
png_path = os.path.relpath('../screenshots/5.1_voxel_seeds_groundfloor.png')
p.show(screenshot= png_path)
save the ground floor latice to csv csv_path = os.path.relpath('../data/voxel_seeds.csv') occ_lattice.to_csv(csv_path)
1.3. for each seed, generate a shaft location
occupation_01_lattice = (occ_lattice > -1).astype(int)
# compute the sum over the columns to add weight to locations that might have multiple seeds above eachother
column_sum = np.sum(occupation_01_lattice, axis = 2)
max_value_seeds = np.max(column_sum)
filled_col_list = []
for i in range(1, max_value_seeds + 1):
# find adresses of columns
column_adress = np.where(column_sum == i )
filled_collumns = np.array(column_adress).T
filled_col_list.append(filled_collumns)
fill_col_stacked = np.vstack(filled_col_list)
clusters = len(fill_col_stacked)
kmeans = KMeans(n_clusters=clusters, random_state=0).fit(fill_col_stacked)
col_labels = kmeans.labels_
cluster_centers = kmeans.cluster_centers_
# creating an empty occ lattice
cluster_lattice = occ_lattice * 0 - 1
# iterating over each filled column
for col, lab in zip(fill_col_stacked, col_labels):
# setting the base of the column into the label value
cluster_lattice[col[0], col[1], :] = lab
1.5. Compute a distance graph
rounded_cluster_centres = np.rint(cluster_centers).astype(int)
shaft_lattice = occ_lattice * 0
# iterating over each shaft
for i, cen in enumerate(rounded_cluster_centres):
shaft_lattice[cen[0], cen[1], :] = i + 1
trimmed_shft_latice = shaft_lattice * init_avail_lattice
# find the number of all voxels
vox_count = avail_lattice.size
# initialize the adjacency matrix
adj_list = []
# 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)
# check whether it is in a shaft or not
if shaft_lattice[tuple(vox_loc)] > 0:
# in case that specific voxel is in a shaft
vox_stencil = stencil
else:
# in case that the voxel is a normal voxel
vox_stencil = h_stencil
# retrieve the list of neighbours of the voxel based on the stencil
vox_neighs = avail_lattice.find_neighbours_masked(vox_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
adj_list.append([1.0, vox_id, neigh])
import scipy as sp
adj_array = np.array(adj_list)
adj_mtrx_sparse = sp.sparse.csr_matrix((adj_array[:,0],(adj_array[:,1],adj_array[:,2])), shape=(vox_count, vox_count))
g = nx.from_scipy_sparse_matrix(adj_mtrx_sparse)
1.6. construct the corridors based on the shortest paths
corridor_lattice = occ_lattice * 0 -1
corr_flat = corridor_lattice.flatten()
cor_groundfloor = shaft_lattice[:, :, 1]
shaft_vox_inds = np.array(np.where(cor_groundfloor >0)).T
corr_latt_shape = corridor_lattice.shape
all_shortest_paths = []
# find the shortest path between all locations to generate a corridor network
for start_shaft in shaft_vox_inds:
paths = []
path_lenghts = []
for dist_shaft_ind in shaft_vox_inds:
# construct the destination adress
src_vox = np.array([start_shaft[0], start_shaft[1], 2])
dst_vox = np.array([dist_shaft_ind[0], dist_shaft_ind[1], 2])
# construct 1-dimentional indices
src_ind = np.ravel_multi_index(src_vox, corr_latt_shape)
dst_ind = np.ravel_multi_index(dst_vox, corr_latt_shape)
# find the shortest path
try:
path = nx.algorithms.shortest_paths.astar.astar_path(g, src_ind, dst_ind)
if len(path) > 1:
paths.append(path)
path_lenghts.append(len(path))
except:
pass
path_order = np.argsort(np.array(path_lenghts))
shortest_path = paths[path_order[1]]
all_shortest_paths.append(paths)
corridor_lattice = corr_flat.reshape(corridor_lattice.shape)
# copy the corridors to all voxels that should make use of these corridors (3 voxels heigh for first level, two voxels heigh for second level)
for i, path in enumerate(all_shortest_paths):
for cen in path:
thrd_ind = np.unravel_index(cen, corridor_lattice.shape)
corridor_lattice[thrd_ind[0], thrd_ind[1], :6] = 1
corridor_lattice[thrd_ind[0], thrd_ind[1], 0] = -1
p = pv.Plotter(notebook=True)
base_lattice = corridor_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 boundingbox wireframe
p.add_mesh(grid.outline(), color="grey", label="Domain")
# adding the avilability lattice
# init_avail_lattice.fast_vis(p)
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", 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, 20])
# 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)
#saving and plotting
png_path = os.path.relpath('../screenshots/5.2_corridors_groundfloor.png')
p.show(screenshot= png_path)
2.3. Saving lattice frames in CSV
# save the ground floor latice to csv
csv_path = os.path.relpath('../data/corridors_groundfloor.csv')
corridor_lattice.to_csv(csv_path)
import pickle
lattice_pickle_path = os.path.relpath('../data/corridors_groundfloor.p')
pickle.dump(corridor_lattice, open(lattice_pickle_path,"wb"))
Credits
__author__ = "Shervin Azadi and Pirouz Nourian"
__editor__ = "Maartje Damen"
__license__ = "MIT"
__version__ = "1.0"
__url__ = "https://github.com/shervinazadi/spatial_computing_workshops"
__summary__ = "Spatial Computing Design Studio Workshop on MCDA and Path Finding for Generative Spatial Relations"