tests/testthat/test-lcc.R

test_that("lcc_fwd works with single standard parallel", {
  pts <- cbind(lon = c(-100, -99, -98), lat = c(40, 41, 42))
  result <- lcc_fwd(pts, lon0 = -100, stdlat = 40)
  
  expect_s3_class(result, "data.frame")
  expect_named(result, c("x", "y", "convergence", "scale", "lon", "lat"))
  expect_equal(nrow(result), 3)
  
  # Check types
  expect_type(result$x, "double")
  expect_type(result$y, "double")
  expect_type(result$convergence, "double")
  expect_type(result$scale, "double")
  
  # Point on central meridian and standard parallel should have x=0
  result_origin <- lcc_fwd(c(-100, 40), lon0 = -100, stdlat = 40)
  expect_equal(result_origin$x, 0, tolerance = 1e-6)
  
  # Scale at standard parallel should be k0 (default 1)
  expect_equal(result_origin$scale, 1, tolerance = 1e-9)
})

test_that("lcc_fwd works with two standard parallels", {
  pts <- cbind(lon = c(-100, -99, -98), lat = c(40, 41, 42))
  result <- lcc_fwd(pts, lon0 = -96, stdlat1 = 33, stdlat2 = 45)
  
  expect_s3_class(result, "data.frame")
  expect_named(result, c("x", "y", "convergence", "scale", "lon", "lat"))
  expect_equal(nrow(result), 3)
  
  # Scale should be 1 at both standard parallels
  result_sp1 <- lcc_fwd(c(-96, 33), lon0 = -96, stdlat1 = 33, stdlat2 = 45)
  result_sp2 <- lcc_fwd(c(-96, 45), lon0 = -96, stdlat1 = 33, stdlat2 = 45)
  expect_equal(result_sp1$scale, 1, tolerance = 1e-9)
  expect_equal(result_sp2$scale, 1, tolerance = 1e-9)
})

test_that("lcc_fwd accepts different input formats", {
  result1 <- lcc_fwd(c(-100, 40), lon0 = -100, stdlat = 40)
  result2 <- lcc_fwd(cbind(-100, 40), lon0 = -100, stdlat = 40)
  result3 <- lcc_fwd(list(lon = -100, lat = 40), lon0 = -100, stdlat = 40)
  
  expect_equal(result1$x, result2$x)
  expect_equal(result1$x, result3$x)
  expect_equal(result1$y, result2$y)
})

test_that("lcc_fwd requires standard parallel specification", {
  expect_error(lcc_fwd(c(-100, 40), lon0 = -100), 
               "Specify either")
  expect_error(lcc_fwd(c(-100, 40), lon0 = -100, stdlat1 = 33), 
               "Specify either")
})

test_that("lcc_rev works with single standard parallel", {
  # First do forward
  pts <- cbind(lon = c(-100, -99, -98), lat = c(40, 41, 42))
  fwd <- lcc_fwd(pts, lon0 = -100, stdlat = 40)
  
  # Then reverse
  result <- lcc_rev(fwd$x, fwd$y, lon0 = -100, stdlat = 40)
  
  expect_s3_class(result, "data.frame")
  expect_named(result, c("lon", "lat", "convergence", "scale", "x", "y"))
  expect_equal(nrow(result), 3)
  
  # Should recover original coordinates
  expect_equal(result$lon, pts[, 1], tolerance = 1e-9)
  expect_equal(result$lat, pts[, 2], tolerance = 1e-9)
})

test_that("lcc_rev works with two standard parallels", {
  # First do forward
  pts <- cbind(lon = c(-100, -99, -98), lat = c(40, 41, 42))
  fwd <- lcc_fwd(pts, lon0 = -96, stdlat1 = 33, stdlat2 = 45)
  
  # Then reverse
  result <- lcc_rev(fwd$x, fwd$y, lon0 = -96, stdlat1 = 33, stdlat2 = 45)
  
  # Should recover original coordinates
  expect_equal(result$lon, pts[, 1], tolerance = 1e-9)
  expect_equal(result$lat, pts[, 2], tolerance = 1e-9)
})

test_that("lcc round-trip preserves coordinates", {
  pts <- cbind(
    lon = runif(10, -120, -80),
    lat = runif(10, 30, 50)
  )
  
  # Single standard parallel
  fwd1 <- lcc_fwd(pts, lon0 = -100, stdlat = 40)
  rev1 <- lcc_rev(fwd1$x, fwd1$y, lon0 = -100, stdlat = 40)
  expect_equal(rev1$lon, pts[, 1], tolerance = 1e-9)
  expect_equal(rev1$lat, pts[, 2], tolerance = 1e-9)
  
  # Two standard parallels
  fwd2 <- lcc_fwd(pts, lon0 = -100, stdlat1 = 35, stdlat2 = 45)
  rev2 <- lcc_rev(fwd2$x, fwd2$y, lon0 = -100, stdlat1 = 35, stdlat2 = 45)
  expect_equal(rev2$lon, pts[, 1], tolerance = 1e-9)
  expect_equal(rev2$lat, pts[, 2], tolerance = 1e-9)
})

test_that("lcc convergence is zero on central meridian", {
  pts <- cbind(lon = -100, lat = c(35, 40, 45))
  result <- lcc_fwd(pts, lon0 = -100, stdlat = 40)
  
  expect_equal(result$convergence, c(0, 0, 0), tolerance = 1e-9)
})

test_that("lcc handles points far from central meridian", {
  # Point 30 degrees away from central meridian
  result <- lcc_fwd(c(-130, 40), lon0 = -100, stdlat = 40)
  
  expect_true(is.finite(result$x))
  expect_true(is.finite(result$y))
  expect_true(result$x < 0)  # West of central meridian
  
  # Should still round-trip
  rev <- lcc_rev(result$x, result$y, lon0 = -100, stdlat = 40)
  expect_equal(rev$lon, -130, tolerance = 1e-9)
  expect_equal(rev$lat, 40, tolerance = 1e-9)
})

test_that("lcc scale varies correctly with latitude", {
  # Between standard parallels, scale should be < 1 (secant cone)
  # Outside standard parallels, scale should be > 1
  
  pts <- cbind(lon = -100, lat = c(30, 39, 45, 50))
  result <- lcc_fwd(pts, lon0 = -100, stdlat1 = 35, stdlat2 = 45)
  
  # At lat 30 (below stdlat1=35): scale > 1
  expect_true(result$scale[1] > 1)
  
  # At lat 39 (between 35 and 45): scale < 1
  expect_true(result$scale[2] < 1)
  
  # At lat 45 (at stdlat2): scale = 1
  expect_equal(result$scale[3], 1, tolerance = 1e-9)
  
  # At lat 50 (above stdlat2=45): scale > 1
  expect_true(result$scale[4] > 1)
})

test_that("lcc handles custom scale factors", {
  pt <- c(-100, 40)
  
  # With k0 = 0.9996 (like UTM)
  result <- lcc_fwd(pt, lon0 = -100, stdlat = 40, k0 = 0.9996)
  expect_equal(result$scale, 0.9996, tolerance = 1e-9)
})

test_that("lcc is vectorized", {
  pts <- cbind(
    lon = seq(-110, -90, by = 2),
    lat = seq(35, 45, length.out = 11)
  )
  
  result <- lcc_fwd(pts, lon0 = -100, stdlat = 40)
  expect_equal(nrow(result), 11)
  
  rev <- lcc_rev(result$x, result$y, lon0 = -100, stdlat = 40)
  expect_equal(nrow(rev), 11)
})

Try the geographiclib package in your browser

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

geographiclib documentation built on March 4, 2026, 9:07 a.m.