# Building Metrics with gloBFPr

# Analyze Building Footprints

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)

## Example data

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
    )

## 1 Morphological Metrics

`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
    )

## 2 Neighbor Metrics

`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)

## 3 Distance to Nearest Greenspace

`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
    )

### Measuring Along Real Streets

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.

## 4 Building Green View Index

`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()

## 5 A Complete Metric Pipeline

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)