def distance_field(occ_lattice, env_lattice, a_id):
# creating neighborhood definition 1
stencil = tg.create_stencil("von_neumann", 1, 1)
# setting the indices
stencil.set_index([0,0,0], 0)
occ_array_padded = np.pad(occ_lattice, 1, mode="constant", constant_values=-1)
env_array_padded = np.pad(env_lattice, 1, mode="constant", constant_values=False)
padded_minbound = env_lattice.minbound - env_lattice.unit
env_lattice_padded = tg.to_lattice(env_array_padded, minbound=padded_minbound, unit=env_lattice.unit)
occ_lattice_padded = tg.to_lattice(occ_array_padded, minbound=padded_minbound, unit=env_lattice.unit)
distance_lattice = env_lattice_padded * False
a_voexls_3d_ind_padded = tuple(np.argwhere(occ_lattice_padded == a_id).T + 1)
# print(a_voexls_3d_ind_padded)
distance_lattice[a_voexls_3d_ind_padded] = True
# retrieve the neighbour list of each cell
neighs = distance_lattice.find_neighbours(stencil)
# set the maximum distance to sum of the size of the lattice in all dimensions.
max_dist = np.sum(distance_lattice.shape)
# initialize the street network distance lattice with all the street cells as 0, and all other cells as maximum distance possible
mn_dist_lattice = 1 - distance_lattice
mn_dist_lattice[mn_dist_lattice==1] = max_dist
# flatten the distance lattice for easy access
mn_dist_lattice_flat = mn_dist_lattice.flatten()
# flatten the envelope lattice
env_pad_lat_flat = env_lattice_padded.flatten()
# main loop for breath-first traversal
for i in range(1, max_dist):
# find the neighbours of the previous step
next_step = neighs[mn_dist_lattice_flat == i - 1]
# find the unique neighbours
next_unq_step = np.unique(next_step.flatten())
# check if the neighbours of the next step are inside the envelope
validity_condition = env_pad_lat_flat[next_unq_step]
# select the valid neighbours
next_valid_step = next_unq_step[validity_condition]
# make a copy of the lattice to prevent overwriting in the memory
mn_nex_dist_lattice_flat = np.copy(mn_dist_lattice_flat)
# set the next step cells to the current distance
mn_nex_dist_lattice_flat[next_valid_step] = i
# find the minimum of the current distance and previous distances to avoid overwriting previous steps
mn_dist_lattice_flat = np.minimum(mn_dist_lattice_flat, mn_nex_dist_lattice_flat)
# check how many of the cells have not been traversed yet
filled_check = mn_dist_lattice_flat * env_pad_lat_flat == max_dist
# if all the cells have been traversed, break the loop
if filled_check.sum() == 0:
# print(i)
break
# reshape and construct a lattice from the street network distance list
mn_dist_lattice = mn_dist_lattice_flat.reshape(mn_dist_lattice.shape)
mn_dist_lattice = mn_dist_lattice.astype(float)
mn_dist_lattice *= env_lattice_padded
mn_dist_lattice = env_lattice_padded - (mn_dist_lattice - mn_dist_lattice.min()) / mn_dist_lattice.max()
return mn_dist_lattice