tests/testthat/test-isovist.R

# Create dummy occluders
occluders_geom <- sf::st_sfc(
  create_occluder(1, 1, 1, 0.5),
  create_occluder(4, 1, 1.5, 0.7),
  create_occluder(7, 1, 0.8, 0.8),
  create_occluder(2, 5, 2, 1),
  create_occluder(5, 5, 1, 1.5),
  create_occluder(1, 7, 1.2, 0.6),
  create_occluder(7, 7, 1.8, 0.9)
)
occluders <- sf::st_sf(id = 1:7, geometry = occluders_geom)

# Create dummy line
line_geom <- sf::st_sfc(
  sf::st_linestring(matrix(c(0, 3, 9, 3), ncol = 2, byrow = TRUE))
)
line <- sf::st_sf(id = 1, geometry = line_geom)

test_that("the correct number of viewpoints is created", {
  density <- 1
  vpoints <- get_viewpoints(sf::st_geometry(line), density = density)
  expect_equal(length(vpoints), ceiling(sf::st_length(line) * density))
})

test_that("density of viewpoints is a non-zero positive number", {
  expect_error(get_viewpoints(line, density = 0))
  expect_error(get_viewpoints(line, density = -1.3))
})

test_that("input cannot be a POINT or MULTIPOINT", {
  expect_error(get_viewpoints(sf::st_point(c(1, 1))))
  expect_error(get_viewpoints(
    sf::st_multipoint(matrix(c(1, 1, 2, 2), ncol = 2, byrow = TRUE))
  ))
})

test_that("POLYGON input is converted to LINESTRING", {
  density <- 1
  line <- sf::st_sf(geometry = sf::st_sfc(sf::st_polygon(
    list(rbind(c(0, 0), c(0, 1), c(1, 1), c(1, 0), c(0, 0)))
  )))
  # Get viewpoints with POLYGON as input
  vpoints <- get_viewpoints(line, density = density)
  # Cast to LINESTRING to allow for length calculation
  line <- sf::st_cast(line, "MULTILINESTRING") |>
    sf::st_cast("LINESTRING")
  expect_equal(length(vpoints), ceiling(sf::st_length(line) * density))
})

test_that("An isovist is properly constructed for a single viewpoint", {
  viewpoint <- sf::st_sfc(sf::st_point(c(6, 3)))
  ray_length <- 5
  isovist <- get_isovist(viewpoint, occluders, ray_length = ray_length)
  expect_true(inherits(isovist, "sfc"))
  expect_equal(length(isovist), 1)
  expect_true(sf::st_is(isovist, "POLYGON"))
  # isovist should be smaller than unoccluded buffer
  buffer <- sf::st_buffer(viewpoint, ray_length)
  expect_true(sf::st_contains(buffer, isovist, sparse = FALSE))
})

test_that("An isovist is properly constructed for multiple viewpoints", {
  viewpoints <- sf::st_cast(sf::st_sample(line_geom, 3), "POINT")
  ray_length <- 5
  isovist <- get_isovist(viewpoints, occluders, ray_length = ray_length)
  expect_true(inherits(isovist, "sfc"))
  expect_equal(length(isovist), 1)
  expect_true(sf::st_is(isovist, "POLYGON"))
  # isovist should be smaller than unoccluded buffer
  buffer <- sf::st_union(sf::st_buffer(viewpoints, ray_length))
  expect_true(sf::st_contains(buffer, isovist, sparse = FALSE))
})

test_that("Rays are properly constructed from a list of viewpoints", {
  ray_length <- 1
  ray_num <- 160
  viewpoints <- sf::st_sfc(
    sf::st_point(c(0, 1)), sf::st_point(c(1, 1)), sf::st_point(c(1, 0))
  )
  rays <- get_rays(viewpoints, ray_num = ray_num, ray_length = ray_length)
  expect_equal(nrow(rays), length(viewpoints) * ray_num)
  expect_true(all(sf::st_is(rays, "LINESTRING")))
  expect_contains(sf::st_length(rays), ray_length)
})

test_that("Rays construction fails for incorrect input arguments", {
  ray_length <- 1
  ray_num <- 160
  viewpoints <- sf::st_sfc(
    sf::st_point(c(0, 1)), sf::st_point(c(1, 1)), sf::st_point(c(1, 0))
  )
  expect_no_error(get_rays(viewpoints, ray_num = ray_num,
                           ray_length = ray_length))
  # ray_num must be a whole number
  expect_error(get_rays(viewpoints, ray_num = 0.1, ray_length = ray_length))
  # ray_length must be positive
  expect_error(get_rays(viewpoints, ray_num = ray_num, ray_length = 0))
  # viewpoints must consist of point geometries
  expect_error(get_rays(line, ray_num = ray_num, ray_length = 0))
})

