///|
fn adjacency_add_space(
map : Map[Int, Array[Int]],
element_id : Int,
space_id : Int,
) -> Unit {
map.update_or_default(element_id, [space_id], values => {
if !values.contains(space_id) {
values.push(space_id)
}
values
})
}
///|
fn adjacency_boundary_map(index : ModelIndex) -> Map[Int, Array[Int]] {
let result : Map[Int, Array[Int]] = Map([])
for entity in index.all() {
if entity.name.to_upper() == "IFCRELSPACEBOUNDARY" {
match
(
optional_reference_argument(entity, 4),
optional_reference_argument(entity, 5),
) {
(Some(element), Some(space)) =>
adjacency_add_space(result, element, space)
_ => ()
}
}
}
result
}
///|
fn adjacency_edge_key(left : Int, right : Int) -> String {
if left < right {
left.to_string() + ":" + right.to_string()
} else {
right.to_string() + ":" + left.to_string()
}
}
///|
fn adjacency_add_neighbor(
map : Map[Int, Array[Int]],
left : Int,
right : Int,
) -> Unit {
map.update_or_default(left, [right], values => {
if !values.contains(right) {
values.push(right)
}
values
})
}
///|
///|
/// Build a room adjacency graph from IfcRelSpaceBoundary records.
pub fn SpaceAdjacencyGraph::new(
model : Model,
) -> SpaceAdjacencyGraph raise ParseError {
let index = model.index()
let spatial = SpatialIndex::new(model)
let boundaries = adjacency_boundary_map(index)
let spaces = index.ids_named("IFCSPACE")
let edges : Array[SpaceAdjacency] = []
let neighbors : Map[Int, Array[Int]] = Map([])
let seen : Map[String, Bool] = Map([])
for _, space_ids in boundaries {
for i in 0.. box_distance(a.bounds, b.bounds)
_ => 0.0
}
edges.push({
left_space: if left < right {
left
} else {
right
},
right_space: if left < right {
right
} else {
left
},
shared_elements: shared,
boundary_count: shared.length(),
distance,
})
adjacency_add_neighbor(neighbors, left, right)
adjacency_add_neighbor(neighbors, right, left)
}
}
}
{ spaces, edges, neighbors }
}
///|
pub fn space_adjacency(model : Model) -> SpaceAdjacencyGraph raise ParseError {
SpaceAdjacencyGraph::new(model)
}
///|
pub fn SpaceAdjacencyGraph::edge_count(self : SpaceAdjacencyGraph) -> Int {
self.edges.length()
}
///|
pub fn SpaceAdjacencyGraph::space_count(self : SpaceAdjacencyGraph) -> Int {
self.spaces.length()
}
///|
pub fn SpaceAdjacencyGraph::neighbors(
self : SpaceAdjacencyGraph,
id : Int,
) -> Array[Int] {
self.neighbors.get(id).unwrap_or([])
}
///|
pub fn SpaceAdjacencyGraph::degree(self : SpaceAdjacencyGraph, id : Int) -> Int {
self.neighbors(id).length()
}
///|
pub fn SpaceAdjacencyGraph::edge(
self : SpaceAdjacencyGraph,
left : Int,
right : Int,
) -> SpaceAdjacency? {
for edge in self.edges {
if (edge.left_space == left && edge.right_space == right) ||
(edge.left_space == right && edge.right_space == left) {
return Some(edge)
}
}
None
}
///|
pub fn SpaceAdjacencyGraph::isolated_spaces(
self : SpaceAdjacencyGraph,
) -> Array[Int] {
self.spaces.filter(id => self.degree(id) == 0)
}
///|
pub fn SpaceAdjacencyGraph::reachable(
self : SpaceAdjacencyGraph,
start : Int,
) -> Array[Int] {
let result : Array[Int] = []
let queue : Array[Int] = [start]
while queue.length() > 0 {
let current = queue.remove(0)
if result.contains(current) {
continue
}
result.push(current)
for next in self.neighbors(current) {
if !result.contains(next) {
queue.push(next)
}
}
}
result
}
///|
pub fn SpaceAdjacencyGraph::shortest_path(
self : SpaceAdjacencyGraph,
start : Int,
goal : Int,
) -> Array[Int]? {
if start == goal {
return Some([start])
}
let queue : Array[Int] = [start]
let visited : Map[Int, Bool] = Map([])
let parents : Map[Int, Int] = Map([])
visited[start] = true
while queue.length() > 0 {
let current = queue.remove(0)
for next in self.neighbors(current) {
if visited.contains(next) {
continue
}
visited[next] = true
parents[next] = current
if next == goal {
queue.clear()
break
}
queue.push(next)
}
}
if !visited.contains(goal) {
return None
}
let reversed : Array[Int] = [goal]
let mut cursor = goal
while cursor != start {
match parents.get(cursor) {
Some(parent) => {
cursor = parent
reversed.push(cursor)
}
None => break
}
}
let result : Array[Int] = []
for index in 0.. String {
"#" +
self.left_space.to_string() +
" <-> #" +
self.right_space.to_string() +
" shared=" +
self.shared_elements.map(id => "#" + id.to_string()).join(",") +
" distance=" +
self.distance.to_string()
}
///|
pub fn SpaceAdjacencyGraph::headline(self : SpaceAdjacencyGraph) -> String {
"spaces=" +
self.spaces.length().to_string() +
" edges=" +
self.edges.length().to_string() +
" isolated=" +
self.isolated_spaces().length().to_string()
}
///|
pub fn SpaceAdjacencyGraph::to_json(self : SpaceAdjacencyGraph) -> String {
let edges = self.edges
.map(edge => {
"{\"left\":" +
edge.left_space.to_string() +
",\"right\":" +
edge.right_space.to_string() +
",\"shared_elements\":[" +
edge.shared_elements.map(id => id.to_string()).join(",") +
"],\"distance\":" +
edge.distance.to_string() +
"}"
})
.join(",")
"{\"space_count\":" +
self.spaces.length().to_string() +
",\"edge_count\":" +
self.edges.length().to_string() +
",\"isolated\":" +
self.isolated_spaces().length().to_string() +
",\"edges\":[" +
edges +
"]}"
}
///|
pub fn SpaceAdjacencyGraph::to_markdown(self : SpaceAdjacencyGraph) -> String {
let builder = StringBuilder()
builder <+ "# Space adjacency\n\n"
builder <+ "- Spaces: \{self.spaces.length()}\n"
builder <+ "- Edges: \{self.edges.length()}\n"
builder <+ "- Isolated: \{self.isolated_spaces().length()}\n\n"
for edge in self.edges {
builder <+ "- \{edge.headline()}\n"
}
builder.to_string()
}
///|
pub fn space_neighbors(
model : Model,
space_id : Int,
) -> Array[Int] raise ParseError {
space_adjacency(model).neighbors(space_id)
}
///|
pub fn space_path(
model : Model,
start : Int,
goal : Int,
) -> Array[Int]? raise ParseError {
space_adjacency(model).shortest_path(start, goal)
}