tests/testthat/test-geohash.R

test_that("geohash_fwd works with single point", {
  gh <- geohash_fwd(c(147.325, -42.881))

  expect_type(gh, "character")
  expect_length(gh, 1)

  expect_equal(nchar(gh), 12)  # Default length
  expect_match(gh, "^[0-9a-z]+$")  # Base32 characters
})

test_that("geohash_fwd works with multiple points", {
  pts <- cbind(lon = c(147, -74, 0), lat = c(-42, 40.7, 51.5))
  gh <- geohash_fwd(pts)

  expect_type(gh, "character")
  expect_length(gh, 3)
  expect_true(all(nchar(gh) == 12))
})

test_that("geohash_fwd handles different lengths", {
  pt <- c(147.325, -42.881)

  for (len in 1:18) {
    gh <- geohash_fwd(pt, len = len)
    expect_equal(nchar(gh), len)
  }
})

test_that("geohash_fwd accepts vector of lengths", {
  pts <- cbind(lon = c(147, -74, 0), lat = c(-42, 40.7, 51.5))
  gh <- geohash_fwd(pts, len = c(4, 8, 12))

  expect_equal(nchar(gh), c(4, 8, 12))
})

test_that("geohash_fwd rejects invalid lengths", {
  expect_error(geohash_fwd(c(0, 0), len = 0), "between 1 and 18")
  expect_error(geohash_fwd(c(0, 0), len = 19), "between 1 and 18")
})

test_that("geohash_fwd accepts different input formats", {
  gh1 <- geohash_fwd(c(147, -42))
  gh2 <- geohash_fwd(cbind(147, -42))
  gh3 <- geohash_fwd(list(lon = 147, lat = -42))

  expect_equal(gh1, gh2)
  expect_equal(gh1, gh3)
})

test_that("geohash_rev returns correct structure", {
  gh <- "r3dp5de7n9qs"
  result <- geohash_rev(gh)

  expect_s3_class(result, "data.frame")
  expect_named(result, c("lon", "lat", "len", "lat_resolution", "lon_resolution"))
  expect_equal(nrow(result), 1)

  expect_type(result$lon, "double")
  expect_type(result$lat, "double")
  expect_type(result$len, "integer")
  expect_type(result$lat_resolution, "double")
  expect_type(result$lon_resolution, "double")
})

test_that("geohash_rev is vectorized", {
  gh <- c("r3dp5de7n9qs", "dr5regw3pg6s", "gcpvj0duq4s0")
  result <- geohash_rev(gh)

  expect_equal(nrow(result), 3)
  expect_equal(result$len, c(12, 12, 12))
})

test_that("geohash round-trip preserves location", {
  pts <- cbind(lon = c(147.325, -74.006, 0.1275),
               lat = c(-42.881, 40.7128, 51.5074))

  gh <- geohash_fwd(pts, len = 12)
  result <- geohash_rev(gh)

  # With length 12, precision is ~19mm, so tolerance is very small
  expect_equal(result$lon, pts[, 1], tolerance = 1e-6)
  expect_equal(result$lat, pts[, 2], tolerance = 1e-6)
})

test_that("geohash truncation preserves containment", {
  pt <- c(147.325, -42.881)
  gh_full <- geohash_fwd(pt, len = 12)

  for (len in 1:11) {
    gh_truncated <- substr(gh_full, 1, len)
    result <- geohash_rev(gh_truncated)

    # Original point should be within the resolution of truncated geohash
    expect_true(abs(pt[1] - result$lon) <= result$lon_resolution)
    expect_true(abs(pt[2] - result$lat) <= result$lat_resolution)
  }
})

test_that("geohash_resolution returns correct structure", {
  result <- geohash_resolution(1:12)

  expect_s3_class(result, "data.frame")
  expect_named(result, c("len", "lat_resolution", "lon_resolution"))
  expect_equal(nrow(result), 12)

  # Resolution should decrease as length increases
  expect_true(all(diff(result$lat_resolution) < 0))
  expect_true(all(diff(result$lon_resolution) < 0))
})

test_that("geohash_resolution rejects invalid lengths", {
  expect_error(geohash_resolution(0), "between 1 and 18")
  expect_error(geohash_resolution(19), "between 1 and 18")
})

test_that("geohash_length returns valid lengths", {
  # ~1 km precision
  len <- geohash_length(resolution = 1/111)
  expect_true(len >= 1 && len <= 18)

  # Higher precision should require longer geohash
  len_low <- geohash_length(resolution = 1)
  len_high <- geohash_length(resolution = 0.0001)
  expect_true(len_high > len_low)
})
  
test_that("geohash_length works with separate lat/lon resolution", {
  len <- geohash_length(lat_resolution = 0.01, lon_resolution = 0.01)
  expect_true(len >= 1 && len <= 18)
})

test_that("geohash_length requires correct arguments", {
  expect_error(geohash_length(), "Specify either")
  expect_error(geohash_length(lat_resolution = 0.01), "Specify either")
})

test_that("geohash handles edge cases", {
  # Equator and prime meridian
  gh <- geohash_fwd(c(0, 0))
  expect_type(gh, "character")
  result <- geohash_rev(gh)
  expect_equal(result$lon, 0, tolerance = 1e-6)
  expect_equal(result$lat, 0, tolerance = 1e-6)

  # Date line
  gh <- geohash_fwd(c(180, 0))
  expect_type(gh, "character")

  gh <- geohash_fwd(c(-180, 0))
  expect_type(gh, "character")

  # Poles
  gh_north <- geohash_fwd(c(0, 90))
  gh_south <- geohash_fwd(c(0, -90))
  expect_type(gh_north, "character")
  expect_type(gh_south, "character")
})

test_that("geohash neighbors share prefixes", {
  # Two nearby points should share geohash prefixes
  pt1 <- c(147.325, -42.881)
  pt2 <- c(147.326, -42.882)

  gh1 <- geohash_fwd(pt1, len = 12)
  gh2 <- geohash_fwd(pt2, len = 12)

  # Should share at least first few characters
  expect_equal(substr(gh1, 1, 6), substr(gh2, 1, 6))
})

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.