test_that("The number of rays are rounded to the closest multiple of four", {
  viewpoint <- sf::st_sfc(sf::st_point(c(0, 1)))
  # Multiple of four, no warning
  ray_num <- 40
  expect_no_warning(get_rays(viewpoint, ray_num = ray_num))
  # Expect warning otherwise
  ray_num <- 41  # round down to 40
  expect_warning(get_rays(viewpoint, ray_num = ray_num), "40")
  ray_num <- 43  # round up to 44
  expect_warning(get_rays(viewpoint, ray_num = ray_num), "44")
})

test_that("Occluding rays without occluders return untouched rays", {
  ray_geoms <- sf::st_sfc(
    sf::st_linestring(cbind(c(0, 1), c(0, 0))),
    sf::st_linestring(cbind(c(0, 0), c(0, 1))),
    sf::st_linestring(cbind(c(0, -1), c(0, 0))),
    sf::st_linestring(cbind(c(0, 0), c(0, -1)))
  )
  rays <- sf::st_as_sf(ray_geoms)
  occluded_rays <- occlude_rays(rays)
  expect_setequal(ray_geoms, sf::st_geometry(occluded_rays))
})

test_that("Occluding rays with a polygon returns modified rays", {
  ray_geoms <- sf::st_sfc(
    sf::st_linestring(cbind(c(0, 0), c(0, 1))),
    sf::st_linestring(cbind(c(0, 1), c(0, 0))),
    sf::st_linestring(cbind(c(0, -1), c(0, 0))),
    sf::st_linestring(cbind(c(0, 0), c(0, -1)))
  )
  rays <- sf::st_as_sf(ray_geoms)
  occluder <- create_occluder(-0.5, 0, 0.2, 0.2)

  actual <- occlude_rays(rays, occluders = occluder)
  expected <- sf::st_sfc(
    sf::st_linestring(cbind(c(0, 1), c(0, 0))),
    sf::st_linestring(cbind(c(0, 0), c(0, 1))),
    sf::st_linestring(cbind(c(0, -0.4), c(0, 0))),
    sf::st_linestring(cbind(c(0, 0), c(0, -1)))
  )
  expect_setequal(sf::st_geometry(actual), expected)
})

test_that("get_isovists transforms rays to polygons", {
  center_isovist_1 <- sf::st_point(c(0, 0))
  points_isovist_1 <- sf::st_sfc(
    sf::st_point(c(0, 1)), sf::st_point(c(1, 0)),
    sf::st_point(c(0, -1)), sf::st_point(c(-1, 0))
  )
  center_isovist_2 <- sf::st_point(c(2, 0))
  points_isovist_2 <- sf::st_sfc(
    sf::st_point(c(2, 1)), sf::st_point(c(3, 0)), sf::st_point(c(2, -1))
  )
  ray_geoms <- sf::st_sfc(
    sf::st_linestring(c(center_isovist_1, points_isovist_1[[1]])),
    sf::st_linestring(c(center_isovist_1, points_isovist_1[[2]])),
    sf::st_linestring(c(center_isovist_1, points_isovist_1[[3]])),
    sf::st_linestring(c(center_isovist_1, points_isovist_1[[4]])),
    sf::st_linestring(c(center_isovist_2, points_isovist_2[[1]])),
    sf::st_linestring(c(center_isovist_2, points_isovist_2[[2]])),
    sf::st_linestring(c(center_isovist_2, points_isovist_2[[3]]))
  )
  rays <- sf::st_as_sf(ray_geoms)
  rays["isovist_id"] <- c(1, 1, 1, 1, 2, 2, 2)
  isovists <- get_isovists(rays)
  # Check that the isovist boundaries contain all original points
  expect_setequal(sf::st_cast(isovists[1], "POINT"), points_isovist_1)
  expect_setequal(sf::st_cast(isovists[2], "POINT"), points_isovist_2)
})

test_that("merge_isovists returns the merged polygon, with or without holes", {
  isovist_1 <-  sf::st_buffer(sf::st_point(c(0, 0)), 2)
  isovist_2 <-  sf::st_buffer(sf::st_point(c(2, 0)), 2)
  hole <- sf::st_buffer(sf::st_point(c(-1, 0)), 0.5)
  isovist_1_hole <- sf::st_difference(isovist_1, hole)
  isovists <- sf::st_sfc(isovist_1, isovist_2)
  isovists_hole <- sf::st_sfc(isovist_1_hole, isovist_2)
  # if remove_holes is FALSE, hole remains
  actual <- merge_isovists(isovists_hole, remove_holes = FALSE)
  expected <- sf::st_union(isovists_hole)
  expect_equal(actual, expected)
  # if remove_holes is TRUE, hole is removed
  actual <- merge_isovists(isovists_hole, remove_holes = TRUE)
  expected <- sf::st_union(isovists)
  expect_equal(actual, expected)
})

Try the visor package in your browser

Any scripts or data that you put into this service are public.

visor documentation built on April 11, 2025, 6:04 p.m.