This vignette shows how to compute building-level metrics from a
building footprint layer with height attributes. Most functions return
the same sf object with additional metric columns, so the
usual workflow is to pipe or reassign the result step by step.
library(gloBFPr)
library(sf)
library(dplyr)
The package includes a small sf example dataset with
building footprints, unique IDs, and building heights.
data(globfp_example)
buildings <- globfp_example
buildings <- buildings[seq_len(min(10, nrow(buildings))), ]
names(buildings)
## [1] "Height" "geometry" "id"
For your own area of interest, first retrieve building footprints
with search_3dglobdf(), then pass the returned polygon
layer to the metric functions.
buildings <- search_3dglobdf(
bbox = c(-83.065644, 42.333792, -83.045217, 42.346988),
out_type = "poly",
quiet = TRUE
)
get_morphology() computes geometric and shape metrics
such as footprint area, perimeter, volume, compactness, convexity,
elongation, and pairwise distance.
morphology <- get_morphology(buildings, quiet = TRUE)
morphology |>
st_drop_geometry() |>
select(id, Height, g_area, pmeter, vol, rec, cnv, elo_z) |>
head()
You can also request only a subset of metrics when you do not need the full set.
basic_shape <- get_morphology(
buildings,
metrics = c("g_area", "pmeter", "vol", "rec"),
quiet = TRUE
)
get_neighbors() estimates the number of neighboring
buildings and centroid distance summaries using a fixed search radius
and Voronoi adjacency.
neighbors <- get_neighbors(
morphology,
radius = 500,
quiet = TRUE
)
neighbors |>
st_drop_geometry() |>
select(id, n_count, m_ndist, min_ndist, max_ndist, sd_ndist) |>
head()
Use a smaller radius for dense local context or a larger radius for broader neighborhood context.
neighbors_100m <- get_neighbors(buildings, radius = 100, quiet = TRUE)
get_dng() computes the distance from each building
centroid to the nearest qualifying green patch within a buffer. The
source can be metachm, esri, or
sentinel2.
By default the distance is measured in a straight line. Every result
also carries a dng_method column recording how the value
was obtained, which matters once network routing is switched on
below.
dng <- get_dng(
neighbors,
datasource = "metachm",
radius = 800,
min_tree_height = 2,
min_area = 500,
unit = "m2",
quiet = TRUE
)
dng |>
st_drop_geometry() |>
select(id, dng, dng_method) |>
head()
For a 2D greenery mask source, use esri or
sentinel2.
dng_esri <- get_dng(
buildings,
datasource = "esri",
zoom = 17,
radius = 800,
min_area = 500,
unit = "m2",
quiet = TRUE
)
Straight-line distance ignores rivers, rail corridors, walls, and the
simple fact that people walk on streets. Set
network = "osm" to measure along real road and path centre
lines instead. get_dng() downloads a walkable OpenStreetMap
network for the study extent (motorways, trunk roads, and ways tagged
foot=no are excluded), builds a routing graph, and
reports
centroid to network + shortest path along network + network to green pixel
dng_net <- get_dng(
neighbors,
datasource = "metachm",
radius = 800,
min_area = 500,
unit = "m2",
network = "osm",
quiet = TRUE
)
dng_net |>
st_drop_geometry() |>
select(id, dng, dng_method) |>
head()
The network is downloaded once for the whole study area rather than
once per building, so routing adds a single Overpass request regardless
of how many buildings you pass in. If Overpass is unreachable or returns
nothing, get_dng() warns and falls back to straight-line
distance rather than failing.
Supply your own lines when you need a specific network — a cleaned
municipal sidewalk layer, a snapshot pinned to a particular date, or the
same network you passed to generate_block(). Any
sf line layer works, and it is reprojected for you.
# roads is an sf LINESTRING layer you already have
dng_custom <- get_dng(
neighbors,
datasource = "metachm",
radius = 800,
min_area = 500,
unit = "m2",
network = roads,
quiet = TRUE
)
dng_method tells you which measure each row actually
used. A building falls back to "euclidean" when no path
connects it to any green patch — typically an isolated network fragment,
or a building whose buffer extends past the edge of the downloaded
network. Check it before interpreting results, and compare the two
measures to see where the street layout imposes a real detour:
table(dng_net$dng_method)
comparison <- data.frame(
id = dng$id,
euclidean = dng$dng,
network = dng_net$dng
)
comparison$detour_ratio <- comparison$network / comparison$euclidean
# Ratios near 1 mean the green space is straight ahead; large ratios flag
# buildings cut off by barriers, dead ends, or missing crossings.
head(comparison[order(-comparison$detour_ratio), ])
A few things to keep in mind. Distances are computed in the projected
UTM CRS of your study area, so they are in metres. Green space is
compared pixel by pixel, so a higher zoom gives a finer
target set at the cost of runtime. And buildings connect to the graph at
its vertices, so the network is densified to about 20 m spacing before
routing — accurate enough for walking distances without inflating the
graph.
get_bgvi() estimates Building Green View Index from
building viewpoints. The DSM can include metaCHM canopy height, while
the visible-green feature layer can come from height-filtered canopy, 2D
map-tile greenspace, or the union of both. An OpenTopography API key is
required for the DEM. This can be computationally heavy; start with a
small subset of buildings.
bgvi <- get_bgvi(
buildings[1:10, ],
datasource_canopy_height = "metachm",
datasource_greenspace = "esri",
min_tree_height = 2,
radius = 800,
floor = FALSE,
workers = 1,
key = Sys.getenv('OPENTOPO_API'),
quiet = TRUE
)
bgvi |>
st_drop_geometry() |>
select(id, mean_gvi, bottom_gvi, top_gvi) |>
head()
To compute BGVI with a DSM built only from buildings and DEM, set
datasource_canopy_height = NULL and provide a 2D greenspace
source.
bgvi_2d_green <- get_bgvi(
buildings[1:3, ],
datasource_canopy_height = NULL,
datasource_greenspace = "esri",
radius = 800,
short_building_threshold = 6,
workers = 1,
key = Sys.getenv('OPENTOPO_API'),
quiet = TRUE
)
To compute directional BGVI, provide one or more directions and a field of view in degrees. The function reuses each computed viewshed and filters visible greenery by direction.
bgvi_directional <- get_bgvi(
buildings[1:3, ],
datasource_canopy_height = "metachm",
datasource_greenspace = "esri",
radius = 800,
directions = c("southwest", "south", "southeast"),
field_of_view = 45,
workers = 1,
key = Sys.getenv('OPENTOPO_API'),
quiet = TRUE
)
bgvi_directional |>
st_drop_geometry() |>
select(id, mean_gvi_south, gvi_bottom_south, gvi_top_south) |>
head()
Use plot_bgvi_viewshed() when you want to inspect the
viewshed behind one building’s BGVI value. It uses the same DSM and
green-feature preparation as get_bgvi(), but computes a
single viewpoint and returns the diagnostic rasters invisibly when
plot = TRUE.
top_direction_view <- plot_bgvi_viewshed(
buildings,
building = 195,
level = "top",
orientation = "north",
field_of_view = 90,
datasource_canopy_height = "metachm",
datasource_greenspace = "esri",
radius = 500,
key = Sys.getenv('OPENTOPO_API'),
quiet = TRUE
)
You can also inspect a specific floor or supply an observer height directly.
fifth_floor_view <- plot_bgvi_viewshed(
buildings,
building = 1,
floor = 5,
orientation = 135,
field_of_view = 90,
datasource_canopy_height = "metachm",
datasource_greenspace = "esri",
radius = 500,
key = Sys.getenv('OPENTOPO_API'),
quiet = TRUE
)
fifth_floor_view$gvi
fifth_floor_view$visible_green
To compute GVI by floor, set floor = TRUE. Use
floor_step to sample every n floors and reduce
runtime. The top estimated floor is always included.
bgvi_by_floor <- get_bgvi(
buildings[1:3, ],
datasource_canopy_height = "metachm",
radius = 800,
floor = TRUE,
floor_step = 3,
workers = 2,
key = Sys.getenv('OPENTOPO_API'),
quiet = TRUE
)
bgvi_by_floor |>
st_drop_geometry() |>
select(id, estimated_floors, mean_gvi, bottom_gvi, top_gvi, min_gvi, max_gvi, sd_gvi) |>
head()
For a typical analysis, build the result incrementally. Keep
quiet = TRUE for batch workflows, or set it to
FALSE when you want messages and progress bars.
result <- buildings |>
get_morphology(quiet = TRUE) |>
get_neighbors(radius = 500, quiet = TRUE) |>
get_dng(
datasource = "metachm",
radius = 800,
min_area = 500,
unit = "m2",
network = "osm",
quiet = TRUE
)
metric_table <- result |>
st_drop_geometry() |>
select(
id, Height, g_area, vol, rec,
n_count, m_ndist, min_ndist, max_ndist, sd_ndist,
dng, dng_method
)
head(metric_table)