s2/0000755000176200001440000000000014645746012010607 5ustar liggesuserss2/tests/0000755000176200001440000000000014530411513011735 5ustar liggesuserss2/tests/area.Rout.save0000644000176200001440000000325714530412230014461 0ustar liggesusers R version 4.3.2 (2023-10-31) -- "Eye Holes" Copyright (C) 2023 The R Foundation for Statistical Computing Platform: x86_64-pc-linux-gnu (64-bit) R is free software and comes with ABSOLUTELY NO WARRANTY. You are welcome to redistribute it under certain conditions. Type 'license()' or 'licence()' for distribution details. R is a collaborative project with many contributors. Type 'contributors()' for more information and 'citation()' on how to cite R or R packages in publications. Type 'demo()' for some demos, 'help()' for on-line help, or 'help.start()' for an HTML browser interface to help. Type 'q()' to quit R. > library(s2) > > u = s2_union( + "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", + "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", + s2_options(snap = s2_snap_level(30)) + ) > s2_area(u, radius = 1) [1] 0.05284581 > s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 1) + + s2_area("POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", radius = 1) - + s2_area("POLYGON ((5 5, 10 5, 10 15, 5 10, 5 5))", radius = 1) [1] 0.04910511 > s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 1) [1] 0.03038216 > s2_area("POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", radius = 1) [1] 0.03002974 > s2_area("POLYGON ((5 5, 10 5, 10 15, 5 10, 5 5))", radius = 1) [1] 0.01130679 > > df = s2_difference( + "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", + "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", + s2_options(snap = s2_snap_level(30)) + ) > s2_area(df, radius = 1) - + (s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 1) - + s2_area("POLYGON ((5 5, 10 5, 10 15, 5 10, 5 5))", radius = 1)) [1] 0.003740703 > > proc.time() user system elapsed 0.436 1.276 0.215 s2/tests/area.R0000644000176200001440000000156314530411473013002 0ustar liggesuserslibrary(s2) u = s2_union( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", s2_options(snap = s2_snap_level(30)) ) s2_area(u, radius = 1) s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 1) + s2_area("POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", radius = 1) - s2_area("POLYGON ((5 5, 10 5, 10 15, 5 10, 5 5))", radius = 1) s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 1) s2_area("POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", radius = 1) s2_area("POLYGON ((5 5, 10 5, 10 15, 5 10, 5 5))", radius = 1) df = s2_difference( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", s2_options(snap = s2_snap_level(30)) ) s2_area(df, radius = 1) - (s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 1) - s2_area("POLYGON ((5 5, 10 5, 10 15, 5 10, 5 5))", radius = 1)) s2/tests/testthat/0000755000176200001440000000000014645746012013611 5ustar liggesuserss2/tests/testthat/test-utils.R0000644000176200001440000000252314530411473016044 0ustar liggesusers test_that("recycle_common works", { expect_identical(recycle_common(1, 2), list(1, 2)) expect_identical(recycle_common(1, b = 2), list(1, b = 2)) expect_identical(recycle_common(1, 2:4), list(c(1, 1, 1), c(2L, 3L, 4L))) expect_identical(recycle_common(numeric(0), 2), list(numeric(0), numeric(0))) expect_error(recycle_common(numeric(0), 2:4), "Incompatible lengths") }) test_that("problems stopper formats problems correctly", { expect_error( stop_problems_create(1, "Houston! We have a correctly formatted problem!"), "Found 1 feature with invalid" ) expect_error( stop_problems_create(1:11, paste0("problem ", 1:11)), "\\.\\.\\.and 1 more" ) }) test_that("processing problems stopper formats problems correctly", { expect_error( stop_problems_process(1, "Houston! We have a correctly formatted problem!"), "Encountered 1 processing error" ) expect_error( stop_problems_process(1:11, paste0("problem ", 1:11)), "\\.\\.\\.and 1 more" ) }) test_that("wkt tester works", { expect_wkt_equal("POINT (0.123456 0)", "POINT (0.1234561 0)", precision = 6) expect_failure(expect_wkt_equal("POINT (0.123456 0)", "POINT (0.1234561 0)", precision = 7)) }) test_that("almost equal expectation works", { expect_near(0.001, 0, epsilon = 0.0011) expect_failure(expect_near(0.001, 0, epsilon = 0.0009)) }) s2/tests/testthat/test-vctrs.R0000644000176200001440000000145514530411473016050 0ustar liggesusers test_that("s2_geography is a vctr", { x <- new_s2_geography(list(NULL)) expect_true(vctrs::vec_is(x)) expect_identical(vctrs::vec_data(x), list(NULL)) expect_identical(vctrs::vec_restore(list(NULL), x), x) expect_identical(vctrs::vec_ptype_abbr(x), class(x)[1]) }) test_that("s2_cell is a vctr", { x <- new_s2_cell(NA_real_) expect_true(vctrs::vec_is(x)) expect_identical(vctrs::vec_data(x), NA_real_) expect_identical(vctrs::vec_restore(NA_real_, x), x) expect_identical(vctrs::vec_ptype_abbr(x), "s2cell") }) test_that("s2_cell_union is a vctr", { x <- new_s2_cell_union(list(NULL)) expect_true(vctrs::vec_is(x)) expect_identical(vctrs::vec_data(x), list(NULL)) expect_identical(vctrs::vec_restore(list(NULL), x), x) expect_identical(vctrs::vec_ptype_abbr(x), "s2cellunion") }) s2/tests/testthat/test-s2-matrix.R0000644000176200001440000001606514530411473016540 0ustar liggesusers test_that("s2_closest|farthest_feature() works", { cities <- s2_data_cities("London") countries <- s2_data_countries() # with zero length y, results will all be empty expect_identical(s2_closest_feature(cities, character(0)), rep_len(NA_integer_, length(cities))) # should correctly identify that London is closest to United Kingdom country_match <- s2_closest_feature(cities, countries) expect_identical(s2_data_tbl_countries$name[country_match], "United Kingdom") country_match_farthest <- s2_farthest_feature(cities, countries) expect_identical(s2_data_tbl_countries$name[country_match_farthest], "New Zealand") }) test_that("s2_closest_edges() works", { expect_identical( s2_closest_edges( "POINT (0 0)", c("POINT (0 0)", "POINT (0 1)", "POINT (0 2)", "POINT (0 3)"), k = 1 ), list(1L) ) expect_identical( s2_closest_edges( "POINT (0 0)", c("POINT (0 0)", "POINT (0 1)", "POINT (0 2)", "POINT (0 3)"), k = 2, min_distance = 0 ), list(2L) ) expect_identical( s2_closest_edges( "POINT (0 0)", c("POINT (0 0)", "POINT (0 1)", "POINT (0 2)", "POINT (0 3)"), k = 5 ) %>% lapply(sort), list(1:4) ) expect_identical( s2_closest_edges( "POINT (0 0)", c("POINT (0 0)", "POINT (0 1)", "POINT (0 2)", "POINT (0 3)"), k = 5, max_distance = 2.5 * pi / 180, radius = 1 ) %>% lapply(sort), list(1:3) ) }) test_that("matrix predicates work", { expect_identical( s2_contains_matrix( "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", c("POINT (-1 0.5)", "POINT (0.5 0.5)", "POINT (2 0.5)"), ), list(2L) ) expect_identical( s2_within_matrix( c("POINT (-1 0.5)", "POINT (0.5 0.5)", "POINT (2 0.5)"), "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))" ), list(integer(0), 1L, integer(0)) ) expect_identical( s2_covers_matrix( "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", c("POINT (-1 0.5)", "POINT (0.5 0.5)", "POINT (2 0.5)"), ), list(2L) ) expect_identical( s2_covered_by_matrix( c("POINT (-1 0.5)", "POINT (0.5 0.5)", "POINT (2 0.5)"), "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))" ), list(integer(0), 1L, integer(0)) ) expect_identical( s2_intersects_matrix( c("POINT (-1 0.5)", "POINT (0.5 0.5)", "POINT (2 0.5)"), "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))" ), list(integer(0), 1L, integer(0)) ) expect_identical( s2_disjoint_matrix( c("POINT (-1 0.5)", "POINT (0.5 0.5)", "POINT (2 0.5)"), "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))" ), list(1L, integer(0), 1L) ) expect_identical( s2_equals_matrix( c("POINT (-1 0.5)", "POINT (0.5 0.5)", "POINT (2 0.5)", "POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))"), "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))" ), list(integer(0), integer(0), integer(0), 1L) ) expect_identical( s2_touches_matrix( c("POINT (0.5 0.5)", "POINT (0 0)", "POINT (-0.5 -0.5)"), "POLYGON ((0 0, 0 1, 1 1, 0 0))" ), list(integer(0), 1L, integer(0)) ) expect_identical( s2_dwithin_matrix( c("POINT (-1 0.5)", "POINT (0.5 0.5)", "POINT (2 0.5)", "POINT (10 0.5)"), "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", 0 ), list(integer(0), 1L, integer(0), integer(0)) ) expect_identical( s2_dwithin_matrix( c("POINT (-1 0.5)", "POINT (0.5 0.5)", "POINT (2 0.5)"), "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", s2_earth_radius_meters() ), list(1L, 1L, 1L) ) }) test_that("s2_(max_)?distance_matrix() works", { x <- c("POINT (0 0)", "POINT (0 90)") y <- c("POINT (180 0)", "POINT (0 -90)", "POINT (0 0)") expect_equal( s2_distance_matrix(x, x, radius = 180 / pi), matrix(c(0, 90, 90, 0), ncol = 2) ) expect_equal( s2_distance_matrix(x, y, radius = 180 / pi), matrix(c(180, 90, 90, 180, 0, 90), ncol = 3) ) # max distance is the same for points expect_equal( s2_max_distance_matrix(x, x, radius = 180 / pi), matrix(c(0, 90, 90, 0), ncol = 2) ) expect_equal( s2_max_distance_matrix(x, y, radius = 180 / pi), matrix(c(180, 90, 90, 180, 0, 90), ncol = 3) ) # NA handling for both rows and cols y[2] <- NA expect_true(all(is.na(s2_distance_matrix(x, y)[, 2]))) expect_true(all(is.na(s2_max_distance_matrix(x, y)[, 2]))) x[2] <- NA expect_true(all(is.na(s2_distance_matrix(x, y)[2, ]))) expect_true(all(is.na(s2_max_distance_matrix(x, y)[2, ]))) }) test_that("s2_may_intersect_matrix() works", { countries <- s2_data_countries() timezones <- s2_data_timezones() maybe_intersects <- s2_may_intersect_matrix(countries, timezones) intersects <- s2_intersects_matrix_brute_force(countries, timezones) for (i in seq_along(countries)) { expect_identical(setdiff(intersects[[!!i]], maybe_intersects[[!!i]]), integer(0)) } }) test_that("indexed matrix predicates return the same thing as brute-force comparisons", { countries <- s2_data_countries() timezones <- s2_data_timezones() # contains expect_identical( s2_contains_matrix(countries, countries), s2_contains_matrix_brute_force(countries, countries) ) expect_identical( s2_contains_matrix(timezones, countries), s2_contains_matrix_brute_force(timezones, countries) ) # within expect_identical( s2_within_matrix(countries, countries), s2_within_matrix_brute_force(countries, countries) ) expect_identical( s2_within_matrix(timezones, countries), s2_within_matrix_brute_force(timezones, countries) ) # covers expect_identical( s2_covers_matrix(countries, countries), s2_covers_matrix_brute_force(countries, countries) ) expect_identical( s2_covers_matrix(timezones, countries), s2_covers_matrix_brute_force(timezones, countries) ) # covered by expect_identical( s2_covered_by_matrix(countries, countries), s2_covered_by_matrix_brute_force(countries, countries) ) expect_identical( s2_covered_by_matrix(timezones, countries), s2_covered_by_matrix_brute_force(timezones, countries) ) # intersects expect_identical( s2_intersects_matrix(countries, countries), s2_intersects_matrix_brute_force(countries, countries) ) expect_identical( s2_intersects_matrix(timezones, countries), s2_intersects_matrix_brute_force(timezones, countries) ) # disjoint expect_identical( s2_disjoint_matrix(countries, countries), s2_disjoint_matrix_brute_force(countries, countries) ) expect_identical( s2_disjoint_matrix(timezones, countries), s2_disjoint_matrix_brute_force(timezones, countries) ) # equals expect_identical( s2_equals_matrix(countries, countries), s2_equals_matrix_brute_force(countries, countries) ) expect_identical( s2_equals_matrix(timezones, countries), s2_equals_matrix_brute_force(timezones, countries) ) # dwithin expect_identical( s2_dwithin_matrix(countries, countries, 1e6), s2_dwithin_matrix_brute_force(countries, countries, 1e6) ) expect_identical( s2_dwithin_matrix(timezones, countries, 1e6), s2_dwithin_matrix_brute_force(timezones, countries, 1e6) ) }) s2/tests/testthat/test-s2-bounds.R0000644000176200001440000000337614530411473016527 0ustar liggesusers test_that("s2_bounds_cap works", { cap_ant <- s2_data_countries("Antarctica") expect_s3_class(s2_bounds_cap(cap_ant), "data.frame") expect_identical(nrow(s2_bounds_cap(cap_ant)), 1L) expect_true(s2_bounds_cap(cap_ant)$lat < -80) expect_true(s2_bounds_cap(cap_ant)$angle > 20) expect_identical(nrow(s2_bounds_cap(s2_data_countries(c("Antarctica", "Netherlands")))), 2L) expect_true(s2_bounds_cap(s2_data_countries("Netherlands"))$angle < 2) expect_true(s2_bounds_cap(s2_data_countries("Fiji"))$angle < 2) }) test_that("s2_bounds_rect works", { rect_ant <- s2_bounds_rect(s2_data_countries("Antarctica")) expect_s3_class(rect_ant, "data.frame") expect_named(rect_ant, c("lng_lo", "lat_lo", "lng_hi", "lat_hi")) expect_identical(nrow(s2_bounds_rect(s2_data_countries(c("Antarctica", "Netherlands")))), 2L) expect_equal(rect_ant$lng_lo, -180) expect_equal(rect_ant$lng_hi, 180) expect_equal(rect_ant$lat_lo, -90) expect_true(rect_ant$lat_hi < -60) expect_identical(nrow(s2_bounds_rect(s2_data_countries(c("Antarctica", "Netherlands")))), 2L) rect_nl <- s2_bounds_rect(s2_data_countries("Netherlands")) expect_true((rect_nl$lng_hi - rect_nl$lng_lo) < 4) rect_fiji <- s2_bounds_rect(s2_data_countries("Fiji")) expect_true(rect_fiji$lng_hi < rect_fiji$lng_lo) rect_multipoint <- s2_bounds_rect("MULTIPOINT(-179 0,179 1,-180 10)") expect_equal(rect_multipoint$lat_lo, 0) expect_equal(rect_multipoint$lat_hi, 10) expect_equal(rect_multipoint$lng_lo, 179) expect_equal(rect_multipoint$lng_hi, -179) rect_linestring <- s2_bounds_rect("LINESTRING(-179 0,179 1)") expect_equal(rect_linestring$lat_lo, 0) expect_equal(rect_linestring$lat_hi, 1) expect_equal(rect_linestring$lng_lo, 179) expect_equal(rect_linestring$lng_hi, -179) }) s2/tests/testthat/test-s2-cell.R0000644000176200001440000002651714530411473016156 0ustar liggesusers test_that("s2_cell class works", { expect_s3_class(new_s2_cell(double()), "s2_cell") expect_s3_class(new_s2_cell(NA_real_), "s2_cell") expect_true(is.na(new_s2_cell(NA_real_))) expect_identical(as_s2_cell(s2_cell()), s2_cell()) expect_identical( as.list(new_s2_cell(NA_real_)), list(new_s2_cell(NA_real_)) ) }) test_that("s2_cell bit64::integer64 support works", { cells <- c(as_s2_cell(NA_character_), s2_cell_sentinel()) int64s <- bit64::as.integer64(cells) expect_identical(int64s, bit64::as.integer64(c(NA, -1))) expect_identical(as_s2_cell(int64s), cells) }) test_that("invalid and sentinel values work as expected", { expect_false(s2_cell_is_valid(s2_cell_sentinel())) expect_false(s2_cell_is_valid(s2_cell_invalid())) expect_false(is.na(s2_cell_sentinel())) expect_false(is.na(s2_cell_invalid())) expect_true(s2_cell_sentinel() > s2_cell_invalid()) }) test_that("sort() and unique() work", { # temporary workaround for broken c() in wk expect_identical( unique(new_s2_cell(c(unclass(s2_cell_sentinel()), NA, 0, 0, unclass(s2_cell("5"))))), new_s2_cell(c(0, unclass(s2_cell("5")), NA, unclass(s2_cell_sentinel()))) ) expect_identical( sort(new_s2_cell(c(unclass(s2_cell_sentinel()), NA, 0, 0, unclass(s2_cell("5"))))), new_s2_cell(c(0, 0, unclass(s2_cell("5")), NA, unclass(s2_cell_sentinel()))) ) expect_identical( sort( new_s2_cell(c(unclass(s2_cell_sentinel()), NA, 0, 0, unclass(s2_cell("5")))), decreasing = TRUE ), rev(new_s2_cell(c(0, 0, unclass(s2_cell("5")), NA, unclass(s2_cell_sentinel())))) ) }) test_that("geography exporters work", { expect_identical( s2_as_text(s2_cell_center(as_s2_cell(s2_lnglat(c(-64, NA), c(45, NA)))), precision = 5), c("POINT (-64 45)", NA) ) expect_identical( s2_as_text(s2_cell_polygon(as_s2_cell(s2_lnglat(c(-64, NA), c(45, NA)))), precision = 5), c("POLYGON ((-64 45, -64 45, -64 45, -64 45, -64 45))", NA) ) expect_identical( s2_as_text(s2_cell_boundary(as_s2_cell(s2_lnglat(c(-64, NA), c(45, NA)))), precision = 5), c("LINESTRING (-64 45, -64 45, -64 45, -64 45, -64 45)", NA) ) expect_equal( s2_x(s2_cell_vertex(s2_cell(c("5", "5", "5", NA)), k = c(0, 1, NA, 1))), c(45, 135, NA, NA) ) }) test_that("s2_cell_is_valid() works", { expect_identical( s2_cell_is_valid(new_s2_cell(double())), logical() ) expect_identical( s2_cell_is_valid(new_s2_cell(NA_real_)), FALSE ) }) test_that("Ops, Math, and Summary errors for non-meaningful s2_cell() ops", { expect_error(s2_cell("X") + 1, "not meaningful") expect_error(abs(s2_cell("X")), "not meaningful") expect_error(all(s2_cell("X")), "not meaningful") }) test_that("Ops, Math, and Summary ops work", { expect_identical( s2_cell(c("5", "5", "x", "x", "x", NA, NA)) == s2_cell(c("5", "x", "5", "x", NA, "x", NA)), c(TRUE, FALSE, FALSE, TRUE, NA, NA, NA) ) expect_identical( s2_cell(c("5", "5", "x", "x", "x", NA, NA)) != s2_cell(c("5", "x", "5", "x", NA, "x", NA)), c(FALSE, TRUE, TRUE, FALSE, NA, NA, NA) ) # 'invalid' is 0 expect_identical( s2_cell(c("5", "5", "x", "x", "x", NA, NA)) > s2_cell(c("5", "x", "5", "x", NA, "x", NA)), c(FALSE, TRUE, FALSE, FALSE, NA, NA, NA) ) expect_identical( s2_cell(c("5", "5", "x", "x", "x", NA, NA)) >= s2_cell(c("5", "x", "5", "x", NA, "x", NA)), c(TRUE, TRUE, FALSE, TRUE, NA, NA, NA) ) expect_identical( s2_cell(c("5", "5", "x", "x", "x", NA, NA)) < s2_cell(c("5", "x", "5", "x", NA, "x", NA)), c(FALSE, FALSE, TRUE, FALSE, NA, NA, NA) ) expect_identical( s2_cell(c("5", "5", "x", "x", "x", NA, NA)) <= s2_cell(c("5", "x", "5", "x", NA, "x", NA)), c(TRUE, FALSE, TRUE, TRUE, NA, NA, NA) ) expect_identical( cummax(s2_cell(c("5", "x", "5", "x", NA, "x", NA))), s2_cell(c("5", "5", "5", "5", NA, NA, NA)) ) expect_identical( cummin(s2_cell(c("5", "x", "5", "x", NA, "x", NA))), s2_cell(c("5", "x", "x", "x", NA, NA, NA)) ) expect_identical( range(s2_cell(c("5", "x", "5", "x", NA, "x", NA)), na.rm = TRUE), s2_cell(c("X", "5")) ) expect_identical( max(s2_cell(c("5", "x", "5", "x", NA, "x", NA)), na.rm = TRUE), s2_cell("5") ) expect_identical( min(s2_cell(c("5", "x", "5", "x", NA, "x", NA)), na.rm = TRUE), s2_cell("X") ) expect_identical( range(s2_cell(c("5", "x", "5", "x", NA, "x", NA))), s2_cell(c(NA_character_, NA_character_)) ) expect_identical(range(s2_cell()), s2_cell(c(NA_character_, NA_character_))) expect_identical( range(s2_cell(NA_character_), na.rm = TRUE), s2_cell(c(NA_character_, NA_character_)) ) }) test_that("Binary ops are recycled at the C++ level", { expect_identical(s2_cell(rep("5", 2)) == s2_cell(rep("5", 2)), c(TRUE, TRUE)) expect_identical(s2_cell(rep("5", 1)) == s2_cell(rep("5", 2)), c(TRUE, TRUE)) expect_identical(s2_cell(rep("5", 2)) == s2_cell(rep("5", 1)), c(TRUE, TRUE)) expect_error(s2_cell(rep("5", 2)) == s2_cell(rep("5", 0)), "Can't recycle") }) test_that("s2_cell is not numeric", { expect_false(is.numeric(s2_cell())) }) test_that("s2_cell subsetting and concatenation work", { cells <- new_s2_cell(c(NA_real_, NA_real_)) expect_identical(cells[1], new_s2_cell(NA_real_)) expect_identical(cells[[1]], cells[1]) expect_identical( c(cells, cells), new_s2_cell(c(NA_real_, NA_real_, NA_real_, NA_real_)) ) expect_identical( rep(cells, 2), new_s2_cell(c(NA_real_, NA_real_, NA_real_, NA_real_)) ) expect_identical( rep_len(cells, 4), new_s2_cell(c(NA_real_, NA_real_, NA_real_, NA_real_)) ) expect_error( c(new_s2_cell(c(NA_real_, NA_real_)), 1:5), "Can't combine" ) }) test_that("s2_cell() can be created from s2_geography()", { expect_identical( as_s2_cell(as_s2_geography("POINT (-64 45)")), s2_cell("4b59a0cd83b5de49") ) }) test_that("s2_cell() can be created from s2_lnglat()", { expect_identical( as_s2_cell(s2_lnglat(-64, 45)), s2_cell("4b59a0cd83b5de49") ) }) test_that("s2_cell() can be created from s2_point()", { expect_identical( as_s2_cell(as_s2_point(s2_lnglat(-64, 45))), s2_cell("4b59a0cd83b5de49") ) }) test_that("s2_cell() can be created from character", { expect_identical( as_s2_cell("4b59a0cd83b5de49"), as_s2_cell(s2_lnglat(-64, 45)) ) }) test_that("subset-assignment works", { x <- as_s2_cell(c(NA, "4b59a0cd83b5de49", "4b5f6a7856889a33")) expect_identical(as.character(x[3]), "4b5f6a7856889a33") x[3] <- "4b59a0cd83b5de49" expect_identical(as.character(x[3]), "4b59a0cd83b5de49") x[[3]] <- "4b5f6a7856889a33" expect_identical(as.character(x[3]), "4b5f6a7856889a33") }) test_that("s2_cell can be put into a data.frame", { expect_identical( data.frame(geom = new_s2_cell(NA_real_)), new_data_frame(list(geom = new_s2_cell(NA_real_))) ) expect_error(as.data.frame(new_s2_cell(NA_real_)), "cannot coerce") }) test_that("s2_cell default format/print/str methods work", { expect_identical( format(s2_cell()), as.character(s2_cell()) ) expect_output(print(new_s2_cell(double())), "s2_cell") expect_output(print(new_s2_cell(NA_real_)), "s2_cell") expect_output(str(as_s2_cell(character())), "s2_cell\\[0\\]") expect_output(str(as_s2_cell(NA_character_)), "NA") }) test_that("s2 cell exporters work", { expect_equal( as.data.frame(s2_cell_to_lnglat(s2_cell(c("4b59a0cd83b5de49", "x", NA)))), as.data.frame(c(s2_lnglat(-64, 45), s2_lnglat(NA, NA), s2_lnglat(NA, NA))) ) expect_identical( s2_cell_debug_string(s2_cell(c("4b5f6a7856889a33", NA))), c("2/112233231103300223101010310121", NA) ) expect_match(s2_cell_debug_string(s2_cell("X")), "Invalid") }) test_that("s2_cell() accessors work", { expect_identical( s2_cell_is_valid(s2_cell(c("4b5f6a7856889a33", "5", "x", NA))), c(TRUE, TRUE, FALSE, FALSE) ) expect_identical( s2_cell_level(s2_cell(c("4b5f6a7856889a33", "5", "x", NA))), c(30L, 0L, NA, NA) ) expect_identical( s2_cell_is_leaf(s2_cell(c("4b5f6a7856889a33", "5", "x", NA))), c(TRUE, FALSE, NA, NA) ) expect_identical( s2_cell_is_face(s2_cell(c("4b5f6a7856889a33", "5", "x", NA))), c(FALSE, TRUE, NA, NA) ) expect_equal( s2_cell_area(s2_cell(c("5", "x", NA)), radius = 1), c(2 * pi / 3, NA, NA) ) expect_equal( s2_cell_area_approx(s2_cell(c("5", "x", NA)), radius = 1), c(2 * pi / 3, NA, NA) ) }) test_that("s2_cell() transversers work", { expect_identical( s2_cell_parent(s2_cell(c("5", "4b5f6a7856889a33", "x", NA)), 0), s2_cell(c("5", "5", NA, NA)) ) leaf_manual <- function(x, children) { if (length(children) == 0) { x } else { leaf_manual( s2_cell_child(x, children[1]), children[-1] ) } } # see output of s2_cell_debug_string() expect_identical( leaf_manual( s2_cell("5"), as.numeric(strsplit("112233231103300223101010310121", "")[[1]]) ), s2_cell("4b5f6a7856889a33") ) expect_identical( s2_cell_child(s2_cell(c("5", "5", "5", "x", NA)), c(-1, 4, NA, 0, 0)), s2_cell(as.character(c(NA, NA, NA, NA, NA))) ) expect_identical( s2_cell_debug_string(s2_cell_edge_neighbour(s2_cell("5"), 0:3)), c("1/", "3/", "4/", "0/") ) expect_identical( s2_cell_edge_neighbour(s2_cell(c("5", "5", "5", "x", NA)), c(-1, 4, NA, 0, 0)), s2_cell(as.character(c(NA, NA, NA, NA, NA))) ) }) test_that("s2_cell() binary operators work", { cell <- s2_cell("4b5f6a7856889a33") expect_true(s2_cell_contains(s2_cell_parent(cell), cell)) expect_false(s2_cell_contains(cell, s2_cell_parent(cell))) expect_identical(s2_cell_contains(s2_cell_sentinel(), s2_cell_sentinel()), NA) expect_equal( s2_cell_distance( as_s2_cell(s2_lnglat(0, 90)), as_s2_cell(s2_lnglat(0, 0)), radius = 1 ), pi / 2 ) expect_equal( s2_cell_max_distance( as_s2_cell(s2_lnglat(0, 90)), as_s2_cell(s2_lnglat(0, 0)), radius = 1 ), pi / 2 ) f1 <- s2_cell_parent(as_s2_cell(s2_lnglat(0, 0)), 0) f2 <- s2_cell_parent(as_s2_cell(s2_lnglat(0, 90)), 0) expect_equal(s2_cell_distance(f1, f2), 0) expect_equal(s2_cell_max_distance(f1, f2, radius = 1), pi) expect_identical(s2_cell_max_distance(s2_cell_sentinel(), s2_cell_sentinel()), NA_real_) expect_identical(s2_cell_distance(s2_cell_sentinel(), s2_cell_sentinel()), NA_real_) expect_false( s2_cell_may_intersect( as_s2_cell(s2_lnglat(0, 90)), as_s2_cell(s2_lnglat(0, 0)) ) ) expect_true(s2_cell_may_intersect(s2_cell_parent(cell), cell)) expect_true(s2_cell_may_intersect(cell, s2_cell_parent(cell))) expect_identical(s2_cell_may_intersect(s2_cell_sentinel(), s2_cell_sentinel()), NA) }) test_that("s2_cell_common_ancestor_level() works", { cell <- s2_cell_parent(s2_cell("4b5f6a7856889a33"), 10) children <- s2_cell_child(cell, 0:4) expect_identical( s2_cell_common_ancestor_level(children, cell), c(rep(10L, 4), NA), ) expect_identical(s2_cell_common_ancestor_level_agg(s2_cell()), NA_integer_) expect_identical( s2_cell_common_ancestor_level_agg( as_s2_cell(as_s2_geography(c("POINT (0 0)", "POINT (180 0)"))) ), -1L ) expect_identical( s2_cell_common_ancestor_level_agg(children, na.rm = TRUE), 10L ) expect_identical( s2_cell_common_ancestor_level_agg(children, na.rm = FALSE), NA_integer_ ) }) s2/tests/testthat/test-s2-options.R0000644000176200001440000000065414530411473016724 0ustar liggesusers test_that("s2_options() works", { expect_s3_class(s2_options(), "s2_options") }) test_that("s2_options() errors are readable", { expect_error(s2_intersects("POINT EMPTY", "POINT EMPTY", options = NULL), "must be created using") expect_error(s2_options(model = "not a model"), "must be one of") expect_error(s2_options(snap_radius = 100), "radius is too large") expect_error(s2_snap_level(31), "between 1 and 30") }) s2/tests/testthat/test-plot.R0000644000176200001440000000173614530411473015667 0ustar liggesusers test_that("s2_plot works", { x <- s2_data_countries() expect_identical(s2_plot(x), x) s2_plot(s2_data_cities(), add = TRUE) expect_identical(s2_plot(x, simplify = FALSE), x) expect_identical(s2_plot(x, plot_hemisphere = TRUE), x) expect_identical(s2_plot(x, centre = s2_lnglat(0, 0)), x) }) test_that("s2_plot works for all examples", { for (name in names(s2_data_example_wkt)) { geog <- as_s2_geography(s2_data_example_wkt[[name]]) # need non-nulls for now: # https://github.com/paleolimbot/wk/issues/143 geog <- geog[!is.na(geog)] expect_identical(s2_plot(geog), geog) } }) test_that("plot() works for vector classes", { x <- as_s2_geography("POINT (0 1)") expect_identical(plot(x), x) x <- s2_covering_cell_ids(as_s2_geography("POLYGON ((0 0, 1 0, 0 1, 0 0))")) expect_identical(plot(x), x) x <- as_s2_cell(as_s2_geography("POINT (0 1)")) expect_identical(plot(x), x) x <- s2_cell_parent(x, 7) expect_identical(plot(x), x) }) s2/tests/testthat/test-s2-transformers.R0000644000176200001440000005245214530411473017761 0ustar liggesusers test_that("s2_centroid() works", { expect_wkt_equal(s2_centroid("POINT (30 10)"), "POINT (30 10)") expect_true(s2_is_empty(s2_centroid("POINT EMPTY"))) expect_wkt_equal(s2_centroid("MULTIPOINT ((0 0), (0 10))"), "POINT (0 5)") expect_wkt_equal(s2_centroid("LINESTRING (0 0, 0 10)"), "POINT (0 5)", precision = 15) expect_near( s2_distance( s2_centroid("POLYGON ((-5 -5, 5 -5, 5 5, -5 5, -5 -5))"), "POINT (0 0)" ), 0, epsilon = 1e-6 ) }) test_that("s2_centroid() and s2_centroid_agg() normalize points", { expect_equal( s2_distance( s2_centroid("MULTIPOINT (1 1, 1 1)"), "POINT (1 1)" ), 0 ) expect_equal( s2_distance( s2_centroid_agg(c("POINT (1 1)", "POINT (1 1)")), "POINT (1 1)" ), 0 ) }) test_that("s2_point_on_surface() works", { expect_wkt_equal(s2_point_on_surface("POINT (30 10)"), "POINT (30 10)") expect_true(s2_is_empty(s2_point_on_surface("POINT EMPTY"))) expect_wkt_equal( s2_point_on_surface("POLYGON ((0 0, 10 0, 1 1, 0 10, 0 0))"), "POINT (0.4502368024893488 0.4502229020796313)", precision = 15 ) expect_wkt_equal( s2_point_on_surface("MULTIPOINT ((0 0), (0 5), (0 10))"), "POINT (0 5)" ) }) test_that("s2_boundary() works", { expect_true(s2_is_empty(s2_boundary("POINT (30 10)"))) expect_true(s2_is_empty(s2_boundary("POINT EMPTY"))) expect_true(s2_is_empty(s2_boundary("POLYGON EMPTY"))) expect_wkt_equal(s2_boundary("LINESTRING (0 0, 0 10)"), "MULTIPOINT ((0 0), (0 10))") expect_wkt_equal( s2_boundary("MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)) )"), "MULTILINESTRING ( (40 40, 20 45, 45 30, 40 40), (20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20) )", precision = 15 ) }) test_that("s2_closest_point() works", { expect_wkt_equal(s2_closest_point("POINT (0 1)", "POINT (30 10)"), "POINT (0 1)") expect_wkt_equal(s2_closest_point("LINESTRING (0 1, -12 -12)", "POINT (30 10)"), "POINT (0 1)") }) test_that("s2_minimum_clearance_line_between() works", { expect_wkt_equal( s2_minimum_clearance_line_between("POINT (0 1)", "POINT (30 10)"), "LINESTRING (0 1, 30 10)" ) expect_true(s2_is_empty(s2_minimum_clearance_line_between("POINT (30 10)", "POINT EMPTY"))) expect_wkt_equal( s2_minimum_clearance_line_between("LINESTRING (0 1, -12 -12)", "POINT (30 10)"), "LINESTRING (0 1, 30 10)" ) expect_wkt_equal( s2_minimum_clearance_line_between("LINESTRING (0 0, 1 1)", "LINESTRING (1 0, 0 1)"), "MULTIPOINT ((0.5 0.500057), (0.5 0.500057))", precision = 6 ) }) test_that("s2_difference() works", { expect_wkt_equal(s2_difference("POINT (30 10)", "POINT EMPTY"), "POINT (30 10)") expect_true(s2_is_empty(s2_difference("POINT (30 10)", "POINT (30 10)"))) expect_true(s2_is_empty(s2_difference("LINESTRING (0 0, 45 0)", "LINESTRING (0 0, 45 0)"))) }) test_that("s2_difference() works for polygons", { # on Windows i386, these fail without snap rounding df <- s2_difference( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", s2_options(snap = s2_snap_level(30)) ) expect_near( s2_area(df, radius = 1), s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 1) - s2_area("POLYGON ((5 5, 10 5, 10 15, 5 10, 5 5))", radius = 1), epsilon = 0.004 ) df0 <- s2_difference( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "open", snap = s2_snap_level(30)) ) df1 <- s2_difference( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "semi-open", snap = s2_snap_level(30)) ) df2 <- s2_difference( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "closed", snap = s2_snap_level(30)) ) expect_equal(s2_area(df0) - s2_area(df2), 0.0) expect_equal(s2_area(df0) - s2_area(df1), 0.0) }) test_that("s2_sym_difference() works", { expect_wkt_equal(s2_sym_difference("POINT (30 10)", "POINT EMPTY"), "POINT (30 10)") expect_true(s2_is_empty(s2_sym_difference("POINT (30 10)", "POINT (30 10)"))) expect_wkt_equal(s2_sym_difference("POINT (30 10)", "POINT (30 20)"), "MULTIPOINT ((30 20), (30 10))") expect_true(s2_is_empty(s2_sym_difference("LINESTRING (0 0, 45 0)", "LINESTRING (0 0, 45 0)"))) }) test_that("s2_sym_difference() works for polygons", { # on Windows i386, these fail without snap rounding df <- s2_sym_difference( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", s2_options(snap = s2_snap_level(30)) ) expect_near( s2_area(df, radius = 1), 2 * s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 1) - s2_area("POLYGON ((5 5, 10 5, 10 15, 5 10, 5 5))", radius = 1), epsilon = 0.0042 ) df0 <- s2_sym_difference( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "open", snap = s2_snap_level(30)) ) df1 <- s2_sym_difference( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "semi-open", snap = s2_snap_level(30)) ) df2 = s2_sym_difference( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", s2_options(model = "closed", snap = s2_snap_level(30)) ) expect_equal(s2_area(df0) - s2_area(df2), 0.0) expect_equal(s2_area(df0) - s2_area(df1), 0.0) }) test_that("s2_intersection() works", { expect_wkt_equal(s2_intersection("POINT (30 10)", "POINT (30 10)"), "POINT (30 10)") expect_true(s2_is_empty(s2_intersection("POINT (30 10)", "POINT (30 11)"))) expect_wkt_equal( s2_intersection( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "LINESTRING (0 5, 10 5)" ), "LINESTRING (0 5, 10 5)" ) expect_equal( s2_distance( s2_intersection("LINESTRING (-45 0, 45 0)", "LINESTRING (0 -10, 0 10)"), "POINT (0 0)" ), 0 ) }) test_that("s2_intersction() works for polygons", { expect_wkt_equal( s2_intersection( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", s2_options(snap = s2_snap_level(30)) ), "POLYGON ((5 5, 10 5, 10 10, 5 10, 5 5))", precision = 2 ) expect_true(s2_is_empty(s2_intersection("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POINT(0 0)"))) expect_true( s2_is_empty( s2_intersection( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POINT(0 0)", s2_options(model = "open") ) ) ) expect_true( s2_is_empty( s2_intersection("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POINT(0 0)", s2_options(model = "semi-open")) ) ) expect_wkt_equal( s2_intersection("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POINT(0 0)", s2_options(model = "closed")), "POINT(0 0)" ) df0 <- s2_intersection( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "open") ) df1 <- s2_intersection( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "semi-open") ) df2 <- s2_intersection( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "closed") ) expect_equal(s2_area(df0) - s2_area(df2), 0.0) expect_equal(s2_area(df0) - s2_area(df1), 0.0) }) test_that("s2_union(x) works", { expect_wkt_equal(s2_union("POINT (30 10)"), "POINT (30 10)") expect_wkt_equal(s2_union("POINT EMPTY"), "GEOMETRYCOLLECTION EMPTY") expect_wkt_equal( s2_union("MULTILINESTRING ((-45 0, 0 0), (0 0, 0 10))"), "LINESTRING (-45 0, 0 0, 0 10)" ) expect_wkt_equal(s2_union("GEOMETRYCOLLECTION (POINT (30 10))"), "POINT (30 10)") expect_wkt_equal( s2_union("GEOMETRYCOLLECTION (POINT (30 10), LINESTRING (0 0, 0 1))"), "GEOMETRYCOLLECTION (POINT (30 10), LINESTRING (0 0, 0 1))" ) }) test_that("s2_union(x) works with empty input", { expect_identical( s2_as_text(s2_union("MULTIPOLYGON EMPTY")), "GEOMETRYCOLLECTION EMPTY" ) }) test_that("s2_union(x) works with polygons that have overlapping input regions", { # two outer loops txt <- "MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), ((0.1 0.9, 0.1 1.9, 1.1 1.9, 1.1 0.9, 0.1 0.9)))" # geos::geos_unary_union(txt) %>% as_wkb() %>% s2_area(radius = 1) unioned <- s2_union(as_s2_geography(txt, check = F)) expect_equal(round(s2_area(unioned, radius = 1), 12), 0.00058172748) # two outer loops, one valid inner loop # geos::geos_unary_union(txt2) %>% as_wkb() %>% s2_area(radius = 1) txt2 <- "MULTIPOLYGON ( ((0 0, 0 1, 1 1, 1 0, 0 0), (0.1 0.1, 0.5 0.1, 0.5 0.5, 0.1 0.5, 0.1 0.1)), ((0.1 0.9, 0.1 1.9, 1.1 1.9, 1.1 0.9, 0.1 0.9)) )" unioned <- s2_union(as_s2_geography(txt2, check = F)) expect_equal(round(s2_area(unioned, radius = 1), 12), 0.000532989259) }) test_that("s2_union(x) errors for the case of mixed dimension collections", { expect_error( s2_union( c("GEOMETRYCOLLECTION(POLYGON ((-10 -10, -10 10, 10 10, 10 -10, -10 -10)), LINESTRING (0 -20, 0 20))") ), "for multidimensional collections not implemented" ) }) test_that("s2_union(x, y) works", { expect_wkt_equal(s2_union("POINT (30 10)", "POINT EMPTY"), "POINT (30 10)") expect_wkt_equal(s2_union("POINT EMPTY", "POINT EMPTY"), "GEOMETRYCOLLECTION EMPTY") # LINESTRING (-45 0, 0 0, 0 10) expect_wkt_equal( s2_union("LINESTRING (-45 0, 0 0)", "LINESTRING (0 0, 0 10)"), "LINESTRING (-45 0, 0 0, 0 10)" ) }) test_that("s2_union() works for polygons", { # on Windows i386, these fail without snap rounding u <- s2_union( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", s2_options(snap = s2_snap_level(30)) ) u0 <- s2_union( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "open", snap = s2_snap_level(30)) ) u1 <- s2_union( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "semi-open", snap = s2_snap_level(30)) ) u2 <- s2_union( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" , s2_options(model = "closed", snap = s2_snap_level(30)) ) expect_equal(s2_area(u0) - s2_area(u2), 0.0) expect_equal(s2_area(u0) - s2_area(u1), 0.0) expect_near( s2_area(u, radius = 1), s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 1) + s2_area("POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", radius = 1) - s2_area("POLYGON ((5 5, 10 5, 10 15, 5 10, 5 5))", radius = 1), epsilon = 0.004 ) }) test_that("binary operations use layer creation options", { expect_wkt_equal( s2_union( "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)", options = s2_options(polyline_type = "path", polyline_sibling_pairs = "discard") ), "LINESTRING (0 0, 0 1, 0 2, 0 3)" ) expect_true( s2_is_collection( s2_union( "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)", options = s2_options(polyline_type = "walk") ) ) ) expect_wkt_equal( s2_coverage_union_agg( "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)", options = s2_options(polyline_type = "path", polyline_sibling_pairs = "discard") ), "LINESTRING (0 0, 0 1, 0 2, 0 3)" ) expect_true( s2_is_collection( s2_coverage_union_agg( "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)", options = s2_options(polyline_type = "walk") ) ) ) }) test_that("s2_coverage_union_agg() works", { expect_wkt_equal(s2_coverage_union_agg(c("POINT (30 10)", "POINT EMPTY")), "POINT (30 10)") expect_wkt_equal(s2_coverage_union_agg(c("POINT EMPTY", "POINT EMPTY")), "GEOMETRYCOLLECTION EMPTY") # NULL handling expect_identical( s2_coverage_union_agg(c("POINT (30 10)", NA), na.rm = FALSE), as_s2_geography(NA_character_) ) expect_wkt_equal( s2_coverage_union_agg(character()), as_s2_geography("GEOMETRYCOLLECTION EMPTY") ) expect_wkt_equal( s2_coverage_union_agg(c("POINT (30 10)", NA), na.rm = TRUE), "POINT (30 10)" ) }) test_that("s2_union_agg() works", { expect_wkt_equal(s2_union_agg(c("POINT (30 10)", "POINT EMPTY")), "POINT (30 10)") expect_wkt_equal(s2_union_agg(c("POINT EMPTY", "POINT EMPTY")), "GEOMETRYCOLLECTION EMPTY") # NULL handling expect_identical( s2_union_agg(c("POINT (30 10)", NA), na.rm = FALSE), as_s2_geography(NA_character_) ) expect_wkt_equal( s2_union_agg(character()), as_s2_geography("GEOMETRYCOLLECTION EMPTY") ) expect_wkt_equal( s2_union_agg(c("POINT (30 10)", NA), na.rm = TRUE), "POINT (30 10)" ) # make sure this works on polygons since they are handled differently than # points and linestrings expect_equal( s2_area(s2_union_agg(s2_data_countries())), sum(s2_area(s2_union_agg(s2_data_countries()))) ) # check non-polygons and polygons together points_and_poly <- s2_union_agg( c( s2_data_countries(), s2_data_cities() ) ) points <- s2_rebuild(points_and_poly, options = s2_options(dimensions = "point")) poly <- s2_rebuild(points_and_poly, options = s2_options(dimensions = "polygon")) expect_false(any(s2_intersects(points, poly))) }) test_that("s2_rebuild_agg() works", { expect_wkt_equal(s2_rebuild_agg(c("POINT (30 10)", "POINT EMPTY")), "POINT (30 10)") expect_wkt_equal(s2_rebuild_agg(c("POINT EMPTY", "POINT EMPTY")), "GEOMETRYCOLLECTION EMPTY") # NULL handling expect_identical( s2_coverage_union_agg(c("POINT (30 10)", NA), na.rm = FALSE), as_s2_geography(NA_character_) ) expect_wkt_equal( s2_rebuild_agg(character()), as_s2_geography("GEOMETRYCOLLECTION EMPTY") ) expect_wkt_equal( s2_rebuild_agg(c("POINT (30 10)", NA), na.rm = TRUE), "POINT (30 10)" ) }) test_that("s2_centroid_agg() works", { expect_wkt_equal(s2_centroid_agg(c("POINT (30 10)", "POINT EMPTY")), "POINT (30 10)") expect_wkt_equal(s2_centroid_agg(c("POINT EMPTY", "POINT EMPTY")), "POINT EMPTY") expect_wkt_equal(s2_centroid_agg(c("POINT (0 0)", "POINT (0 10)")), "POINT (0 5)", precision = 15) # NULL handling expect_identical( s2_centroid_agg(c("POINT (30 10)", NA), na.rm = FALSE), as_s2_geography(NA_character_) ) expect_wkt_equal( s2_centroid_agg(c("POINT (30 10)", NA), na.rm = TRUE), "POINT (30 10)" ) }) test_that("s2_snap_to_grid() works", { expect_wkt_equal( s2_as_text(s2_snap_to_grid("POINT (0.333333333333 0.666666666666)", 1e-2)), "POINT (0.33 0.67)", precision = 6 ) }) test_that("s2_buffer() works", { # create a hemisphere! ply <- s2_buffer_cells("POINT (0 0)", distance = pi / 2, radius = 1) expect_near(s2_area(ply, radius = 1), 4 * pi / 2, epsilon = 0.1) }) test_that("s2_simplify() works", { expect_wkt_equal( s2_simplify("LINESTRING (0 0, 0.001 1, -0.001 2, 0 3)", tolerance = 100), "LINESTRING (0 0, 0.001 1, -0.001 2, 0 3)" ) expect_wkt_equal( s2_simplify("LINESTRING (0 0, 0.001 1, -0.001 2, 0 3)", tolerance = 1000), "LINESTRING (0 0, 0 3)" ) }) test_that("s2_rebuild() works", { s2_rebuild("POINT (-64 45)") s2_rebuild("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))") s2_rebuild("GEOMETRYCOLLECTION (POINT (-64 45), LINESTRING (-64 45, 0 0))") # duplicated edges expect_wkt_equal( s2_rebuild("MULTIPOINT (-64 45, -64 45)", options = s2_options(duplicate_edges = FALSE)), "POINT (-64 45)" ) expect_wkt_equal( s2_rebuild("MULTIPOINT (-64 45, -64 45)", options = s2_options(duplicate_edges = TRUE)), "MULTIPOINT (-64 45, -64 45)" ) # crossing edges expect_true( s2_is_collection( s2_rebuild( "LINESTRING (0 -5, 0 5, -5 0, 5 0)", options = s2_options(split_crossing_edges = TRUE) ) ) ) # snap expect_wkt_equal( s2_rebuild( "MULTIPOINT (0.01 0.01, -0.01 -0.01)", options = s2_options( snap = s2_snap_precision(1e1), duplicate_edges = TRUE ) ), "MULTIPOINT ((0 0), (0 0))" ) # snap radius expect_wkt_equal( s2_rebuild( "LINESTRING (0 0, 0 1, 0 2, 0 3)", options = s2_options( snap_radius = 1.5 * pi / 180 ) ), "LINESTRING (0 0, 0 2)" ) # simplify edge chains expect_wkt_equal( s2_rebuild( "LINESTRING (0 0, 0 1, 0 2, 0 3)", options = s2_options( snap_radius = 0.01, simplify_edge_chains = TRUE ) ), "LINESTRING (0 0, 0 3)" ) # validate bad_poly <- s2_geog_from_text( "POLYGON ((0 0, 1.0 0, 1.0 1.0, -0.1 1.0, 1.1 0, 0 0))", check = FALSE ) expect_wkt_equal( s2_rebuild(bad_poly, options = s2_options(validate = FALSE)), bad_poly ) expect_error( s2_rebuild(bad_poly, options = s2_options(validate = TRUE)), "Edge 1 crosses edge 3" ) # polyline type expect_wkt_equal( s2_rebuild( "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)", s2_options(polyline_type = "walk") ), "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)" ) expect_true( s2_is_collection( s2_rebuild( "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)", s2_options(polyline_type = "path") ) ) ) # sibling edge pairs expect_true( s2_is_collection( s2_rebuild( "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)", s2_options(polyline_type = "path", polyline_sibling_pairs = "keep") ) ) ) expect_false( s2_is_collection( s2_rebuild( "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)", s2_options(polyline_type = "path", polyline_sibling_pairs = "discard") ) ) ) # dimension expect_true( s2_is_empty( s2_rebuild( "LINESTRING (0 0, 0 1, 0 2, 0 1, 0 3)", s2_options(dimensions = c("point", "polygon")) ) ) ) }) test_that("real data survives the S2BooleanOperation", { # the 32-bit Solaris build results in some of the roundtripped # edges becoming degenerate. Rather than pass check = FALSE to # as_s2_geography(), just skip this on Solaris skip_on_os("solaris") for (continent in unique(s2::s2_data_tbl_countries$continent)) { # this is primarily a test of the S2BooleanOperation -> Geography constructor unioned <- expect_s3_class(s2_coverage_union_agg(s2_data_countries(continent)), "s2_geography") # this is a test of RGeography::Export() on potentially complex polygons exported <- expect_length(s2_as_binary(unioned), 1) # the output WKB should load as a polygon with oriented = TRUE and result in the # same number of points and similar area reloaded <- s2_geog_from_wkb(exported, oriented = TRUE) expect_equal(s2_num_points(reloaded), s2_num_points(unioned)) expect_equal(s2_area(reloaded, radius = 1), s2_area(unioned, radius = 1)) # also check with oriented = FALSE (may catch quirky nesting) reloaded <- s2_geog_from_wkb(exported, oriented = FALSE) expect_equal(s2_num_points(reloaded), s2_num_points(unioned)) expect_equal(s2_area(reloaded, radius = 1), s2_area(unioned, radius = 1)) } }) test_that("s2_interpolate() and s2_interpolate_normalized() work", { expect_identical( s2_as_text( s2_interpolate_normalized("LINESTRING (0 0, 0 60)", c(0, 0.25, 0.75, 1, NA)), precision = 5 ), c("POINT (0 0)", "POINT (0 15)", "POINT (0 45)", "POINT (0 60)", NA) ) expect_identical( s2_as_text( s2_interpolate("LINESTRING (0 0, 0 60)", c(0, 0.25, 0.75, 1, NA) * pi / 3, radius = 1), precision = 5 ), c("POINT (0 0)", "POINT (0 15)", "POINT (0 45)", "POINT (0 60)", NA) ) expect_error( s2_interpolate_normalized("POINT (0 1)", 1), "must be a polyline" ) expect_error( s2_interpolate_normalized("MULTILINESTRING ((0 1, 1 1), (1 1, 1 2))", 1), "must be a simple geography" ) }) test_that("s2_convex_hull() works", { expect_equal( s2_area(s2_convex_hull( c("GEOMETRYCOLLECTION(POINT(3.6 43.2), POINT (0 0), POINT(3.61 43.21))", NA) )), s2_area(c("POLYGON ((0 0, 3.61 43.21, 3.6 43.2, 0 0))", NA)) ) }) test_that("s2_convex_hull_agg() works", { expect_equal( s2_area(s2_convex_hull_agg(c("POINT(3.6 43.2)", "POINT (0 0)", "POINT(3.61 43.21)"))), s2_area("POLYGON ((0 0, 3.61 43.21, 3.6 43.2, 0 0))") ) expect_equal( s2_area(s2_convex_hull_agg(c( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))"))), s2_area("POLYGON ((0 0, 10 0, 15 5, 15 15, 5 15, 0 10, 0 0))") ) expect_equal( s2_area(s2_convex_hull_agg(c( "POINT (3.6 43.2)", "LINESTRING (3.49 43.05, 3.52 43.1, 3.38 43.2, 3.1 43.1)", "POLYGON ((3.01 43.2, 3.4 44.01, 3.5 43.5, 3.1 43.2, 3.01 43.2))", "GEOMETRYCOLLECTION EMPTY" ))), s2_area( "POLYGON ((3.49 43.05, 3.6 43.2, 3.4 44.01, 3.01 43.2, 3.1 43.1, 3.49 43.05))" ) ) expect_equal( s2_area(s2_convex_hull_agg( "GEOMETRYCOLLECTION(POLYGON ((3.01 43.2, 3.4 44.01, 3.5 43.5, 3.1 43.2, 3.01 43.2)), POINT (3.6 43.2))" )), s2_area(s2_convex_hull_agg( c( "POLYGON ((3.01 43.2, 3.4 44.01, 3.5 43.5, 3.1 43.2, 3.01 43.2))", "POINT (3.6 43.2)" ) )) ) expect_identical( s2_convex_hull_agg(c("POINT (0 0)", NA), na.rm = FALSE), as_s2_geography(NA_character_) ) expect_equal( s2_area(s2_convex_hull_agg(c("POINT (0 0)", NA), na.rm = TRUE)), 0 ) }) s2/tests/testthat/test-s2-cell-union.R0000644000176200001440000001256514530411473017302 0ustar liggesusers test_that("s2_cell_union() class works", { expect_s3_class(s2_cell_union(), "s2_cell_union") expect_s3_class(s2_cell_union(), "wk_vctr") x <- s2_cell_union() expect_identical(as_s2_cell_union(x), x) expect_output(expect_identical(str(x), x), "s2_cell_union") expect_output(expect_identical(print(x), x), "s2_cell_union") expect_identical(unlist(x), s2_cell()) expect_identical(is.na(new_s2_cell_union(list(NULL))), TRUE) }) test_that("s2_cell_union can be roundtripped through character", { expect_identical( as_s2_cell_union(c("3442c;345d5", NA)), s2_cell_union(list(s2_cell(c("3442c", "345d5")), NULL)) ) expect_identical( as.character(s2_cell_union(list(c("3442c", "345d5"), NULL))), c("3442c;345d5", NA) ) }) test_that("as_s2_cell_union() for s2_cell() works", { expect_identical( as_s2_cell_union(s2_cell(c("4b59a0cd83b5de49", NA))), s2_cell_union(list(s2_cell("4b59a0cd83b5de49"), NULL)) ) }) test_that("as_s2_geography() for s2_cell_union works", { union <- as_s2_cell_union(s2_cell(c("4b59a0cd83b5de49", NA))) geog <- as_s2_geography(union) expect_identical( s2_intersects(geog, s2_lnglat(c(-64, NA), c(45, NA))), c(TRUE, NA) ) expect_identical(s2_dimension(geog), c(2L, NA)) }) test_that("s2_cell_union_normalize() works", { cell <- s2_cell_parent(as_s2_cell("4b59a0cd83b5de49"), 10) children <- s2_cell_union(list(s2_cell_child(cell, 0:3))) expect_identical( s2_cell_union_normalize(children), as_s2_cell_union(cell) ) expect_identical( s2_cell_union_normalize(new_s2_cell_union(list(NULL))), new_s2_cell_union(list(NULL)) ) }) test_that("s2_cell_union_contains() works", { cell_na <- s2_cell_union(list(NULL)) cell <- s2_cell_parent(as_s2_cell("4b59a0cd83b5de49"), 10) children <- as_s2_cell_union(s2_cell_child(cell, 0:3)) expect_identical( s2_cell_union_contains(cell, c(children, cell_na)), c(rep(TRUE, 4), NA) ) expect_identical( s2_cell_union_contains(cell_na, children), rep(NA, 4) ) expect_identical( s2_cell_union_contains(c(children, cell_na), cell), c(rep(FALSE, 4), NA) ) expect_identical( s2_cell_union_contains(children, cell_na), rep(NA, 4) ) expect_error( s2_cell_union_contains(children, c(s2_cell_union(cell), cell_na)), "Can't recycle vectors" ) }) test_that("s2_cell_union_contains() works for cell y", { cell_na <- s2_cell(NA) cell <- s2_cell_parent(as_s2_cell("4b59a0cd83b5de49"), 10) children <- s2_cell_child(cell, 0:3) expect_identical( s2_cell_union_contains(cell, c(children, cell_na)), c(rep(TRUE, 4), NA) ) expect_identical( s2_cell_union_contains(cell_na, children), rep(NA, 4) ) expect_identical( s2_cell_union_contains(c(children, cell_na), cell), c(rep(FALSE, 4), NA) ) expect_identical( s2_cell_union_contains(children, cell_na), rep(NA, 4) ) expect_error( s2_cell_union_contains(children, c(children, cell_na)), "Incompatible lengths" ) }) test_that("s2_cell_union_intersects() works", { cell_na <- s2_cell_union(list(NULL)) cell <- s2_cell_parent(as_s2_cell("4b59a0cd83b5de49"), 10) children <- as_s2_cell_union(s2_cell_child(cell, 0:3)) expect_identical( s2_cell_union_intersects(cell, c(children, cell_na)), c(rep(TRUE, 4), NA) ) expect_identical( s2_cell_union_intersects(cell_na, children), rep(NA, 4) ) expect_identical( s2_cell_union_intersects(c(children, cell_na), cell), c(rep(TRUE, 4), NA) ) expect_identical( s2_cell_union_intersects(children, cell_na), rep(NA, 4) ) expect_error( s2_cell_union_intersects(children, c(s2_cell_union(cell), cell_na)), "Can't recycle vectors" ) }) test_that("s2_cell_union_intersection|difference|union() works", { cell <- s2_cell_parent(as_s2_cell("4b59a0cd83b5de49"), 10) children <- as_s2_cell_union(s2_cell_child(cell, 0:3)) expect_identical( s2_cell_union_intersection(cell, children[1]), children[1] ) expect_identical( s2_cell_union_difference(cell, children[1]), s2_cell_union(list(unlist(children[2:4]))) ) expect_identical( s2_cell_union_union( s2_cell_union(list(unlist(children[1:2]))), s2_cell_union(list(unlist(children[3:4]))) ), s2_cell_union(cell) ) }) test_that("s2_covering_cell_ids() works", { expect_length(unlist(s2_covering_cell_ids(s2_data_countries("France"))), 8) expect_length( unlist(s2_covering_cell_ids(s2_data_countries("France"), max_cells = 4)), 4 ) expect_length( unlist(s2_covering_cell_ids(s2_data_countries("France"), interior = TRUE)), 8 ) expect_identical(s2_covering_cell_ids(NA_character_), new_s2_cell_union(list(NULL))) }) test_that("s2_covering_cell_ids_agg() works", { geog <- s2_data_countries(c("France", "Germany")) coverings <- s2_covering_cell_ids(geog) coverings_agg <- s2_covering_cell_ids_agg(geog) expect_length(unlist(coverings_agg), 8) coverings_interior_agg <- s2_covering_cell_ids_agg(geog, interior = TRUE) expect_length(unlist(coverings_interior_agg), 8) expect_identical( s2_covering_cell_ids_agg(NA_character_, na.rm = FALSE), new_s2_cell_union(list(NULL)) ) expect_identical( s2_covering_cell_ids_agg(character(), radius = NA_real_), new_s2_cell_union(list(NULL)) ) expect_identical( s2_covering_cell_ids_agg(NA_character_, na.rm = TRUE), new_s2_cell_union(list(s2_cell())) ) }) s2/tests/testthat/test-s2-lnglat.R0000644000176200001440000000165414530411473016513 0ustar liggesusers test_that("s2_lnglat objects can be created from and converted back to R objects", { # in expect_s3_class(s2_lnglat(45, 64), "wk_xy") expect_length(s2_lnglat(45, 64), 1) expect_s3_class(as_s2_lnglat(matrix(c(45, 64), ncol = 2)), "wk_xy") lnglat <- s2_lnglat(45, 64) expect_identical(as_s2_lnglat(lnglat), lnglat) expect_identical( as_s2_lnglat(s2_point(1, 0, 0)), s2_lnglat(0, 0) ) expect_identical( as_s2_lnglat(s2_point(NaN, NaN, NaN)), s2_lnglat(NaN, NaN) ) }) test_that("s2_lnglat can be imported from s2_geography", { expect_equal( as_s2_lnglat(as_s2_geography("POINT (-64 45)")), s2_lnglat(-64, 45) ) }) test_that("s2_lnglat can be imported from wkb", { wkb_point <- wk::as_wkb("POINT (-64 45)") expect_equal( as_s2_lnglat(wkb_point), s2_lnglat(-64, 45) ) }) test_that("s2_lnglat objects can be printed", { expect_output(print(s2_lnglat(-64, 45)), "OGC:CRS84") }) s2/tests/testthat/test-s2-constructors-formatters.R0000644000176200001440000001244214530411473022163 0ustar liggesusers test_that("s2_geog_point() works", { expect_wkt_equal(s2_geog_point(-64, 45), "POINT (-64 45)") }) test_that("s2_make_line() works", { expect_wkt_equal( s2_make_line(c(-64, 8), c(45, 71)), "LINESTRING (-64 45, 8 71)" ) # check separation using feature_id expect_wkt_equal( s2_make_line( c(-64, 8, -27, -27), c(45, 71, 0, 45), feature_id = c(1, 1, 2, 2) ), c("LINESTRING (-64 45, 8 71)", "LINESTRING (-27 0, -27 45)") ) }) test_that("s2_make_polygon() works", { expect_wkt_equal( s2_make_polygon(c(-45, 8, 0), c(64, 71, 90)), "POLYGON ((-45 64, 8 71, 0 90, -45 64))" ) # check that loops can be open or closed expect_wkt_equal( s2_make_polygon(c(-45, 8, 0, -45), c(64, 71, 90, 64)), "POLYGON ((-45 64, 8 71, 0 90, -45 64))" ) # check feature/ring separation expect_wkt_equal( s2_make_polygon( c(20, 10, 10, 30, 45, 30, 20, 20, 40, 20, 45), c(35, 30, 10, 5, 20, 20, 15, 25, 40, 45, 30), feature_id = c(rep(1, 8), rep(2, 3)), ring_id = c(1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1) ), c( "POLYGON ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20))", "POLYGON ((40 40, 20 45, 45 30, 40 40))" ) ) }) test_that("s2_geog_from_wkt() works", { expect_wkt_equal(s2_geog_from_text("POINT (-64 45)"), "POINT (-64 45)") }) test_that("s2_geog_from_wkb() works", { expect_wkt_equal(s2_geog_from_wkb(as_wkb("POINT (-64 45)")), "POINT (-64 45)") }) test_that("s2_as_text() works", { expect_identical( s2_as_text("POINT (0.1234567890123456 0.1234567890123456)"), "POINT (0.1234567890123456 0.1234567890123456)" ) }) test_that("s2_as_binary() works", { expect_identical( s2_as_binary("POINT (0 0)", endian = 1), structure( list( as.raw( c(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ) ) ), class = "blob" ) ) }) test_that("s2_as_binary works on (multi)polygons", { geog <- s2_data_countries() wkb <- s2_as_binary(geog) expect_identical( sum(vapply(wkb, length, integer(1))), 173318L ) expect_identical(length(wkb), length(geog)) }) test_that("polygon constructors respect oriented and check arguments", { polygon_with_bad_hole_wkt <- "POLYGON ( (20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 25, 20 15, 30 20) )" polygon_with_bad_hole_wkb <- wk::as_wkb(polygon_with_bad_hole_wkt) polygon_with_bad_hole_df <- data.frame( x = c(20, 10, 10, 30, 45, 30, 20, 20), y = c(35, 30, 10, 5, 20, 20, 25, 15), ring_id = c(1, 1, 1, 1, 1, 2, 2, 2) ) expect_false( s2_intersects( s2_geog_from_text(polygon_with_bad_hole_wkt, oriented = FALSE), "POINT (23 19.5)" ) ) expect_false( s2_intersects( s2_geog_from_wkb(polygon_with_bad_hole_wkb, oriented = FALSE), "POINT (23 19.5)" ) ) expect_false( s2_intersects( with( polygon_with_bad_hole_df, s2_make_polygon(x, y, ring_id = ring_id, oriented = FALSE) ), "POINT (23 19.5)" ) ) expect_error( s2_intersects( s2_geog_from_text(polygon_with_bad_hole_wkt, oriented = TRUE, check = TRUE), "POINT (23 19.5)" ), "Inconsistent loop orientations" ) expect_error( s2_intersects( s2_geog_from_wkb(polygon_with_bad_hole_wkb, oriented = TRUE, check = TRUE), "POINT (23 19.5)" ), "Inconsistent loop orientations" ) expect_error( s2_intersects( with( polygon_with_bad_hole_df, s2_make_polygon(x, y, ring_id = ring_id, oriented = TRUE, check = TRUE) ), "POINT (23 19.5)" ), "Inconsistent loop orientations" ) expect_silent( s2_intersects( s2_geog_from_text(polygon_with_bad_hole_wkt, oriented = TRUE, check = FALSE), "POINT (23 19.5)" ) ) expect_silent( s2_intersects( s2_geog_from_wkb(polygon_with_bad_hole_wkb, oriented = TRUE, check = FALSE), "POINT (23 19.5)" ) ) expect_silent( s2_intersects( with( polygon_with_bad_hole_df, s2_make_polygon(x, y, ring_id = ring_id, oriented = TRUE, check = FALSE) ), "POINT (23 19.5)" ) ) }) test_that("planar = TRUE works for s2_geog_from_text()", { geog_wkt <- "LINESTRING (-64 45, 0 45)" geog <- s2_geog_from_text(geog_wkt, planar = TRUE) expect_true(s2_distance(geog, "POINT (-30 45)") < s2_tessellate_tol_default()) }) test_that("planar = TRUE works for s2_geog_from_wkb()", { geog_wkb <- wk::as_wkb("LINESTRING (-64 45, 0 45)") geog <- s2_geog_from_wkb(geog_wkb, planar = TRUE) expect_true(s2_distance(geog, "POINT (-30 45)") < s2_tessellate_tol_default()) }) test_that("planar = TRUE works for s2_as_text()", { # cells very specifically have geodesic edges geog <- s2_cell_polygon(s2_cell_parent(as_s2_cell(s2_lnglat(-64, 45)), 4)) expect_identical(s2_num_points(geog), 4L) out <- s2_as_text(geog, planar = TRUE) expect_true(s2_num_points(out) > s2_num_points(geog)) }) test_that("planar = TRUE works for s2_geog_from_text()", { geog <- s2_cell_polygon(s2_cell_parent(as_s2_cell(s2_lnglat(-64, 45)), 4)) expect_identical(s2_num_points(geog), 4L) out <- s2_as_binary(geog, planar = TRUE) expect_true(s2_num_points(out) > s2_num_points(geog)) }) s2/tests/testthat/test-wk-utils.R0000644000176200001440000001666414530411473016476 0ustar liggesusers test_that("wk_handle() for s2_geography works", { for (name in names(s2_data_example_wkt)) { geog <- wk::wk_handle( s2_data_example_wkt[[name]], s2_geography_writer() ) geog2 <- wk::wk_handle( geog, s2_geography_writer(check = TRUE, oriented = TRUE) ) expect_equal(wk::wk_coords(geog), wk::wk_coords(geog2)) } }) test_that("wk_handle() for s2_geography works for s2_point projection", { for (name in names(s2_data_example_wkt)) { geog <- wk::wk_handle( s2_data_example_wkt[[name]], s2_geography_writer() ) geog2 <- wk::wk_handle( geog, s2_geography_writer( check = TRUE, oriented = TRUE, projection = NULL ), s2_projection = NULL ) expect_identical(wk::wk_coords(geog), wk::wk_coords(geog2)) } }) test_that("wk_writer() works for s2_geography()", { expect_s3_class(wk::wk_writer(s2_geography()), "s2_geography_writer") }) test_that("the s2_geography_writer() works for example WKT", { # nc has some rings that get reordered by this operation for (name in setdiff(names(s2_data_example_wkt), "nc")) { geog <- wk::wk_handle( s2_data_example_wkt[[name]], s2_geography_writer() ) expect_equal( wk::wk_coords(as_wkt(geog))[c("x", "y")], wk::wk_coords(s2_data_example_wkt[[name]])[c("x", "y")] ) } }) test_that("wk_handle() works for example WKT", { for (name in names(s2_data_example_wkt)) { geog <- wk::wk_handle( s2_data_example_wkt[[name]], s2_geography_writer() ) expect_wkt_equal( wk_handle(geog, s2_geography_writer()), geog, precision = 14 ) } }) test_that("wk_handle() works for example WKT with tessellation", { for (name in names(s2_data_example_wkt)) { geog <- wk::wk_handle( s2_data_example_wkt[[name]], s2_geography_writer() ) expect_wkt_equal( # use a big but non-infinite number to trigger the tessellator wk_handle(geog, s2_geography_writer(), s2_tessellate_tol = 1e10), geog, precision = 14 ) } }) test_that("the s2_trans_point() and s2_trans_lnglat() work", { lng_lats <- s2_lnglat(-179:179, 45) points <- as_s2_point(lng_lats) expect_identical(as_s2_lnglat(lng_lats), lng_lats) expect_equal( wk::wk_transform(lng_lats, s2_trans_point()), wk::wk_set_crs(points, NULL) ) expect_equal( wk::wk_transform(points, s2_trans_lnglat()), wk::wk_set_crs(lng_lats, NULL) ) }) test_that("s2_geography_writer() with tesselate_tol works", { # using big examples here, so use a tolerance of 100 km (forces # adding at least one point) tol <- 100000 / s2_earth_radius_meters() expect_equal( wk::as_xy( wk::wk_handle( wk::xy(0, 0), s2_geography_writer(tessellate_tol = tol) ) ), wk::xy(0, 0, crs = wk::wk_crs_longlat()) ) expect_identical( wk::wk_handle( wk::wkt("LINESTRING (0 0, 0 45, -60 45)"), s2_geography_writer(tessellate_tol = tol) ) %>% s2_num_points(), 6L ) expect_identical( wk::wk_handle( wk::wkt("POLYGON ((0 0, 0 45, -60 45, 0 0))"), s2_geography_writer(tessellate_tol = tol) ) %>% s2_num_points(), 8L ) }) test_that("s2_geography_writer() with tesselate_tol works with real data", { tol <- 1000 / s2_earth_radius_meters() countries_tes <- wk::wk_handle( s2::s2_data_tbl_countries$geometry, s2_geography_writer(tessellate_tol = tol) ) expect_true( sum(s2_num_points(countries_tes)) > sum(s2_num_points(s2_data_countries())) ) }) test_that("wk_handle + tessellate_tol works", { tol <- 100000 / s2_earth_radius_meters() expect_equal( wk::wk_handle( as_s2_geography(s2_lnglat(0, 0)), wk::xy_writer(), s2_tessellate_tol = tol ), wk::xy(0, 0) ) expect_identical( wk::wk_handle( as_s2_geography("LINESTRING (0 0, 0 45, -60 45)"), s2_geography_writer(), s2_tessellate_tol = tol ) %>% s2_num_points(), 6L ) expect_identical( wk::wk_handle( as_s2_geography("POLYGON ((0 0, 0 45, -60 45, 0 0))"), s2_geography_writer(), s2_tessellate_tol = tol ) %>% s2_num_points(), 8L ) }) test_that("s2_geography_writer() with tesselate_tol works with real data", { tol <- 1000 / s2_earth_radius_meters() countries <- s2_data_countries() countries_tes <- wk::wk_handle( countries, s2_geography_writer(check = FALSE), s2_tessellate_tol = tol ) expect_true( sum(s2_num_points(countries_tes)) > sum(s2_num_points(s2_data_countries())) ) }) test_that("wk_handle() for s2_geography works with s2_projection_mercator()", { # sf::sf_project("EPSG:4326", "EPSG:3857", wk::xy(30, 10)) %>% dput() geog <- wk::wk_handle( wk::xy(3339584.72379821, 1118889.97485796), s2_geography_writer(projection = s2_projection_mercator()) ) expect_equal( wk::wk_handle( geog, wk::xy_writer(), s2_projection = s2_projection_mercator() ), wk::xy(3339584.72379821, 1118889.97485796) ) expect_equal( wk::wk_handle( geog, wk::xy_writer(), s2_projection = s2_projection_mercator(), s2_tessellate_tol = 1e10 ), wk::xy(3339584.72379821, 1118889.97485796) ) }) test_that("s2_geography_writer() works with s2_projection_mercator()", { # sf::sf_project("EPSG:4326", "EPSG:3857", wk::xy(30, 10)) %>% dput() expect_equal( wk::as_xy( wk::wk_handle( wk::xy(3339584.72379821, 1118889.97485796), s2_geography_writer(projection = s2_projection_mercator()) ) ), wk::xy(30, 10, crs = wk::wk_crs_longlat()) ) }) test_that("wk_handle() for s2_geography works with s2_projection_orthographic()", { geog <- as_s2_geography(c("POINT (0 0)", "POINT (0 45)", "POINT (45 0)")) result <- wk::wk_handle( geog, wk::xy_writer(), s2_projection = s2_projection_orthographic() ) expect_equal( result, wk::xy( c(0, 0, sqrt(2) / 2), c(0, sqrt(2) / 2, 0) ) ) }) test_that("orthographic projection maintains 'north up' orientation", { result_coords <- wk::wk_coords( as_s2_geography(s2_lnglat(-64, c(45, 50))), s2_projection = s2_projection_orthographic(s2_lnglat(-64, 45)) ) expect_equal(result_coords$x[1], 0) expect_equal(result_coords$y[1], 0) expect_equal(result_coords$x[1], result_coords$x[2]) # proj_result <- sf::sf_project( # "EPSG:4326", # "+proj=ortho +lon_0=-64 +lat_0=45 +ellips=sphere", # s2::s2_lnglat(-64, c(45, 50)) # ) / 6370997 # result_coords <- wk::wk_coords(wk::as_xy(proj_result)) }) test_that("s2_geography_writer() works with s2_projection_mercator()", { # sf::sf_project("EPSG:4326", "EPSG:3857", wk::xy(30, 10)) %>% dput() xy <- wk::xy( c(0, 0, sqrt(2) / 2), c(0, sqrt(2) / 2, 0) ) geog <- wk::wk_handle( xy, s2_geography_writer(projection = s2_projection_orthographic()) ) expect_identical( s2_as_text(geog, precision = 5), c("POINT (0 0)", "POINT (0 45)", "POINT (45 0)") ) }) test_that("s2_hemisphere() works", { expect_equal( s2_area(s2_hemisphere(s2_lnglat(0, 0)), radius = 1), 2 * pi ) }) test_that("s2_world_plate_carree() works", { world0 <- s2_world_plate_carree(0, 0) expect_identical( wk::wk_bbox(wk::wkt(s2_as_text(world0))), wk::rct(-180, -90, 180, 90) ) world_eps <- s2_world_plate_carree(1, 2) expect_identical( wk::wk_bbox(wk::wkt(s2_as_text(world_eps))), wk::rct(-179, -88, 179, 88) ) }) s2/tests/testthat/test-s2-earth.R0000644000176200001440000000021714530411473016327 0ustar liggesusers test_that("s2_earth_radius_meters works", { expect_type(s2_earth_radius_meters(), "double") expect_length(s2_earth_radius_meters(), 1) }) s2/tests/testthat/test-s2-accessors.R0000644000176200001440000001712114530411473017213 0ustar liggesusers test_that("s2_is_collection works", { expect_identical(s2_is_collection(NA_character_), NA) expect_false(s2_is_collection("POINT (-64 45)")) expect_false(s2_is_collection("POINT EMPTY")) expect_true(s2_is_collection("MULTIPOINT ((0 0), (1 1))")) expect_false(s2_is_collection("LINESTRING (0 0, 1 1)")) expect_true(s2_is_collection("MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))")) expect_false(s2_is_collection("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))")) expect_true( s2_is_collection("MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)) )") ) }) test_that("s2_is_valid() works", { expect_identical( s2_is_valid( c( # valid "POINT (0 1)", "LINESTRING (0 0, 1 1)", "POLYGON ((0 0, 0 1, 1 0, 0 0))", "GEOMETRYCOLLECTION (POINT (0 1))", # (for the purposes of S2, linestrings that cross aren't considered invalid # in the sense that they won't cause errors when you try to pass them to # a boolean operation or the builder) # invalid "LINESTRING (0 0, 0 0, 1 1)", "POLYGON ((0 0, 0 1, 1 0, 0 0, 0 0))", "GEOMETRYCOLLECTION (POINT (0 1), POLYGON ((0 0, 0 1, 1 0, 0 0, 0 0)))", NA ) ), c(TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, NA) ) }) test_that("s2_is_valid_detail() works", { expect_identical( s2_is_valid_detail( c( # valid "POINT (0 1)", "LINESTRING (0 0, 1 1)", "POLYGON ((0 0, 0 1, 1 0, 0 0))", "GEOMETRYCOLLECTION (POINT (0 1))", # (for the purposes of S2, linestrings that cross aren't considered invalid # in the sense that they won't cause errors when you try to pass them to # a boolean operation or the builder) # invalid "LINESTRING (0 0, 0 0, 1 1)", "POLYGON ((0 0, 0 1, 1 0, 0 0, 0 0))", "GEOMETRYCOLLECTION (POINT (0 1), POLYGON ((0 0, 0 1, 1 0, 0 0, 0 0)))", NA ) ), data.frame( is_valid = c(TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, NA), reason = c( NA, NA, NA, NA, "Vertices 0 and 1 are identical", "Loop 0: Edge 3 is degenerate (duplicate vertex)", "Loop 0: Edge 3 is degenerate (duplicate vertex)", NA ), stringsAsFactors = FALSE ) ) }) test_that("s2_dimension works", { expect_identical(s2_dimension(NA_character_), NA_integer_) expect_identical(s2_dimension("POINT EMPTY"), 0L) expect_identical(s2_dimension("LINESTRING EMPTY"), 1L) expect_identical(s2_dimension("POLYGON EMPTY"), 2L) }) test_that("s2_num_points works", { expect_identical(s2_num_points(NA_character_), NA_integer_) expect_identical(s2_num_points("POINT (-64 45)"), 1L) expect_identical(s2_num_points("POINT EMPTY"), 0L) expect_identical(s2_num_points("LINESTRING (0 0, 1 1)"), 2L) expect_identical(s2_num_points("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"), 4L) expect_identical( s2_num_points("GEOMETRYCOLLECTION (POINT (0 1), LINESTRING (0 0, 1 1))"), 3L ) }) test_that("s2_is_empty works", { expect_identical(s2_is_empty(NA_character_), NA) expect_false(s2_is_empty("POINT (-64 45)")) expect_true(s2_is_empty("POINT EMPTY")) expect_false(s2_is_empty("LINESTRING (0 0, 1 1)")) expect_true(s2_is_empty("LINESTRING EMPTY")) expect_false(s2_is_empty("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))")) expect_true(s2_is_empty("POLYGON EMPTY")) }) test_that("s2_area works", { expect_identical(s2_area(NA_character_), NA_real_) expect_identical(s2_area("POINT (-64 45)"), 0) expect_identical(s2_area("POINT EMPTY"), 0) expect_identical(s2_area("LINESTRING (0 0, 1 1)"), 0) expect_identical(s2_area("POLYGON EMPTY"), 0) expect_identical(s2_area("POLYGON ((0 0, 90 0, 0 90, 0 0))", radius = 1), 4 * pi / 8) # make sure the radius is squared! expect_true( abs(s2_area("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 180 / pi) - 100) < 0.27 ) expect_identical( s2_area("POLYGON ((0 0, 90 0, 0 90, 0 0))"), s2_area("GEOMETRYCOLLECTION(POLYGON ((0 0, 90 0, 0 90, 0 0)))") ) }) test_that("s2_length works", { expect_identical(s2_length(NA_character_), NA_real_) expect_identical(s2_length("POINT (-64 45)"), 0) expect_identical(s2_length("POINT EMPTY"), 0) expect_identical(s2_length("LINESTRING EMPTY"), 0) expect_identical(s2_length("LINESTRING (0 0, 0 1)", radius = 180 / pi), 1) expect_identical(s2_length("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"), 0) }) test_that("s2_perimeter works", { expect_identical(s2_perimeter(NA_character_), NA_real_) expect_identical(s2_perimeter("POINT (-64 45)"), 0) expect_identical(s2_perimeter("POINT EMPTY"), 0) expect_identical(s2_perimeter("LINESTRING EMPTY"), 0) expect_identical(s2_perimeter("LINESTRING (0 0, 1 1)"), 0) # there is some error here because of the way this is calculated involves # some round-tripping through lat/lon expect_true( abs(s2_perimeter("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", radius = 180 / pi) - 40) < 0.155 ) }) test_that("s2_x and s2_y works", { expect_identical(s2_x(NA_character_), NA_real_) expect_identical(s2_y(NA_character_), NA_real_) expect_equal(s2_x("POINT (-64 45)"), -64) expect_equal(s2_y("POINT (-64 45)"), 45) expect_identical(s2_x("POINT EMPTY"), NaN) expect_identical(s2_y("POINT EMPTY"), NaN) expect_error(s2_x("LINESTRING EMPTY"), "Can't compute") expect_error(s2_y("LINESTRING EMPTY"), "Can't compute") expect_error(s2_x("POLYGON EMPTY"), "Can't compute") expect_error(s2_y("POLYGON EMPTY"), "Can't compute") }) test_that("s2_project() and s2_project_normalized() work", { expect_equal( s2_project( "LINESTRING (0 0, 0 90)", c("POINT (0 0)", "POINT (0 22.5)", "POINT (0 67.5)", "POINT (0 90)", NA), radius = 1 ), c(0, 0.25, 0.75, 1, NA_real_) * pi / 2 ) expect_equal( s2_project_normalized( "LINESTRING (0 0, 0 90)", c("POINT (0 0)", "POINT (0 22.5)", "POINT (0 67.5)", "POINT (0 90)", "POINT EMPTY", NA) ), c(0, 0.25, 0.75, 1, NaN, NA_real_) ) expect_identical( s2_project_normalized("POINT (0 1)", "POINT (0 1)"), NaN ) expect_identical( s2_project_normalized("LINESTRING (0 1, 1 1)", "LINESTRING (0 1, 1 1)"), NaN ) expect_identical( s2_project_normalized("LINESTRING (0 1, 1 1)", "MULTIPOINT (0 1, 1 1)"), NaN ) }) test_that("s2_distance works", { expect_equal( s2_distance("POINT (0 0)", "POINT (90 0)", radius = 180 / pi), 90 ) expect_equal( s2_distance("POINT (0 0)", "LINESTRING (90 0, 91 0)", radius = 180 / pi), 90 ) expect_equal( s2_distance("POINT (0 0)", "POLYGON ((90 0, 91 0, 92 1, 90 0))", radius = 180 / pi), 90 ) expect_identical(s2_distance("POINT (0 0)", NA_character_), NA_real_) expect_identical(s2_distance(NA_character_, "POINT (0 0)"), NA_real_) expect_identical(s2_distance("POINT (0 0)", "POINT EMPTY"), NA_real_) expect_identical(s2_distance("POINT EMPTY", "POINT (0 0)"), NA_real_) }) test_that("s2_max_distance works", { expect_equal( s2_max_distance("POINT (0 0)", "POINT (90 0)", radius = 180 / pi), 90 ) expect_equal( s2_max_distance("POINT (0 0)", "LINESTRING (90 0, 91 0)", radius = 180 / pi), 91 ) expect_equal( s2_max_distance("POINT (0 0)", "POLYGON ((90 0, 91 0, 89 1, 90 0))", radius = 180 / pi), 91 ) expect_identical(s2_max_distance("POINT (0 0)", NA_character_), NA_real_) expect_identical(s2_max_distance(NA_character_, "POINT (0 0)"), NA_real_) expect_identical(s2_max_distance("POINT (0 0)", "POINT EMPTY"), NA_real_) expect_identical(s2_max_distance("POINT EMPTY", "POINT (0 0)"), NA_real_) }) s2/tests/testthat/test-data.R0000644000176200001440000000167514530411473015624 0ustar liggesusers test_that("s2_data_country() works", { expect_s3_class(s2_data_countries("Germany"), "s2_geography") expect_length(s2_data_countries("Germany"), 1) expect_s3_class(s2_data_countries("Europe"), "s2_geography") expect_length(s2_data_countries("Europe"), 39) expect_s3_class(s2_data_countries(), "s2_geography") expect_length(s2_data_countries(), 177) }) test_that("s2_data_timezone() works", { expect_s3_class(s2_data_timezones(), "s2_geography") expect_length(s2_data_timezones(), 120) expect_s3_class(s2_data_timezones(-4), "s2_geography") expect_length(s2_data_timezones(-4), 3) expect_s3_class(s2_data_timezones(-15, 15), "s2_geography") expect_length(s2_data_timezones(-15, 15), 120) }) test_that("s2_data_cities() works", { expect_s3_class(s2_data_cities(), "s2_geography") expect_length(s2_data_cities(), 243) expect_s3_class(s2_data_cities("Cairo"), "s2_geography") expect_length(s2_data_cities("Cairo"), 1) }) s2/tests/testthat/test-s2-predicates.R0000644000176200001440000002153014530411473017350 0ustar liggesusers test_that("s2_contains() works", { expect_identical(s2_contains("POINT (0 0)", NA_character_), NA) expect_true(s2_contains("POINT (0 0)", "POINT (0 0)")) expect_false(s2_contains("POINT (0 0)", "POINT (1 1)")) expect_false(s2_contains("POINT (0 0)", "POINT EMPTY")) # make sure model is passed on to at least one binary predicate # in the open model, lines do not contain endpoints (but do contain other points) expect_false(s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0)", s2_options(model = "open"))) expect_true(s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 1)", s2_options(model = "open"))) expect_false(s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0.5)", s2_options(model = "open"))) # semi-open and closed: endpoints are contained expect_true(s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0)", s2_options(model = "semi-open"))) expect_true(s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 1)", s2_options(model = "semi-open"))) expect_false(s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0.5)", s2_options(model = "semi-open"))) expect_true(s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0)", s2_options(model = "closed"))) expect_true(s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 1)", s2_options(model = "closed"))) expect_false(s2_contains("LINESTRING (0 0, 0 1, 1 1)", "POINT (0 0.5)", s2_options(model = "closed"))) }) test_that("s2_covers() and s2_covered_by() work", { expect_true(s2_covers("POINT (0 0)", "POINT (0 0)")) expect_false(s2_covers("POINT (0 0)", "POINT (1 1)")) expect_false(s2_covers("POINT (0 0)", "POINT EMPTY")) expect_true(s2_covers("LINESTRING (0 0, 1 1)", "POINT (0 0)")) expect_false(s2_covers("LINESTRING (0 0, 1 1)", "POINT (-1 -1)")) # make sure line is along a geodesic edge # if the line doesn't contain the endpoints, floating # point math won't always keep it strictly inside or outside # the polygon (hard to predict which way it will go) # (when the model is set such that the boundary of the polygon # is considered 'contained' by it) polygon <- "POLYGON ((0 0, 1 1, 0 1, 0 0))" line <- "LINESTRING (0 0, 1 1)" point <- "POINT (0.5 0.7)" expect_true(s2_covers(polygon, polygon)) expect_false(s2_covers(polygon, line, s2_options(model = "open"))) expect_false(s2_covers(polygon, line, s2_options(model = "semi-open"))) expect_true(s2_covers(polygon, line, s2_options(model = "closed"))) expect_true(s2_covers(polygon, point, s2_options(model = "open"))) expect_true(s2_covers(polygon, point, s2_options(model = "semi-open"))) expect_true(s2_covers(polygon, point, s2_options(model = "closed"))) expect_false(s2_covered_by(polygon, line, s2_options(model = "open"))) expect_false(s2_covered_by(polygon, line, s2_options(model = "semi-open"))) expect_false(s2_covered_by(polygon, line, s2_options(model = "closed"))) expect_true(s2_covered_by(point, polygon, s2_options(model = "open"))) expect_true(s2_covered_by(point, polygon, s2_options(model = "semi-open"))) expect_true(s2_covered_by(point, polygon, s2_options(model = "closed"))) }) test_that("s2_disjoint() works", { expect_identical(s2_disjoint("POINT (0 0)", NA_character_), NA) expect_false(s2_disjoint("POINT (0 0)", "POINT (0 0)")) expect_true(s2_disjoint("POINT (0 0)", "POINT (1 1)")) expect_true(s2_disjoint("POINT (0 0)", "POINT EMPTY")) expect_false(s2_disjoint("LINESTRING (0 0, 1 1)", "POINT (0 0)")) expect_true(s2_disjoint("LINESTRING (0 0, 1 1)", "POINT (-1 -1)")) }) test_that("s2_equals() works", { expect_identical(s2_equals("POINT (0 0)", NA_character_), NA) expect_true(s2_equals("POINT (0 0)", "POINT (0 0)")) expect_true(s2_equals("POINT (0 0)", "POINT (0 0)", s2_options(model = "open"))) expect_true(s2_equals("POINT (0 0)", "POINT (0 0)", s2_options(model = "semi-open"))) expect_true(s2_equals("POINT (0 0)", "POINT (0 0)", s2_options(model = "closed"))) expect_false(s2_equals("POINT (0 0)", "POINT (1 1)")) expect_false(s2_equals("POINT (0 0)", "POINT EMPTY")) expect_true(s2_equals("POINT EMPTY", "POINT EMPTY")) expect_true(s2_equals("LINESTRING (0 0, 1 1)", "LINESTRING (1 1, 0 0)")) expect_false(s2_equals("LINESTRING (0 1, 1 1)", "LINESTRING (1 1, 0 0)")) }) test_that("s2_intersects() works", { expect_identical(s2_intersects("POINT (0 0)", NA_character_), NA) expect_true(s2_intersects("POINT (0 0)", "POINT (0 0)")) expect_false(s2_intersects("POINT (0 0)", "POINT (1 1)")) expect_false(s2_intersects("POINT (0 0)", "POINT EMPTY")) expect_true(s2_intersects("LINESTRING (0 0, 1 1)", "LINESTRING (1 0, 0 1)")) expect_false(s2_intersects("LINESTRING (0 0, 1 1)", "LINESTRING (-2 -2, -1 -1)")) expect_true(s2_intersects("LINESTRING (0 0, 1 1)", "POINT (0 0)")) expect_false(s2_intersects("LINESTRING (0 0, 1 1)", "POINT (0 0)", s2_options(model = "open"))) expect_true(s2_intersects("LINESTRING (0 0, 1 1)", "POINT (0 0)", s2_options(model = "semi-open"))) expect_true(s2_intersects("LINESTRING (0 0, 1 1)", "POINT (0 0)", s2_options(model = "closed"))) polygon = "POLYGON((0 0,1 0,1 1,0 1,0 0))" expect_false(s2_intersects(polygon, "POINT (0 0)")) expect_false(s2_intersects(polygon, "POINT (0 0)", s2_options(model = "open"))) expect_false(s2_intersects(polygon, "POINT (0 0)", s2_options(model = "semi-open"))) expect_true(s2_intersects(polygon, "POINT (0 0)", s2_options(model = "closed"))) }) test_that("s2_intersects_box() works", { expect_error( s2_intersects_box("POINT (-1 -1)", -2, -2, 2, 2, detail = 0), "Can't create polygon" ) expect_true(s2_intersects_box("POINT (0.1 0.1)", 0, 0, 1, 1)) expect_true(s2_intersects_box("POINT (0.1 0.1)", 0, 0, 1, 1)) expect_true(s2_intersects_box("POINT (0.1 0.1)", 0, 0, 1, 1, options = s2_options(model = "open"))) expect_true(s2_intersects_box("POINT (0.1 0.1)", 0, 0, 1, 1, options = s2_options(model = "semi-open"))) expect_true(s2_intersects_box("POINT (0.1 0.1)", 0, 0, 1, 1, options = s2_options(model = "closed"))) expect_false(s2_intersects_box("POINT (1 1)", 0, 0, 1, 1)) expect_false(s2_intersects_box("POINT (0 0)", 0, 0, 1, 1)) expect_false(s2_intersects_box("POINT (0 0)", 0, 0, 1, 1)) expect_false(s2_intersects_box("POINT (0 0)", 0, 0, 1, 1, options = s2_options(model = "open"))) expect_false(s2_intersects_box("POINT (0 0)", 0, 0, 1, 1, options = s2_options(model = "semi-open"))) expect_true(s2_intersects_box("POINT (0 0)", 0, 0, 1, 1, options = s2_options(model = "closed"))) expect_true(s2_intersects_box("POINT (-1 -1)", -2, -2, 2, 2)) expect_false(s2_intersects_box("POINT (-1 -1)", 0, 0, 2, 2)) expect_false(s2_intersects_box("POINT (0 0)", 1, 1, 2, 2)) }) test_that("s2_within() works", { expect_identical(s2_within("POINT (0 0)", NA_character_), NA) expect_true(s2_within("POINT (0 0)", "POINT (0 0)")) expect_false(s2_within("POINT (0 0)", "POINT (1 1)")) expect_false(s2_within("POINT (0 0)", "POINT EMPTY")) expect_false(s2_within("POINT EMPTY", "POINT (0 0)")) expect_true(s2_within("POINT (0 0)", "LINESTRING (0 0, 1 1)", s2_options(model = "semi-open"))) expect_false(s2_within("POINT (0 0)", "LINESTRING (0 0, 1 1)", s2_options(model = "open"))) expect_false(s2_within("POINT (0 0)", "LINESTRING (1 1, 2 2)")) }) test_that("s2_touches() works", { # is inside expect_false(s2_touches("POLYGON ((0 0, 0 1, 1 1, 0 0))", "POINT (0.5 0.75)")) # is outside expect_false(s2_touches("POLYGON ((0 0, 0 1, 1 1, 0 0))", "POINT (-0.5 0.75)")) # is vertex expect_true(s2_touches("POLYGON ((0 0, 0 1, 1 1, 0 0))", "POINT (0 0)")) }) test_that("s2_dwithin() works", { expect_identical(s2_dwithin("POINT (0 0)", NA_character_, 0), NA) expect_true(s2_dwithin("POINT (0 0)", "POINT (90 0)", pi / 2 + 0.01, radius = 1)) expect_false(s2_dwithin("POINT (0 0)", "POINT (90 0)", pi / 2 - 0.01, radius = 1)) expect_false(s2_dwithin("POINT (0 0)", "POINT EMPTY", 0)) expect_true(s2_dwithin("LINESTRING (-45 0, 45 0)", "POINT (0 20)", 21, radius = 180 / pi)) expect_false(s2_dwithin("LINESTRING (-45 0, 45 0)", "POINT (0 20)", 19, radius = 180 / pi)) # check vectorization expect_identical( s2_dwithin("POINT (0 0)", "POINT (90 0)", pi / 2 + c(0.01, -0.01), radius = 1), c(TRUE, FALSE) ) }) test_that("s2_prepared_dwithin() works", { expect_identical(s2_prepared_dwithin("POINT (0 0)", NA_character_, 0), NA) expect_true(s2_prepared_dwithin("POINT (0 0)", "POINT (90 0)", pi / 2 + 0.01, radius = 1)) expect_false(s2_prepared_dwithin("POINT (0 0)", "POINT (90 0)", pi / 2 - 0.01, radius = 1)) expect_false(s2_prepared_dwithin("POINT (0 0)", "POINT EMPTY", 0)) expect_true(s2_prepared_dwithin("LINESTRING (-45 0, 45 0)", "POINT (0 20)", 21, radius = 180 / pi)) expect_false(s2_prepared_dwithin("LINESTRING (-45 0, 45 0)", "POINT (0 20)", 19, radius = 180 / pi)) # check vectorization expect_identical( s2_prepared_dwithin("POINT (0 0)", "POINT (90 0)", pi / 2 + c(0.01, -0.01), radius = 1), c(TRUE, FALSE) ) }) s2/tests/testthat/test-s2-point.R0000644000176200001440000000131214530411473016352 0ustar liggesusers test_that("s2_point objects can be created from and converted back to R objects", { # in expect_s3_class(s2_point(1, 2, 3), "wk_xyz") expect_length(s2_point(1, 2, 3), 1) expect_s3_class(as_s2_point(matrix(c(1, 2, 3), ncol = 3)), "wk_xyz") point <- s2_point(1, 2, 3) expect_identical(as_s2_point(point), point) expect_identical( as_s2_point(s2_lnglat(0, 0)), s2_point(1, 0, 0) ) }) test_that("s2_point can be imported from s2_geography", { expect_equal( as_s2_point(as_s2_geography("POINT (-64 45)")), as_s2_point(as_s2_lnglat(as_s2_geography("POINT (-64 45)"))) ) }) test_that("s2_point objects can be printed", { expect_output(print(s2_point(1, 2, 3)), "s2_point_crs") }) s2/tests/testthat/test-s2-geography.R0000644000176200001440000002053014530411473017211 0ustar liggesusers test_that("s2_geography class works", { expect_s3_class(s2_geography(), "s2_geography") geog <- new_s2_geography(list(NULL)) expect_output(print(geog), "s2_geography") expect_output(str(geog), "s2_geography") expect_identical(as_s2_geography(geog), geog) expect_identical( is.na(as_s2_geography(c("POINT (0 1)", NA_character_))), c(FALSE, TRUE) ) # subset assignment geog2 <- geog geog2[1] <- geog expect_identical(geog2, geog) geog2 <- geog geog2[[1]] <- geog expect_identical(geog2, geog) }) test_that("s2_geography vectors can be put in a data frame", { expect_identical( data.frame(geog = s2_geography()), new_data_frame(list(geog = s2_geography())) ) }) test_that("s2_geography vectors can't have other types of objects concatenated or asssigned", { geog <- new_s2_geography(list(NULL)) expect_s3_class(c(geog, geog), "s2_geography") expect_error(c(geog, wk::wkt()), "Can't combine 'wk_vctr' objects") expect_error(geog[1] <- factor(1), "no applicable method") expect_error(geog[[1]] <- factor(1), "no applicable method") }) test_that("s2_geography vectors can be created from s2_lnglat and s2_point", { expect_wkt_equal(as_s2_geography(s2_lnglat(-64, 45)), "POINT (-64 45)") expect_wkt_equal(as_s2_geography(as_s2_point(s2_lnglat(-64, 45))), "POINT (-64 45)") }) test_that("s2_geography vectors can be created from WKB and WKT", { wkb_point <- wk::as_wkb(wk::wkt("POINT (-64 45)", geodesic = TRUE)) expect_output(print(as_s2_geography(wkb_point)), "POINT \\(-64 45\\)") expect_error( as_s2_geography(wk::as_wkb("LINESTRING (0 0, 1 1)")), "Cartesian wkb\\(\\)" ) # empty, null, and point features are OK expect_identical(as_s2_geography(wk::wkb()), as_s2_geography(character())) expect_identical(as_s2_geography(wk::wkb(list(NULL))), as_s2_geography(NA_character_)) expect_silent(as_s2_geography(wk::as_wkb("POINT (0 1)"))) expect_silent(as_s2_geography(wk::as_wkb("MULTIPOINT (0 1)"))) wkt_point <- wk::as_wkt(wk::wkt("POINT (-64 45)", geodesic = TRUE)) expect_output(print(as_s2_geography(wkt_point)), "POINT \\(-64 45\\)") expect_error( as_s2_geography(wk::wkt("LINESTRING (0 0, 1 1)")), "Cartesian wkt\\(\\)" ) # empty, null, and point features are OK expect_identical(as_s2_geography(wk::wkt()), as_s2_geography(character())) expect_identical(as_s2_geography(wk::wkt(NA_character_)), as_s2_geography(NA_character_)) expect_silent(as_s2_geography(wk::wkt("POINT (0 1)"))) expect_silent(as_s2_geography(wk::wkt("MULTIPOINT (0 1)"))) # also test other classes commonly used to signify WKB or WKT expect_output(print(as_s2_geography(structure(wkb_point, class = "WKB")), "POINT \\(-64 45\\)")) expect_output(print(as_s2_geography(structure(wkb_point, class = "blob")), "POINT \\(-64 45\\)")) }) test_that("s2_geography can be exported to WKB/WKT", { expect_wkt_equal( wk::as_wkb(as_s2_geography("POINT (-64 45)")), wk::as_wkb(wk::wkt("POINT (-64 45)", geodesic = TRUE)), precision = 10 ) expect_wkt_equal( wk::as_wkt(as_s2_geography("POINT (-64 45)")), wk::as_wkt(wk::wkt("POINT (-64 45)", geodesic = TRUE)), precision = 10 ) }) test_that("s2_geography vectors can be created from wkt", { expect_output(print(as_s2_geography("POINT (-64 45)")), "POINT \\(-64 45\\)") expect_output(print(as_s2_geography("POINT EMPTY")), "POINT EMPTY") expect_output( print(as_s2_geography("MULTIPOINT ((-64 45), (30 10))")), "MULTIPOINT \\(\\(-64 45\\), \\(30 10\\)\\)" ) expect_output( print(as_s2_geography("LINESTRING (-64 45, 0 0)")), "LINESTRING \\(-64 45, 0 0\\)" ) expect_output( print(as_s2_geography("LINESTRING EMPTY")), "LINESTRING EMPTY" ) expect_output( print(as_s2_geography("MULTILINESTRING ((-64 45, 0 0), (0 1, 2 3))")), "MULTILINESTRING \\(\\(-64 45, 0 0), \\(0 1, 2 3\\)\\)" ) expect_output(print(as_s2_geography("POLYGON EMPTY"), "POLYGON EMPTY")) expect_output( print(as_s2_geography("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))")), "POLYGON \\(\\(0 0, 10 0, 10 10" ) expect_output( print(as_s2_geography("MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0)))")), "POLYGON \\(\\(0 0, 10 0, 10 10" ) expect_output( print( as_s2_geography("MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)) )") ), "MULTIPOLYGON" ) expect_output( print(as_s2_geography("GEOMETRYCOLLECTION (POINT (-64 45))")), "GEOMETRYCOLLECTION \\(POINT \\(-64 45\\)\\)" ) expect_match( s2_as_text( as_s2_geography( "GEOMETRYCOLLECTION ( POINT (30 10), MULTIPOINT (11 12, 12 13), LINESTRING (40 40, 40 41), MULTILINESTRING ((-10 -12, -12 -13), (-15 -15, -16 -16)), POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)), MULTIPOLYGON (((0 0, -10 0, -10 -10, 0 -10, 0 0))), GEOMETRYCOLLECTION (POINT (-100 0), MULTIPOINT(-101 0, -102 0)) )" ) ), paste0( "GEOMETRYCOLLECTION.*?POINT.*?MULTIPOINT.*?LINESTRING.*?MULTILINESTRING.*?", "POLYGON.*?POLYGON.*?GEOMETRYCOLLECTION.*?POINT.*?MULTIPOINT" ) ) expect_output(print(as_s2_geography("GEOMETRYCOLLECTION EMPTY")), "GEOMETRYCOLLECTION EMPTY") }) test_that("empty points are empty when imported from WKB", { wkb_empty <- wk::as_wkb("POINT EMPTY") expect_true(s2_is_empty(s2_geog_from_wkb(wkb_empty))) }) test_that("nested ring depths are correctly exported", { # polygon with hole expect_match( s2_as_text( as_s2_geography("MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ( (20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20) ) )"), precision = 15 ), "\\(20 35, 10 30, 10 10, 30 5, 45 20, 20 35\\), \\(30 20, 20 15, 20 25" ) # polygon with a hole in a hole! expect_match( s2_as_text( as_s2_geography("MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ( (20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20) ), ((27 21, 21 21, 21 16, 27 21)) )"), precision = 15 ), "\\(\\(27 21, 21 21, 21 16, 27 21\\)\\)\\)" ) }) test_that("polygons with holes are interpreted as such by S2", { expect_true( s2_intersects( "MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ( (20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20), (27 21, 21 21, 21 16, 27 21) ) )", "POINT (23 19.5)" ) ) expect_false( s2_intersects( "MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ( (20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20) ) )", "POINT (23 19.5)" ) ) }) test_that("polygon construction works with oriented = TRUE and oriented = FALSE", { polygon_with_bad_hole_nested <- as_s2_geography("MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ( (20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 25, 20 15, 30 20) ) )", oriented = FALSE) expect_false(s2_intersects(polygon_with_bad_hole_nested, "POINT (23 19.5)")) expect_error( as_s2_geography("MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ( (20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 25, 20 15, 30 20) ) )", oriented = TRUE, check = TRUE), "Inconsistent loop orientations" ) expect_silent( as_s2_geography("MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ( (20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 25, 20 15, 30 20) ) )", oriented = TRUE, check = FALSE) ) }) test_that("Full polygons work", { expect_true(s2_intersects(as_s2_geography(TRUE), "POINT(0 1)")) expect_wkt_equal(s2_difference(as_s2_geography(TRUE), "POINT(0 1)"), "POLYGON ((0 -90, 0 -90))") }) test_that("wk crs and geodesic methods are defined", { geog <- as_s2_geography("POINT (0 0)") expect_identical(wk::wk_crs(geog), wk::wk_crs_longlat()) expect_true(wk::wk_is_geodesic(geog)) expect_identical(wk::wk_set_crs(geog, wk::wk_crs_longlat()), geog) expect_identical(wk::wk_set_geodesic(geog, TRUE), geog) expect_warning( wk::wk_set_crs(geog, "EPSG:32620"), "is not supported" ) expect_error(wk::wk_set_geodesic(geog, FALSE), "Can't set geodesic") }) s2/tests/testthat.R0000644000176200001440000000055414530411473013731 0ustar liggesusers# This file is part of the standard setup for testthat. # It is recommended that you do not modify it. # # Where should you do additional test configuration? # Learn more about the roles of various files in: # * https://r-pkgs.org/tests.html # * https://testthat.r-lib.org/reference/test_package.html#special-files library(testthat) library(s2) test_check("s2") s2/MD50000644000176200001440000013055314645746012011126 0ustar liggesusers9b559271baeabb154815b9e39b522a62 *DESCRIPTION 976861cda1588338545dc222e269f6a0 *NAMESPACE 6ab6416908eb953cd94178099f948460 *NEWS.md 6e30cf9b0b3a9e4629b7b684856b4b07 *R/RcppExports.R 26fbc9df4408ccb165ddbd0cd8577b7e *R/data.R 4ca766e1349ea7baf27d4c8638fcb62b *R/plot.R b783a368a460bcae5d174c575d863e59 *R/s2-accessors.R b218944d59f977684342e52d49ab881f *R/s2-bounds.R 9b93004c163961946245e2ce97d2f27f *R/s2-cell-union.R 0230ea24aa3d0871817eb14e38db5e86 *R/s2-cell.R ed0cadf256cc791b85b392eef085c6f6 *R/s2-constructors-formatters.R 0968515e318a2c7a7372bddad1be6c23 *R/s2-earth.R 9cda6c070b015341cf92f57b9307a770 *R/s2-geography.R cabc1e60f8ac15a3b463f4cd11143df9 *R/s2-lnglat.R 007cb436f26e501361a48b7782062eb6 *R/s2-matrix.R e960477e2e4f889c85831e3d438bfe0f *R/s2-options.R 1bab90d590a0e66114c86739ba0d6ce4 *R/s2-package.R 994a3c25167362ce7175a580d75cc1f6 *R/s2-point.R 1618630f02d0c71a6884c2ef5359db7a *R/s2-predicates.R e7dff1519bc26e311ae3e3f1bb894a69 *R/s2-transformers.R a2bcb714226ffff00165255cd261dcc5 *R/utils.R 1b0b65e06a8b04d12df322094d6a16c2 *R/vctrs.R 542c90500e67cf705cba921a201f5a57 *R/wk-utils.R cab4cb3dc48318d6b30fea904fea6bd8 *R/zzz.R 1c1e19169a122703b3f214ced2d6e9a7 *README.md f61db891eb928123aba4412ca322f508 *build/partial.rdb cc6eebf14a3f516ec74168f3ea416fb2 *cleanup cbac38784589bac27de4468ecf03fb13 *configure d41d8cd98f00b204e9800998ecf8427e *configure.win 885346a0f57b92fd80b41b8afac11bad *data/s2_data_example_wkt.rda e59a2d1d70e5a35ca7a5d9bc17ff9554 *data/s2_data_tbl_cities.rda 044840e4280d77984dc42feb2c3f387c *data/s2_data_tbl_countries.rda 94d2fee6187d451e94b028677e579b00 *data/s2_data_tbl_timezones.rda d41d8cd98f00b204e9800998ecf8427e *inst/extdata/emptyfile 743a5630af573f46d1916e4810419a77 *man/as_s2_geography.Rd ed01c59b91c1a9c3382d1f2ab67e60cc *man/figures/rc300.png 3f1175562994dccc07c261d41a59f9a8 *man/s2-package.Rd 52eef06fb0b570954600e8e619fe9756 *man/s2_boundary.Rd 1ec1e77f9bf12466ef8c50a23beed9ca *man/s2_bounds_cap.Rd b1d9411f69c129c5b1af27f91a1563b7 *man/s2_cell.Rd bb77b7b6b94be80cd6cdf8d8f074d03b *man/s2_cell_is_valid.Rd 79be4874bfb6397f7e4ae40b18df426f *man/s2_cell_union.Rd 71b26a6c7cc913b2dd369afcd744a8f1 *man/s2_cell_union_normalize.Rd d2590d0319581da0f2b40f438374e5ea *man/s2_closest_feature.Rd 673de08c721dbf5fe295811ceb1a6bd6 *man/s2_contains.Rd 7c5dbf9f18b14b1fa00b61ecd5efeccf *man/s2_data_example_wkt.Rd addd614791ada510339317f68202d348 *man/s2_data_tbl_countries.Rd d7a1ee72a69458a577921e02c2f4142f *man/s2_earth_radius_meters.Rd c56c83fe4daade0fd891802084cc54fc *man/s2_geog_point.Rd 9676c31cb07674f1d6935a5b0462f3ca *man/s2_interpolate.Rd 32c6a0bb1280c25f7da7dc9781315d92 *man/s2_is_collection.Rd 85431ef0005937d9132048784c2ac887 *man/s2_lnglat.Rd 43d061044e95b05b97b7cdb01d392708 *man/s2_options.Rd f9c29bc19098f5fc595e260341adeacb *man/s2_plot.Rd 6c76792603f4ce1efc64879d62b59517 *man/s2_point.Rd de7b3d7e917213319c2adcbda8c5bf98 *man/wk_handle.s2_geography.Rd b593c53774e6c0d3fd1542d70263af9f *src/Makevars.in d29bfcbd2d9c20aabd03fe1ecf012e26 *src/Makevars.ucrt 7f0e39e59982689e148a8c55669548e5 *src/Makevars.win d80ed8b93cfdc99fe99541b9e089b76f *src/RcppExports.cpp 642c7be2ce45690aea290338fec0675b *src/absl/algorithm/algorithm.h fd93de75d5eb59a37dc897d538c9e428 *src/absl/algorithm/container.h 8dda102d5b63125edd164fe345e55f9b *src/absl/base/attributes.h daad8899b1d374f22c26521512c3a8bb *src/absl/base/call_once.h 26a0a864fbee53bce7754262cf239314 *src/absl/base/casts.h 43ea4eaaef94e0338dc008826e43fd7a *src/absl/base/config.h f787189f34e273f4314344b53f5ccae9 *src/absl/base/const_init.h 033cd6a3c2de9bf80923f5054e2701e2 *src/absl/base/dynamic_annotations.h 376abfbcc035fb5ddd76fb513589a1e9 *src/absl/base/internal/atomic_hook.h 429261c728bd91fe9a974d2fcf8b8db6 *src/absl/base/internal/cycleclock.cc b921282429acd16526077295c5ac6b6e *src/absl/base/internal/cycleclock.h 45d608f9994f8702730d7d6faef2b390 *src/absl/base/internal/direct_mmap.h 3c6051b3b1895802b42fd070e0739173 *src/absl/base/internal/dynamic_annotations.h f2a2f6524ea5494e7a484254ec99ce78 *src/absl/base/internal/endian.h 1ad6b95fe99ebc367d31ff7a9a8dd1fa *src/absl/base/internal/errno_saver.h 7cb685577c52ecbefba2c3f86f4247eb *src/absl/base/internal/fast_type_id.h bf927fee390f1b62989522868b3104ec *src/absl/base/internal/hide_ptr.h 83cc5f6d80a853694ccd21d49215b38a *src/absl/base/internal/identity.h ca95d2f7ac2c07926c2935004bab2077 *src/absl/base/internal/inline_variable.h 4139c861a8431f96daf9068fcddcb64f *src/absl/base/internal/invoke.h f75b6817d994658a3311ae68b3f695f4 *src/absl/base/internal/low_level_alloc.cc d6ce2c3d0d4abc8a9187e1c0d3917ef6 *src/absl/base/internal/low_level_alloc.h 99efd333efc4cc6a44a9abf4de89b2de *src/absl/base/internal/low_level_scheduling.h cd26ae8fafb346728a1759160d46495e *src/absl/base/internal/per_thread_tls.h c3e7ad34eacc65b541d0aa760ba1599d *src/absl/base/internal/prefetch.h cff9e9f5e90b5e4455317dddbda47753 *src/absl/base/internal/pretty_function.h cd8235040ec57fdc0cf6f85f6d8c752d *src/absl/base/internal/raw_logging.cc 3b3edfd7c70fae6821a0b911f42259a4 *src/absl/base/internal/raw_logging.h bb74d44f7e47edcd1d865adb924db224 *src/absl/base/internal/scheduling_mode.h fb9f075189d06957e1ae00ceefa34ee5 *src/absl/base/internal/scoped_set_env.cc 80dbd6581eea81b45ba532470673e777 *src/absl/base/internal/scoped_set_env.h 4e0f135a07c004bdecd6b414a7c6c546 *src/absl/base/internal/spinlock.cc ac92c2ee2b026885a0fa47e95aeb0988 *src/absl/base/internal/spinlock.h 5e1d7bbdbd633f5f9e26bc6000d7bdc5 *src/absl/base/internal/spinlock_akaros.inc 184a057ae2d2273a6d02f7012585b854 *src/absl/base/internal/spinlock_linux.inc 63f56ff5eab205a2ba5a852dbd6276f2 *src/absl/base/internal/spinlock_posix.inc 3917a541890f75bcb371b26c11491bec *src/absl/base/internal/spinlock_wait.cc 3bc3b1f81150afa98a5b0abc10905f3e *src/absl/base/internal/spinlock_wait.h d3c5fb1ed8db28e9fc573aa2939af254 *src/absl/base/internal/spinlock_win32.inc f04df5e54b2977788e030fb2c44735c3 *src/absl/base/internal/strerror.cc 8c75ee873aa3e514edd1390f3056f89a *src/absl/base/internal/strerror.h b3c6e089ed89e018ffdb93638014292c *src/absl/base/internal/sysinfo.cc 89920866f6792bd96338cb6f9587f3a9 *src/absl/base/internal/sysinfo.h af1f8d88b67b54f7302728488ddb98a0 *src/absl/base/internal/thread_annotations.h a720634e6b2aad1f7627838a08311902 *src/absl/base/internal/thread_identity.cc 675c5b31b96e0ebaa84e739ed8216b32 *src/absl/base/internal/thread_identity.h 94e874964762b519d4e59b796466116b *src/absl/base/internal/throw_delegate.cc c1f9f8e9ee944f3b8a2640096b37f61f *src/absl/base/internal/throw_delegate.h 8c803550a22ccece7660ca6ad9a89560 *src/absl/base/internal/tsan_mutex_interface.h c2045a4b16b8b7efce39c96a88adc6da *src/absl/base/internal/unaligned_access.h 51e2fc783ce386b7754b15fc92a92c81 *src/absl/base/internal/unscaledcycleclock.cc 17285df781bac882a78c1c4baa87d4a8 *src/absl/base/internal/unscaledcycleclock.h 6e85910a100a6c90893d108d2b80a7a1 *src/absl/base/log_severity.cc f0fc3d187005a7cf23c54a12e04ab06c *src/absl/base/log_severity.h 9a7add8647ed73b494f794f18c11270d *src/absl/base/macros.h 7cb6ea6377947be6384172e8e4292fcf *src/absl/base/optimization.h 7553dbe117696c89a6214587a7664f9c *src/absl/base/options.h 30a55e8729c05984884801d5f5fa1422 *src/absl/base/policy_checks.h c942a5d8341ce35b666c8a2e3625aed9 *src/absl/base/port.h 990163d6b22b150b08d9bec43b61bf8b *src/absl/base/thread_annotations.h 4903f7daa7a9b816419e6389bdffbf4a *src/absl/container/btree_map.h d248b7efb25539492e4da9760d0f71ee *src/absl/container/btree_set.h db720952112a075bb8ed68171b681ce1 *src/absl/container/fixed_array.h 1029770c28381ec983f929a950525aed *src/absl/container/flat_hash_map.h 31b4c898d148515c41d65eff48710843 *src/absl/container/flat_hash_set.h 72d3d49a360d5d05518b245d80da2e0b *src/absl/container/inlined_vector.h 0c9fc5d35bb8ce4a2d4b2aef0a065b40 *src/absl/container/internal/btree.h 471f4b576592fa97439abe4c1f4f5af6 *src/absl/container/internal/btree_container.h 04112e121678319b6f74e2c7c4bb2877 *src/absl/container/internal/common.h 013341274845cbce0b85a94bc2984b38 *src/absl/container/internal/compressed_tuple.h 38c85e29bfe2d83b08fb2ee63e84f428 *src/absl/container/internal/container_memory.h a47304f7692364f874a92cce7526a13d *src/absl/container/internal/counting_allocator.h 619244bd2568402acdf114998c58fc52 *src/absl/container/internal/hash_function_defaults.h 2091e3185f7126b826ce099f50f5fdc9 *src/absl/container/internal/hash_policy_traits.h ed69e77d87d7e4c2b42f5248904cd6d6 *src/absl/container/internal/hashtable_debug.h 85a576cfb12917cd7126d9e76e13309b *src/absl/container/internal/hashtable_debug_hooks.h a9026aae4987bd08dd8def14e33574de *src/absl/container/internal/hashtablez_sampler.cc e6a1d3c5965aea1bb0a84e5ce8d3ad08 *src/absl/container/internal/hashtablez_sampler.h fcc5f2abdf1fd0edb18d122a846e84f0 *src/absl/container/internal/hashtablez_sampler_force_weak_definition.cc 0f420d68cbca4e9d9ca1274179d15e8e *src/absl/container/internal/inlined_vector.h dde28d12969cb6df933b96f90b287506 *src/absl/container/internal/layout.h a8db9c2e305ee7ddc082d6d1bcf74103 *src/absl/container/internal/node_slot_policy.h 7256093f3d9e6d880cf9b25295e0c61e *src/absl/container/internal/raw_hash_map.h 80426b9c7f99e24919cdfce7af00df15 *src/absl/container/internal/raw_hash_set.cc 7627e3cd5ecb10ed4ab6ba09a1ad7fee *src/absl/container/internal/raw_hash_set.h b2c91b1fd5a05ea12fd76abd5e7401bc *src/absl/container/internal/tracked.h dede177b23ded734f450d112907a7981 *src/absl/container/node_hash_map.h fa098d67166a0c79cfb38f955d62aa7d *src/absl/container/node_hash_set.h 83dca33b15e6052405cf7737516cc6f1 *src/absl/debugging/failure_signal_handler.cc 33d4e12e60b432bafa7920d2b5fa67c0 *src/absl/debugging/failure_signal_handler.h 88511382784375519a3ef65a5ce247b9 *src/absl/debugging/internal/address_is_readable.cc 91ad1bd868606c791022ba6aa6ef4860 *src/absl/debugging/internal/address_is_readable.h c8e0eb65e6f8010624c5e8b584780240 *src/absl/debugging/internal/demangle.cc de5e466a575d551be06c658446b26ca9 *src/absl/debugging/internal/demangle.h e54662f502407a088f04a6d51708f1e5 *src/absl/debugging/internal/elf_mem_image.cc 352cfa6b3dd2465cc293e610a0e38769 *src/absl/debugging/internal/elf_mem_image.h dc8caa62e93f72fb1c5e4cc1fd6f0c34 *src/absl/debugging/internal/examine_stack.cc 19d1e4c50158c08334756c0fcd65728a *src/absl/debugging/internal/examine_stack.h 76431729cc82622bc53935063b049ef6 *src/absl/debugging/internal/stack_consumption.cc 817879abccfc61d31c97aa15e5b5a8df *src/absl/debugging/internal/stack_consumption.h dc3e0ee0e29049fcd25242c4b14ca920 *src/absl/debugging/internal/stacktrace_aarch64-inl.inc 921fde95b6a08aa52d9cb628bb79c15d *src/absl/debugging/internal/stacktrace_arm-inl.inc 67da7c45a5fe837feb76fcfeff37f28b *src/absl/debugging/internal/stacktrace_config.h d412bbe566473c31ffb571b5df159589 *src/absl/debugging/internal/stacktrace_emscripten-inl.inc 31136485c4ae4719146fe310d09794b3 *src/absl/debugging/internal/stacktrace_generic-inl.inc 4dddddc30c71242db2eff066287537fa *src/absl/debugging/internal/stacktrace_powerpc-inl.inc 8827fe270ce53a56049f0ea9d7d91429 *src/absl/debugging/internal/stacktrace_riscv-inl.inc e7ced4e67444a66063312dc27d7bb17d *src/absl/debugging/internal/stacktrace_unimplemented-inl.inc 36cbcb6faf6816b2fd3db94ed61e077d *src/absl/debugging/internal/stacktrace_win32-inl.inc 4a9fddacc38ff421181282981ad11321 *src/absl/debugging/internal/stacktrace_x86-inl.inc 7528d1aa40e6a6645a8173fcbf8150cd *src/absl/debugging/internal/symbolize.h 66aa580ffd00da806719b6e4fee4fd67 *src/absl/debugging/internal/vdso_support.cc 32da4040ebc4e635623be36e861380d4 *src/absl/debugging/internal/vdso_support.h 944240b259867fbd920a992cb1b5610a *src/absl/debugging/leak_check.cc 803e646bd74bcc61527c082b05a41573 *src/absl/debugging/leak_check.h fdb200b5ff89b0388299a36738915d29 *src/absl/debugging/stacktrace.cc 54920d74c95e9623f986ca3bf9a49c14 *src/absl/debugging/stacktrace.h 82338e576ae2cc4fef71e50f421164cc *src/absl/debugging/symbolize.cc 2a4f1e90fdc9a56d8812afd0f0291876 *src/absl/debugging/symbolize.h e1ff8c65c67e769e4d929d70b4c315a4 *src/absl/debugging/symbolize_darwin.inc f91083e80a5e31074987a0b78f5de558 *src/absl/debugging/symbolize_elf.inc 505df4d563b199a1accb368724eb8dd2 *src/absl/debugging/symbolize_emscripten.inc 37d1355403219402fa4d888e9be56fca *src/absl/debugging/symbolize_unimplemented.inc 59dde77656bded8e7a9c0141349f6230 *src/absl/debugging/symbolize_win32.inc bde9e8a109a26a709ac7f6cd32d26335 *src/absl/functional/any_invocable.h 086310a4f0676fb86df40fcdac96d2a6 *src/absl/functional/bind_front.h f3ea4b26c77278c38b2e7c8d88f0ba2f *src/absl/functional/function_ref.h e62331c1f6ffc3458ada2d1d3dbff3bb *src/absl/functional/internal/any_invocable.h ca707a4761ddef188eefa1c47a49305d *src/absl/functional/internal/front_binder.h acc1da8f94634fb69d04d64af189f7d9 *src/absl/functional/internal/function_ref.h c88a984158d3e1eaf700a703a747d9c0 *src/absl/memory/memory.h a2462b42675608566733fc11ab1dbd40 *src/absl/meta/type_traits.h 7caad1d859f008e728d1a783e2007745 *src/absl/numeric/bits.h fd2f0faf81574b2c86dd29356e120fac *src/absl/numeric/int128.cc 79cc004d6d6bf6bebf584022f33bec9e *src/absl/numeric/int128.h 2128cf980b91df6c0fc6c218d0497761 *src/absl/numeric/int128_have_intrinsic.inc 437ad46e0474b28cd231540fba4a8968 *src/absl/numeric/int128_no_intrinsic.inc cda33272dc55c7d4e85c050048ac3b68 *src/absl/numeric/internal/bits.h b71517976c0230c0f0be9e102124708b *src/absl/numeric/internal/representation.h 97c5bf0389ead2a8d9584b5516e03f30 *src/absl/profiling/internal/exponential_biased.cc 8dd473273bce3e970afd8a2592a00780 *src/absl/profiling/internal/exponential_biased.h 3c781cfebcbab764fa4e56e941e07a74 *src/absl/profiling/internal/periodic_sampler.cc d815a430f4c9ff6f941a741742112bff *src/absl/profiling/internal/periodic_sampler.h 93e67b064bc20858ed6fba23748b7ddf *src/absl/profiling/internal/sample_recorder.h d8fbe682d7f971ad41cf3585695e5b8c *src/absl/strings/ascii.cc b1212449ec5b585102abd5fef8394f88 *src/absl/strings/ascii.h d52529dfcbd429d31cf34475d9345029 *src/absl/strings/charconv.cc e022adbe4ace51d7a2d3a065e30fc511 *src/absl/strings/charconv.h 8eebdbf778e886f261a8990232281e37 *src/absl/strings/cord.cc 6f1377e5033f06d48b32e92c09f97957 *src/absl/strings/cord.h 38bf276145e6768ce7d55bc72c51f4a6 *src/absl/strings/cord_analysis.cc 9eb388a8f95c0a8b6c629e139f5b19a7 *src/absl/strings/cord_analysis.h fc2b8427acd714ecfa2c87c4d653df20 *src/absl/strings/cord_buffer.cc d87e5ed66957627950977d971f80dcba *src/absl/strings/cord_buffer.h 3d3ee945b740992e7977948f64d37abf *src/absl/strings/escaping.cc ca2211cce011d818358126d737a1b960 *src/absl/strings/escaping.h 9d688a5ba34606869bbb97318c6e3583 *src/absl/strings/internal/char_map.h 9ba44e5ca0ab3eaf2eea1c1d1135cbd2 *src/absl/strings/internal/charconv_bigint.cc effc6ae35cf71076cb6d243984ef02db *src/absl/strings/internal/charconv_bigint.h cda5aec99cde64d542c231aec6eecaeb *src/absl/strings/internal/charconv_parse.cc bbb89ffd31b13599454ac66c7fca1021 *src/absl/strings/internal/charconv_parse.h 265c664ad73036c39cd42631e3d56ec2 *src/absl/strings/internal/cord_data_edge.h b2aeb1b3985e3dd0e321c60975686d7c *src/absl/strings/internal/cord_internal.cc 89fc71fb8fc9cb06b7001af62a550023 *src/absl/strings/internal/cord_internal.h d2efb216cd07c01bfa472b8adc633706 *src/absl/strings/internal/cord_rep_btree.cc 9a883463d6431cdc5a0689383884474a *src/absl/strings/internal/cord_rep_btree.h 2143a8c51257e8b4e4b165f155b38a1a *src/absl/strings/internal/cord_rep_btree_navigator.cc 6f6e44ad036456711251cd952f475a00 *src/absl/strings/internal/cord_rep_btree_navigator.h e7ad5632b2aed15ce6f88246a8e40f6a *src/absl/strings/internal/cord_rep_btree_reader.cc d2b0c3364d0d8e4a11288617889bfd60 *src/absl/strings/internal/cord_rep_btree_reader.h 62b2b3eaa89a7fa5bf2f790b61c80437 *src/absl/strings/internal/cord_rep_consume.cc f500a141d4fbada9fe73c9bde8e9b997 *src/absl/strings/internal/cord_rep_consume.h 3c8f9400090931ecc6aed37d19b4214b *src/absl/strings/internal/cord_rep_crc.cc 8a1a2a0db3e9d4ab31dbb86f624277a3 *src/absl/strings/internal/cord_rep_crc.h d701cf5c641280417b0290e8ff177dcd *src/absl/strings/internal/cord_rep_flat.h 160f554c0b0acb5c287ff2fa457f8b5a *src/absl/strings/internal/cord_rep_ring.cc 70ff93c8943bb54f4ec4b7bc03d8e298 *src/absl/strings/internal/cord_rep_ring.h 1b95eb768dc0a65c2aca391ce917168b *src/absl/strings/internal/cord_rep_ring_reader.h 2bb5021298c3f89948deb15c1cc14e67 *src/absl/strings/internal/cordz_functions.cc 7797f5c82bed0f2dd95d9a491164d0cd *src/absl/strings/internal/cordz_functions.h d897bb95f3792816dae12cc99600f0ee *src/absl/strings/internal/cordz_handle.cc 2f4d12d83ff4611f3e8278a08a1f1dbb *src/absl/strings/internal/cordz_handle.h 46b23e9b9e81676d6633ab1edcbef807 *src/absl/strings/internal/cordz_info.cc cceee7615f63027b2da6eead19bbab18 *src/absl/strings/internal/cordz_info.h f20b57c507b149391110f0c4ec58e0eb *src/absl/strings/internal/cordz_sample_token.cc 8804ca3803da6893811636e9fe775b5a *src/absl/strings/internal/cordz_sample_token.h 7e6d750252301ed305689311fc447a6d *src/absl/strings/internal/cordz_statistics.h 406e4a93e3c6af34fe85e6e4ca4ef693 *src/absl/strings/internal/cordz_update_scope.h 4a710c85201ef57bdc282b2003186b29 *src/absl/strings/internal/cordz_update_tracker.h 3b55edcf191a2ece5a8f5071f3b186d2 *src/absl/strings/internal/escaping.cc dd1f4cd55816cd3df569601375e19dd6 *src/absl/strings/internal/escaping.h bbcc2e8ef020b2ce6fd2e17e7e019dd7 *src/absl/strings/internal/memutil.cc 73102cf47e4f94aee5dd43faa699eed5 *src/absl/strings/internal/memutil.h 2fd122662d4524b7e60268ed88de2699 *src/absl/strings/internal/ostringstream.cc f4dd448efcf57c35b09e4ecf12ba595a *src/absl/strings/internal/ostringstream.h 0f79e5539614bead9e8b2899109414f2 *src/absl/strings/internal/pow10_helper.cc a2710593664b501447140371f8f2910b *src/absl/strings/internal/pow10_helper.h d41ed4ac8b5c0439686eee2a437ba24a *src/absl/strings/internal/resize_uninitialized.h 0b127a362be3ed6ce297a9bd6704b6f5 *src/absl/strings/internal/stl_type_traits.h 7e645ceaf6302cb953b278e7bd170b11 *src/absl/strings/internal/str_format/arg.cc bbf7a17d739c9e9388ed9bbb30852ca2 *src/absl/strings/internal/str_format/arg.h 541807e23166b8458e4fe668064abff2 *src/absl/strings/internal/str_format/bind.cc 642c25c2a95404dbfbad403297c4f6e4 *src/absl/strings/internal/str_format/bind.h 25e4d30547e25840e7ade5d5361e4a6c *src/absl/strings/internal/str_format/checker.h f1a8157f63bb446070dac767129701f9 *src/absl/strings/internal/str_format/extension.cc 7b166dea6c1b8683f113e9bfa498896c *src/absl/strings/internal/str_format/extension.h 602d5b81943933d209d9347e2138e031 *src/absl/strings/internal/str_format/float_conversion.cc 1775ec23d1a0b61c9c3cbc2786e20b9d *src/absl/strings/internal/str_format/float_conversion.h 38f00d24ace7d912f9410c77298f9450 *src/absl/strings/internal/str_format/output.cc b9bb4e0ffdab9b7dd39fd2448df38651 *src/absl/strings/internal/str_format/output.h bc2c67e0646b6442d7e594737371a059 *src/absl/strings/internal/str_format/parser.cc da983b71caca5afea2879a1877269726 *src/absl/strings/internal/str_format/parser.h 3982f532a08d74ed1a024162c0752374 *src/absl/strings/internal/str_join_internal.h 31be88550fefe52ab3ede699f1e0ffa7 *src/absl/strings/internal/str_split_internal.h e011f29d010463642a59a736caabdbd1 *src/absl/strings/internal/string_constant.h 58e100deab3a1a5d102f6767a106b023 *src/absl/strings/internal/utf8.cc da238e6d26546310d03ce815a181f9c3 *src/absl/strings/internal/utf8.h c26d8dc3c6998d1e4adb73b7b3e7307d *src/absl/strings/match.cc d78b85f24f13f203df715033ebbedac4 *src/absl/strings/match.h 1ffbf9839fb7e742c2b9b5fc5d2ba8bd *src/absl/strings/numbers.cc 4ce4d257979a3fb096d3eee6c8285449 *src/absl/strings/numbers.h 4ebd95dbd026cacebb04407f77a40998 *src/absl/strings/str_cat.cc 499e4504c480d651cbbb45878ddd07d0 *src/absl/strings/str_cat.h a0e1b0af9234c7d668a9542a31df2bc0 *src/absl/strings/str_format.h 305b931d297cecef7a7a5b1cbc92d183 *src/absl/strings/str_join.h 2b56176c8350695b37d0d810fe705486 *src/absl/strings/str_replace.cc 4cfe52299b13b659b0b79e926310859c *src/absl/strings/str_replace.h 0e0684d40f7ebcfa5e3ee516d602bc1b *src/absl/strings/str_split.cc 85eb91c7dbf8f799c2f6fdc862a9de57 *src/absl/strings/str_split.h 9513d8a3c60cffbd288b9f571af3599e *src/absl/strings/string_view.cc bc6088f45dcc4f91be39442fe9b9c223 *src/absl/strings/string_view.h 202bdeadb019318d35a2141b0bf90ed8 *src/absl/strings/strip.h 317fe715d78ae47d1660f1a600d34fd8 *src/absl/strings/substitute.cc 35662cdc3431d806eafd35cf21ab276c *src/absl/strings/substitute.h e8a0e5ed8343c44e8d414a72596dbde9 *src/absl/synchronization/barrier.cc 104730dc624a422f9e0610e357d1d319 *src/absl/synchronization/barrier.h 7adb87ccae08cfb4f5163c38043307b0 *src/absl/synchronization/blocking_counter.cc 76ca084891876db3ba604bb86c36e714 *src/absl/synchronization/blocking_counter.h bce6fe21f3819cc963d73dcc7b7ce45c *src/absl/synchronization/internal/create_thread_identity.cc 51ffeadf7c798c4c32cd7ad26786b7c3 *src/absl/synchronization/internal/create_thread_identity.h edbc4b6b4c303dd6a1c8856356bbdccf *src/absl/synchronization/internal/futex.h 39446c3969b401efba5b1b875a97ba17 *src/absl/synchronization/internal/graphcycles.cc 98204bc87786a909620b798d82b44258 *src/absl/synchronization/internal/graphcycles.h 0c51cca58de692a4dbe0f95750a20512 *src/absl/synchronization/internal/kernel_timeout.h 4fbf2a922ba2d618cc960c516c6858aa *src/absl/synchronization/internal/per_thread_sem.cc 07ff0b00a159d1e49e78f4953a375749 *src/absl/synchronization/internal/per_thread_sem.h dcd3d301b476895c2de035ae9f4a9c2f *src/absl/synchronization/internal/thread_pool.h cbf5c2ab41f486164b71e940d4df35c3 *src/absl/synchronization/internal/waiter.cc 6b034e180a573148b7512a25340e53fd *src/absl/synchronization/internal/waiter.h 4c313ee4f352a843fecccbd6a4047927 *src/absl/synchronization/mutex.cc 76e58005875696e6a2b00f38b22a9cad *src/absl/synchronization/mutex.h 18f40d74c189e4cb3bade5bbfac62eac *src/absl/synchronization/notification.cc 9e989de0e62bd8e3b804141f78ded6e9 *src/absl/synchronization/notification.h 43151664d8fcbd888e2b320ea6c59d4f *src/absl/time/civil_time.cc 21f77d9a8b12daee08647a79fa2f1c24 *src/absl/time/civil_time.h 5bec957f3a6069a1bbf1747096d51829 *src/absl/time/clock.cc 7669a9d4fb695b4bdf63f2199778b7da *src/absl/time/clock.h f770f6ea46e1f6d5f99d40b058f76a23 *src/absl/time/duration.cc 1029367259d746995cfcc86300cb550e *src/absl/time/format.cc 153fe848b8d395acdc4d7188b6b1f7bb *src/absl/time/internal/cctz/include/cctz/civil_time.h 6061b7a2fb993133e2d77ab90882e4b3 *src/absl/time/internal/cctz/include/cctz/civil_time_detail.h 58a6845478b0a635b7474602a86154e9 *src/absl/time/internal/cctz/include/cctz/time_zone.h 6f945a703ababb99477bff55062287ec *src/absl/time/internal/cctz/include/cctz/zone_info_source.h 65ca6d96e925b084e0d56180aa5fe84d *src/absl/time/internal/cctz/src/civil_time_detail.cc 7d6876b726bdc87d68adb5a3be7523a6 *src/absl/time/internal/cctz/src/time_zone_fixed.cc 3afdc610576df58fccc41a7029f1591b *src/absl/time/internal/cctz/src/time_zone_fixed.h 37a70a5e5c9f72abb36cda2b9dfef5dc *src/absl/time/internal/cctz/src/time_zone_format.cc 977f9a0fc17a72093bc4c8a87784f632 *src/absl/time/internal/cctz/src/time_zone_if.cc 5aaf522801582f1c4e511bd5f47143cc *src/absl/time/internal/cctz/src/time_zone_if.h b9933f8f7ea6ef25b5ca3ff1bf3e0ac9 *src/absl/time/internal/cctz/src/time_zone_impl.cc 5b006ce94d5395cbd204c8eb42aa673a *src/absl/time/internal/cctz/src/time_zone_impl.h b920d1e53b3556ba81f65140c60c5517 *src/absl/time/internal/cctz/src/time_zone_info.cc 39446561973c7f453d351f8dd0bfc947 *src/absl/time/internal/cctz/src/time_zone_info.h 9be74d3b67a28a954fc5f9cb9721e173 *src/absl/time/internal/cctz/src/time_zone_libc.cc 6205d25a90cd16fe6fef686eac98a13b *src/absl/time/internal/cctz/src/time_zone_libc.h baf6b2ca44a23c2ffc57a5185eb28c42 *src/absl/time/internal/cctz/src/time_zone_lookup.cc e54d65914d1f72e412abe32cb94b12df *src/absl/time/internal/cctz/src/time_zone_posix.cc 21a06775679a372f33ac8918b0b03dfe *src/absl/time/internal/cctz/src/time_zone_posix.h de6d3a441d96d5b0632441a939602bd3 *src/absl/time/internal/cctz/src/tzfile.h 09c17fe44b3b7d5eb61c51d642d227c9 *src/absl/time/internal/cctz/src/zone_info_source.cc f510c27e928680c4df82fcd584b13620 *src/absl/time/internal/get_current_time_chrono.inc 921b6cfae31d6e15730d211d301f5bed *src/absl/time/internal/get_current_time_posix.inc 012254523fe4c7e1842f31b277e9bc60 *src/absl/time/internal/zoneinfo.inc 8893c1ef798bfa8c6a3421eafd82d0aa *src/absl/time/time.cc f2405de29587b4932366a36e0ce8c607 *src/absl/time/time.h c5e6e75a58fb6134a7bdad5ff6ef94b4 *src/absl/types/any.h 31416a5f0241128632dde299c016fe61 *src/absl/types/bad_any_cast.cc e57304b355027637c47fa5b4138b5ef0 *src/absl/types/bad_any_cast.h 76faa97137f7f2f4eb70152889e11227 *src/absl/types/bad_optional_access.cc ffd0f2debe86db173721761f28b64480 *src/absl/types/bad_optional_access.h 339cb16d4f7c125ef6f9975e9c70e2fd *src/absl/types/bad_variant_access.cc 63b5f63942914ccdaf91a52ae46b5b8f *src/absl/types/bad_variant_access.h 216ae502b816cc541789db0915e4dd76 *src/absl/types/compare.h 2466c511230ee6497dc26dd3633f7702 *src/absl/types/internal/conformance_aliases.h aa319c2077664d2c39a6bd0813c79c18 *src/absl/types/internal/conformance_archetype.h f5863e36a75e055aee57a18f967c5489 *src/absl/types/internal/conformance_profile.h a87cad8cf13eaebb402020f8d7fe4119 *src/absl/types/internal/conformance_testing_helpers.h 0ceabff37c1e8943c78fc2a10db32e94 *src/absl/types/internal/optional.h 9d178054161b76df535a760be34d6213 *src/absl/types/internal/parentheses.h 27f0c5818e53bdc944c73938edf49a15 *src/absl/types/internal/span.h 599feda2b9b004573e161a9550054e99 *src/absl/types/internal/transform_args.h 637f1331d00d757439fcba667b80a4f5 *src/absl/types/internal/variant.h 29994a10008e74ff8963787848912fbc *src/absl/types/optional.h 6e8045dbd0797fcac5d8b9dbe5c6a322 *src/absl/types/span.h 1d24f87c9fb98d810977a981043d8201 *src/absl/types/variant.h 9b15fdb150cb72ea63d34d08fffd39c2 *src/absl/utility/utility.h 5f46d29bc94bcdfc28e2a4df442310de *src/cpp-compat.cpp afabc65ce7a01abdb14efb1abccd7247 *src/cpp-compat.h c20b3dc7b63f3bbf6af6827ac6557ca9 *src/geography-operator.h 023f7dbc8c3dd00c481507be51a9ca91 *src/geography.h 0365ec85c22784d83cce5b84d71d794a *src/init.cpp 62ed6c893b0bc0b75b67e9b02706a0b5 *src/s2-accessors.cpp 4cfaeb17ebda5876ac7110d5e15b6e87 *src/s2-bounds.cpp 10bfc12436ce2945f667824653c829d1 *src/s2-cell-union.cpp 16dc89505d02a555613082143e1d79a7 *src/s2-cell.cpp cf59fd9682672c99319ba906455e5b32 *src/s2-constructors-formatters.cpp 19657182677a20a4e14bcd89e03f6f06 *src/s2-geography.cpp e9f1b666c7a410cadaa4dfa34feba4b2 *src/s2-lnglat.cpp db77ba68fab15ce99c37d6d50471cbbf *src/s2-matrix.cpp 96980fe0bed80e6566fbbd84bc8dcdf8 *src/s2-options.h 70c6d7bd00bb98b98a35f9ca8d77677d *src/s2-predicates.cpp 9c30c6461c446d323ae75d0b9f2395e5 *src/s2-transformers.cpp 8428235fc1165d0c32375e2e21a686fe *src/s2/_fp_contract_off.h 28e49579436bf834cd114aa6f01f396e *src/s2/base/casts.h 4c10c37f35c3ea32da84e18e427e0a8f *src/s2/base/commandlineflags.h 95d68a4bda8ac1ed1238c06999c7fe8f *src/s2/base/integral_types.h 188fa699db99f7b32bf12c8f5d49afab *src/s2/base/log_severity.h e6796dc6fac7834af2337a6298f6b4f2 *src/s2/base/logging.h 322d31aa00d2046a0f35c7e373748985 *src/s2/base/mutex.h bc87e8d47e0131c7f3b2e943823c79f8 *src/s2/base/port.h 7217ae2988c45fe3c657a6fbb5a01334 *src/s2/base/spinlock.h 4c6c57a3c08c66211d519f387ea8e972 *src/s2/base/stringprintf.cc df6c98233d53cb881136f5f52d2c8012 *src/s2/base/stringprintf.h c10c3c7ca58239c0915bacffc70e37e1 *src/s2/base/strtoint.cc ac9edc4a5c5139604384d611a9d5686f *src/s2/base/strtoint.h 8a23b88128feaefecac82381870b7e1b *src/s2/base/timer.h a85315a801220cea6aedb7545bed0675 *src/s2/encoded_s2cell_id_vector.cc ae93520447e17b7a5ee570e973b14058 *src/s2/encoded_s2cell_id_vector.h d7cc642275acd754dd38bc1db012beb8 *src/s2/encoded_s2point_vector.cc d7870427a21d7a4288a82ef3d9002404 *src/s2/encoded_s2point_vector.h 1c9eda503a7610170bb7b20b221a8123 *src/s2/encoded_s2shape_index.cc 8c71134bbac741ab14ad58645c587efd *src/s2/encoded_s2shape_index.h 7ded14eeab5eba2a2cca20c71b837d1c *src/s2/encoded_string_vector.cc 2b5b150cb6a9356b9360ab4bed8fb9e0 *src/s2/encoded_string_vector.h 9e444256aa6a7b5e5c3eb73ba1708065 *src/s2/encoded_uint_vector.h 788f48e5bbfca22ed1e396d4bd675ad3 *src/s2/id_set_lexicon.cc e7cbe5a6c88b9a286b17adb5e690286f *src/s2/id_set_lexicon.h e77194370c1950bc155ee61708af57b0 *src/s2/mutable_s2shape_index.cc f2106aebeef507adcbc699ee5bb2ae5b *src/s2/mutable_s2shape_index.h a5fd0e84e01c56c79c50f208ae1b08b9 *src/s2/r1interval.h e363bde392175774fc26dcde1c3b5076 *src/s2/r2.h da84b9bfc2aeabd4f8ae0f84343623e7 *src/s2/r2rect.cc 4b12844b6a0327fbc6f0488b4fbea0b3 *src/s2/r2rect.h e7ca7f23a7e8fe6f707e4169f1aadb77 *src/s2/s1angle.cc 0e9a83acf9613dc68174c6575afa373d *src/s2/s1angle.h d9387a9af7477f6b82ec73d206cfd8d9 *src/s2/s1chord_angle.cc 7b2a01d9f67a03390a0f34b03a46fef8 *src/s2/s1chord_angle.h 34874873bee5edbf9af38cd7692ab3ae *src/s2/s1interval.cc 2a46bfae65f4a24dbbc0c8a7df478c4c *src/s2/s1interval.h ad8719b6e9e1c836e9f4c59b216739c9 *src/s2/s2boolean_operation.cc 99423d6009cb68d7879793f9311e1bb7 *src/s2/s2boolean_operation.h aead3947dd83dd0ed2d4becb7d14d426 *src/s2/s2builder.cc 5cf1544718ecb4046621e2b0b89a0222 *src/s2/s2builder.h bbdb9da0943cf55e60e846d2b0e7187b *src/s2/s2builder_graph.cc cc61ae8fff8370c728bf6de7513e06d9 *src/s2/s2builder_graph.h 0558b2bb40025b583974ac1a5d2b5857 *src/s2/s2builder_layer.h 485fed02d2c102ef03502c959735152c *src/s2/s2builderutil_closed_set_normalizer.cc 4b17df092dd21d0d5b22499a7a0d8d63 *src/s2/s2builderutil_closed_set_normalizer.h 128b04413654742d9655bc2b12dfeda7 *src/s2/s2builderutil_find_polygon_degeneracies.cc dc965c407adeb3070db3510de3b1f0bd *src/s2/s2builderutil_find_polygon_degeneracies.h 59390c82ae51ffd8dc8a2154c7f5cc6b *src/s2/s2builderutil_graph_shape.h 85e926aef77aeccd2d0384001b3e7c09 *src/s2/s2builderutil_lax_polygon_layer.cc 8d156a2b07ddd94dd53ed45a086ddabd *src/s2/s2builderutil_lax_polygon_layer.h 6c0c3e9f73a2d693d25392a6f053cca9 *src/s2/s2builderutil_s2point_vector_layer.cc 5bcdb1842aff634eb86151c41aa92942 *src/s2/s2builderutil_s2point_vector_layer.h 338a73e56e2a0406bc308dcb7ae2a1db *src/s2/s2builderutil_s2polygon_layer.cc d0f7d3975bf12ad12e5a453b1c664879 *src/s2/s2builderutil_s2polygon_layer.h bc24648fc1ba139430b08df68cc45004 *src/s2/s2builderutil_s2polyline_layer.cc 0c78a6937a60f24a9896a4d411b68e90 *src/s2/s2builderutil_s2polyline_layer.h 9ed299b94f9fd05d3b228cbbb77a29e9 *src/s2/s2builderutil_s2polyline_vector_layer.cc 8177eb92bdd7082985e5e2cef2cbb57e *src/s2/s2builderutil_s2polyline_vector_layer.h 41c634fe2532fd5946349947743d9b0b *src/s2/s2builderutil_snap_functions.cc 14fae38b064b536a50fd99090bf2bb80 *src/s2/s2builderutil_snap_functions.h 0e7eb31b407e02950751a990c528833d *src/s2/s2builderutil_testing.cc e91fca8f2ddf6c1826122f601803cf6f *src/s2/s2builderutil_testing.h 15661d854ecdbd9b5adb37f4fa38e7fd *src/s2/s2cap.cc 1aaf20b1794973131d85bbd6d16fc296 *src/s2/s2cap.h d89508276ed169153d1e80d85989bfd5 *src/s2/s2cell.cc cc050d60fc6b31479ad2cfc444aebc2b *src/s2/s2cell.h df582edaa44ad77d7a5d8e9fb7c34262 *src/s2/s2cell_id.cc 2b7fcd907f3904fa8e8fedf12fd79303 *src/s2/s2cell_id.h b77c66885dd9ce9decb058448a53bf2b *src/s2/s2cell_index.cc c967db283c31cf3b04720da4aaf951be *src/s2/s2cell_index.h 2df8fa84fabd7e103ecbcf3dddec4ab7 *src/s2/s2cell_union.cc b1877f966f026dafb27bd156d26d8194 *src/s2/s2cell_union.h cf1a07e72197832f2b1302f7b3630f98 *src/s2/s2centroids.cc b0e46b1f9b0302bf28993c83b1c29952 *src/s2/s2centroids.h d23a0e7ec3560f1191df801a3ded25e4 *src/s2/s2closest_cell_query.cc 1022d523b50d2186b166ca68dc0deb11 *src/s2/s2closest_cell_query.h 1b857efbfb27e180b3cb9a554e64048d *src/s2/s2closest_cell_query_base.h 16334d3128469be0f498a5e1a6427659 *src/s2/s2closest_edge_query.cc a239c16e1797acb32e845a6da24ca0b0 *src/s2/s2closest_edge_query.h a4484a9f30e74392ad90ac037b3404d3 *src/s2/s2closest_edge_query_base.h 0974a6dabc5a7541eebf0e81656e522c *src/s2/s2closest_edge_query_testing.h 09e2f693ad6e491a4d50eb8b5d966840 *src/s2/s2closest_point_query.cc e982b798e56156c65fb5ddf919a0520c *src/s2/s2closest_point_query.h ffdeee92985536a16b5a6212547872ba *src/s2/s2closest_point_query_base.h c5ac9b7327a1f2eb8c9b8683973bc48c *src/s2/s2contains_point_query.h 8cea937d5d1881cdb71d1287f5474cff *src/s2/s2contains_vertex_query.cc 0a9a1857a2e79884b600de88b69cefbe *src/s2/s2contains_vertex_query.h 06d65d3cca94ccb99beb7e33f5267e10 *src/s2/s2convex_hull_query.cc 7c2da76e9c9917194d48939ee919f088 *src/s2/s2convex_hull_query.h effdbbf64bb7f235381201657e021ea6 *src/s2/s2coords.cc e2a5abdcf611242bdf5cf0edf0eea5ad *src/s2/s2coords.h 28c4af3631469e232aab685be3e33122 *src/s2/s2coords_internal.h 48655b45fa96f0da0b234ca68fe4b54d *src/s2/s2crossing_edge_query.cc 28b59ab0651bf933d5271393e3313deb *src/s2/s2crossing_edge_query.h cbeeb65a7261dc8430dcf6edc24deb94 *src/s2/s2debug.cc 12e9446c9a552088c70b7b3062c1ba5b *src/s2/s2debug.h 7298228fc7e23751181ad62e211a81b8 *src/s2/s2distance_target.h a863fc668a334819762480cc0646c898 *src/s2/s2earth.cc b90e02812e7d1afa32d15de3a0245dd8 *src/s2/s2earth.h d58b020152efbe99f672b8952460d2ce *src/s2/s2edge_clipping.cc cb59ea7179d978f41ecac9ff2ab101c5 *src/s2/s2edge_clipping.h c5fd68f5fab9c30285a9d1995be3ddde *src/s2/s2edge_crosser.cc 91b536b7351c85048ac5f4fc8f9c36f3 *src/s2/s2edge_crosser.h 69bb6ff8072fe88be08b2e693071ea65 *src/s2/s2edge_crossings.cc 6de8f9979a94b2001e3134b4b10391a5 *src/s2/s2edge_crossings.h 482bff39a41ea67b1073b9cfdc11400b *src/s2/s2edge_crossings_internal.h 3bdbadd260d873672d7821d8a0f557ed *src/s2/s2edge_distances.cc db89cee1d7d37be1b7f889cc92df8e94 *src/s2/s2edge_distances.h 75acbcdde0e718a3a990fa63c78c3423 *src/s2/s2edge_tessellator.cc 0f223cfa5b4b1153617bd64d47f68212 *src/s2/s2edge_tessellator.h 99195e00ed62fc8127a6de6305730930 *src/s2/s2edge_vector_shape.h 483c797be9c9eb4b9a9059577e548e7b *src/s2/s2error.cc 30c2e0bb485124026064b8a6e680c60a *src/s2/s2error.h fe0c39101c8c58f45b75fc4c36261a82 *src/s2/s2furthest_edge_query.cc 9ab743cac7f27a70e2f49f6a8d659c49 *src/s2/s2furthest_edge_query.h fe5f7037943d797d93f4d2be3d1119b0 *src/s2/s2latlng.cc f86006f14f82fb5fb2dc61b217d79be0 *src/s2/s2latlng.h e42ec23a4fdf8b6aea983db2ca3cc4d9 *src/s2/s2latlng_rect.cc bd642eda609967162d94ac500632e454 *src/s2/s2latlng_rect.h f8689b6a3eef9053601d310ca6146f5c *src/s2/s2latlng_rect_bounder.cc 486a11f20722a978644e99ec990516ad *src/s2/s2latlng_rect_bounder.h 6ea59dae602c6e87765068c7c4357baf *src/s2/s2lax_loop_shape.cc 6aaf1e13f42ac06794d6179a45992a12 *src/s2/s2lax_loop_shape.h fc760f9c0ff8545929b4a1e0da1e6ff5 *src/s2/s2lax_polygon_shape.cc 35b22571a5ddbe1e7b91e6497b39e111 *src/s2/s2lax_polygon_shape.h 56c7b3c6c942d0cffe353b03ee605a8f *src/s2/s2lax_polyline_shape.cc 4d4f60c49166c8657258de45884c4087 *src/s2/s2lax_polyline_shape.h 818253e9f331b59caf71a9e18f7dfd12 *src/s2/s2loop.cc da57f0c9e0f4c2cdf2599e0de097dbd9 *src/s2/s2loop.h 2f78e2f1e0911445a0ffe5e5b0fe014a *src/s2/s2loop_measures.cc eef153a4e7bb190f620c250f78ac7c1c *src/s2/s2loop_measures.h 2e54cb10538dee0beadcfcf774875b64 *src/s2/s2max_distance_targets.cc ad2641cab77de9fe057639d5afdf4edd *src/s2/s2max_distance_targets.h 66d7e4a5a3700d4b5a8c7e013fcfc370 *src/s2/s2measures.cc b230afb1b144cfe2dfa5241d0d935cc1 *src/s2/s2measures.h b70ef8989674552ca3d0e0c9fcb754a8 *src/s2/s2metrics.cc c974a90f9d2ed8199af139ad33147aa4 *src/s2/s2metrics.h 582857ac635543b496cf93ee1bbe133b *src/s2/s2min_distance_targets.cc 49d8a47ed9a0f58dd97b2258b1764e77 *src/s2/s2min_distance_targets.h 94bd23bc709062647f0bdc993dfc7933 *src/s2/s2padded_cell.cc b2841070cce458abff94905776f74eae *src/s2/s2padded_cell.h af4bfede404056bb6ecc81a6ec4ed141 *src/s2/s2point.h caf1d4962bb43ca5e3bfdc9d17640b49 *src/s2/s2point_compression.cc 0261a1c5b0ed5a8fec6b797ec53b5f30 *src/s2/s2point_compression.h 127b57a24cfa7ea2c78bc0fb7c9f446b *src/s2/s2point_index.h 7b88d7113b91cd8d5f0c1ad5bbacc7c4 *src/s2/s2point_region.cc 1c40d79d4102f56ec2301b62d2767a0e *src/s2/s2point_region.h 291c8ec7baef072cb29bc341fb3cb5c1 *src/s2/s2point_span.h 7d75a6945e15b9c78a713a59bd85f199 *src/s2/s2point_vector_shape.h 0475a05e50a35ca03b06e084178cd31a *src/s2/s2pointutil.cc 5d2100a54f4302ee75608d48d586fe3c *src/s2/s2pointutil.h 3272d4e15adf23b8b98b7fe50ca6e43a *src/s2/s2polygon.cc dad5e0faba176fe983c613c3d6a60d07 *src/s2/s2polygon.h 12ef96f03b504ccf5c7523d15448b882 *src/s2/s2polyline.cc 16ea03d777619f1e59a47cbf83d53071 *src/s2/s2polyline.h 2914934120ec55346427958e5f73fb5e *src/s2/s2polyline_alignment.cc c0f9b77a9439bc0c84d9dfe2dcbcc25b *src/s2/s2polyline_alignment.h 8a2d7dedff6ee98faf1edcc694a07a19 *src/s2/s2polyline_alignment_internal.h c206c2fa52e4a5588b1024f45c63595b *src/s2/s2polyline_measures.cc 3b78b40b711bc148a7cc8f01f044d9a7 *src/s2/s2polyline_measures.h eab00b14b7643e320f8044521eaa16e4 *src/s2/s2polyline_simplifier.cc a12b87a494ef6e98dd8b19091ebf8cd8 *src/s2/s2polyline_simplifier.h ff7549a0d6195b34b24867fff4cc6d06 *src/s2/s2predicates.cc 3e580e8e5db3954a6918420df5d50630 *src/s2/s2predicates.h 7dd5ac042f3403c26f10e8bc6bfcc8ac *src/s2/s2predicates_internal.h 7478e1714d9dcaf520159458d10e383b *src/s2/s2projections.cc fa7c158fb73fced84c86e73bc40a4215 *src/s2/s2projections.h 8baa87c45aa11148a5207f367237e753 *src/s2/s2r2rect.cc eef1eb7446a79b5b6430af8eede7a698 *src/s2/s2r2rect.h 57778c7c84ae75e67ed68e76f9026ec0 *src/s2/s2region.cc 2234c6d0d865cc61ca6959c01e0015e4 *src/s2/s2region.h e12692516fc6b52c237e16cf16f11e19 *src/s2/s2region_coverer.cc dc72c2322d4aa4d89edaeac8a99d3110 *src/s2/s2region_coverer.h f622c31665d97c607e1cb5106c295959 *src/s2/s2region_intersection.cc d146a90111cd2d2b33b1526d5d1eab9e *src/s2/s2region_intersection.h 4bedf29698cbce5a1d755b5d9f0e5312 *src/s2/s2region_term_indexer.cc ba4411f3cbd9ffe23482ec22e2045b31 *src/s2/s2region_term_indexer.h f8232c70a590ed11666c93181c9d7343 *src/s2/s2region_union.cc 7acd16e242bad3bf6f8651417f6940c2 *src/s2/s2region_union.h 9e0666e7c03402dbf71fec6da5aa9244 *src/s2/s2shape.h 25ac091ec0963d3216b86e86a050eae9 *src/s2/s2shape_index.cc 1b8ddf576bde4dc6647dcec8341a18a3 *src/s2/s2shape_index.h 906e1e16bc8aa9f5d4e99651a6cdf0fe *src/s2/s2shape_index_buffered_region.cc 2f6f557e792611c362fee453ca16eb88 *src/s2/s2shape_index_buffered_region.h c0543285953743a9605aa3ef63ad6127 *src/s2/s2shape_index_measures.cc 1caf918a11c8bafc0835861870fbd0ea *src/s2/s2shape_index_measures.h b095a57fa9ad2adfa3cc31f6b8d6fffd *src/s2/s2shape_index_region.h 23c94c388142e68594cbf29b677d2bbc *src/s2/s2shape_measures.cc ce9d81e0539f9c3a136e07d12c46cb05 *src/s2/s2shape_measures.h c5794119535ecf13d27f5ae34c58f8b0 *src/s2/s2shapeutil_build_polygon_boundaries.cc 36f49316d02ac4f5c1124d7a3365419f *src/s2/s2shapeutil_build_polygon_boundaries.h 2b6d1731185a9ff771d2f150cf6c66f2 *src/s2/s2shapeutil_coding.cc 3c7dde9ddf64c467729e08fb30741bbe *src/s2/s2shapeutil_coding.h afd2720bf07ba0aa5f94dfe9ba0c4d3a *src/s2/s2shapeutil_contains_brute_force.cc f6d89268f04abb12ccd63236d4362060 *src/s2/s2shapeutil_contains_brute_force.h 1344b756712c76ac8c390abdb5a5886e *src/s2/s2shapeutil_count_edges.h a699c9e30b9f8eaea6b9308c099c2285 *src/s2/s2shapeutil_edge_iterator.cc 79c3c7d45f832a4b348116e5b9ec993d *src/s2/s2shapeutil_edge_iterator.h ae2b2addb5cb94cd24c57c195e1b0b49 *src/s2/s2shapeutil_get_reference_point.cc 53160e1333206256c06775aceb56ff6e *src/s2/s2shapeutil_get_reference_point.h 6d1353bb32206db05048ee50a073c0e7 *src/s2/s2shapeutil_range_iterator.cc d12c9628c73beb0f42bb462060facbe7 *src/s2/s2shapeutil_range_iterator.h a249fc71604963665d21c311c0e1c391 *src/s2/s2shapeutil_shape_edge.h bdf516e73992d5a15fdaa074596f8d6f *src/s2/s2shapeutil_shape_edge_id.h 007fda069d8144645da7f2c7462a8359 *src/s2/s2shapeutil_testing.h 2d43581ce7e9638ee9d4390016d422df *src/s2/s2shapeutil_visit_crossing_edge_pairs.cc f21bfbc04ce2d1d819512176b45954bb *src/s2/s2shapeutil_visit_crossing_edge_pairs.h 489cf62e6b1d3e3ff20b67aa19975130 *src/s2/s2testing.cc fd1b7e4602b2f79e66496c81232f9cb1 *src/s2/s2testing.h b504a72745dbeab05a8f198831eebb7d *src/s2/s2text_format.cc 294d80e1f4a307f03d84e66ae11767a1 *src/s2/s2text_format.h 518f25738bd53f6439d3708f56a85435 *src/s2/s2wedge_relations.cc 3c1f74157144820e8e5008fdc433e018 *src/s2/s2wedge_relations.h d3ffa54eb4cab5a70d23bee5cbc17b3e *src/s2/sequence_lexicon.h 9b6952846f960ce7c8f86d425a6cfc53 *src/s2/strings/ostringstream.cc a7553d41936a6491b5e79bad88a676b9 *src/s2/strings/ostringstream.h 33d32d088678a9117a40a020df05e8fc *src/s2/strings/serialize.cc ae996ef21b8b910fc64050493a0595ae *src/s2/strings/serialize.h c90e431dac905cc9e596e1b1071b7ea1 *src/s2/util/bits/bit-interleave.cc 3bfcb45aa9bfa89827d30afba984f79c *src/s2/util/bits/bit-interleave.h ce0ab9919f6091c79c677b39c23f8b00 *src/s2/util/bits/bits.cc 37c9da7e1a7d27c20ca04a3a8ef559ec *src/s2/util/bits/bits.h c0d70edbf2ac3f698c17d89959e326f9 *src/s2/util/coding/coder.cc 92ada6509b11d1a90ec200c2adf9e0dd *src/s2/util/coding/coder.h c4fe67fd8a294e03bfc46ce9fb1134d1 *src/s2/util/coding/nth-derivative.h 2e711fab16d3ec015ecc12d522b8b0ae *src/s2/util/coding/transforms.h 90d94cda2cd92a02c4851ebb93d7453e *src/s2/util/coding/varint.cc 9a4e580abf363f1e0bb4bded873514cd *src/s2/util/coding/varint.h 9e59dc6aad536cbce24eedfb658d8716 *src/s2/util/endian/endian.h 4525710cda51323baa3327ac57ed3106 *src/s2/util/gtl/btree.h 118e86e589e0fae520e30c18dc1f3e08 *src/s2/util/gtl/btree_container.h 52e6fdc3fa81363e043d7e5a35a697ef *src/s2/util/gtl/btree_map.h 81e2de007193adce24cd9d5f5739bf5e *src/s2/util/gtl/btree_set.h d3ee6aee2a2c316240faa7a33434a23e *src/s2/util/gtl/compact_array.h 09f417c582f54941a864fcbae7d93397 *src/s2/util/gtl/container_logging.h 6868f5ef7082e686952ee034c62a6cce *src/s2/util/gtl/dense_hash_set.h e6d5083ac705a0300e77fe096a7a0589 *src/s2/util/gtl/densehashtable.h fa56660fc40be26b3dbfc7115c56674e *src/s2/util/gtl/hashtable_common.h 8e531532de42f1c1f7cd4adb553fe486 *src/s2/util/gtl/layout.h 8c80e94bcdfb52740a0b8c0561845642 *src/s2/util/gtl/legacy_random_shuffle.h a36fbccb7e3c064e5a16b4ac1e7842f3 *src/s2/util/hash/mix.h a77a8f0d9d512bbec62949a0b3db20aa *src/s2/util/math/exactfloat/exactfloat.cc 828d0024dcc62675ab2be8b6f3cc3f93 *src/s2/util/math/exactfloat/exactfloat.h 2037118f434c6f41bc8014de2eb18c92 *src/s2/util/math/mathutil.cc 0c3be461d027e0cccb9c0facb12cd046 *src/s2/util/math/mathutil.h 160830f8d1d1983f1b773f953abff72d *src/s2/util/math/matrix3x3.h c07f92fe336b33334c46895999a4c62a *src/s2/util/math/vector.h 5b091051cad1329e1e2b535e40e9c142 *src/s2/util/math/vector3_hash.h 2aba55dd12417d944cd91a2f02b1d946 *src/s2/util/units/length-units.cc 1bdf37d9bef7eba2b40d5baad2403f8d *src/s2/util/units/length-units.h bdfd79c04de7e7893d9cadb9dd9d2f63 *src/s2/util/units/physical-units.h 400af05643ac08d897c2100a0a68f1aa *src/s2/value_lexicon.h f5e89fcca3521af3ae1984cf3b988541 *src/s2geography.h d8287075eb3b5a0d59d684a3bbc5b5ba *src/s2geography/accessors-geog.cc 10c803af1a5b075cb974f138658b1f41 *src/s2geography/accessors-geog.h 8e45d5fd12d368bb2989b24b6e158da4 *src/s2geography/accessors.cc 3348390f6d77133281e54b1e8a29c45b *src/s2geography/accessors.h a3e670ac87785c083206586c8f3f4ca6 *src/s2geography/aggregator.h 3b1bf6b72b4dd96ca7c4b760554fc63c *src/s2geography/build.cc fed04d2f5ddae951483be94e78dd8d46 *src/s2geography/build.h 241843e894b3d958be1f475991febe29 *src/s2geography/constructor.h 26d396867ab4214aaa9c811a3f5076e0 *src/s2geography/coverings.cc 9245d1de9f44acb9dc6f2ea03324761b *src/s2geography/coverings.h 93135f5c6f3755e0b4d2c2c0e40776ed *src/s2geography/distance.cc ac0bf08dcc4f407205a4d78007006e02 *src/s2geography/distance.h cb5e8ca81a2ad12cd88d1cc88a2ec00b *src/s2geography/geoarrow-imports.h be903b4618b334e46916d7c56a3b4cf0 *src/s2geography/geography.cc f705c43a8173f1b54eb7d391004654c5 *src/s2geography/geography.h 275d807d19673e87935f24d914697a7b *src/s2geography/index.h 520c06d0ec1884663e62ba6e7b183f49 *src/s2geography/linear-referencing.cc ad9e7623296a977bc6b38f6acd1e29b2 *src/s2geography/linear-referencing.h d8d87d7c6b89d370f45398ce0bc4b545 *src/s2geography/predicates.cc a8a420182aad78038f2f87e8b8e0539d *src/s2geography/predicates.h 6622cfeaf8b569e1dbb71209addae76f *src/tests/main.c 4495750656420f7cd041bf0bf27ec020 *src/tests/soname.h 3ff04996080488941707e8fd75d50d02 *src/wk-impl.c a9ffa55303a6dcba9c5c9c9d322e2009 *tests/area.R ca2d545540a24ee1cdbf83a0060f6a43 *tests/area.Rout.save 9a6a22f7e00fd5147781e07d2edd589e *tests/testthat.R 755c4ca0ad5e6f558b9eb7c7e04ded6e *tests/testthat/test-data.R d8d89b5827614edf67567d7e15e2f213 *tests/testthat/test-plot.R c94a63e70152c14d6ea3d8e0c947d739 *tests/testthat/test-s2-accessors.R 6d272b0f1b19c173c19ff225c361cd31 *tests/testthat/test-s2-bounds.R 5c3476c24589151b804b66580c20e9b8 *tests/testthat/test-s2-cell-union.R c64aab49b83a1fdc400a28233aefbda7 *tests/testthat/test-s2-cell.R 2fb5fbb6a8d7d4d13c80c650380a2763 *tests/testthat/test-s2-constructors-formatters.R 532d31b6373a11733f72fda2b4254ef6 *tests/testthat/test-s2-earth.R 022f63897a9ae3916d1990c77c23c4ae *tests/testthat/test-s2-geography.R 68d9f8c6639d165549ed1e487365bb0e *tests/testthat/test-s2-lnglat.R b874f311928c98a3056d5628c55422c8 *tests/testthat/test-s2-matrix.R 97bdafc52d85952cf4d72ef1818ba36c *tests/testthat/test-s2-options.R b8d52c633fa3d389a46c8198804f9757 *tests/testthat/test-s2-point.R 6a0b51e1b62caf1dc1ce91d662c9a424 *tests/testthat/test-s2-predicates.R 5ef809d341a4b5203d675644845e6e94 *tests/testthat/test-s2-transformers.R a7de1183f03382626ca2b46bf1c4e781 *tests/testthat/test-utils.R 2c9ecaae7d9fe889868e0c69bfecc42c *tests/testthat/test-vctrs.R eb4ce45137cf0d61472a37c5f493d59b *tests/testthat/test-wk-utils.R 0ec27c181a66ce5b72bc8c3940e9e00e *tools/version.c 497dfd3cbf113981c70269ce4b31c496 *tools/winlibs.R s2/configure.win0000755000176200001440000000000014530411473013271 0ustar liggesuserss2/R/0000755000176200001440000000000014540324344011002 5ustar liggesuserss2/R/s2-point.R0000644000176200001440000000260314530411473012600 0ustar liggesusers #' Create an S2 Point Vector #' #' In S2 terminology, a "point" is a 3-dimensional unit vector representation #' of an [s2_point()]. Internally, all s2 objects are stored as #' 3-dimensional unit vectors. #' #' @param x,y,z Vectors of latitude and longitude values in degrees. #' @param ... Unused #' #' @return An object with class s2_point #' @export #' #' @examples #' point <- s2_lnglat(-64, 45) # Halifax, Nova Scotia! #' as_s2_point(point) #' as.data.frame(as_s2_point(point)) #' s2_point <- function(x, y, z) { wk::xyz(x, y, z, crs = s2_point_crs()) } #' @rdname s2_point #' @export s2_point_crs <- function() { structure(list(), class = "s2_point_crs") } #' @export format.s2_point_crs <- function(x, ...) { "s2_point_crs" } #' @rdname s2_point #' @export as_s2_point <- function(x, ...) { UseMethod("as_s2_point") } #' @rdname s2_point #' @export as_s2_point.default <- function(x, ...) { as_s2_point(wk::as_xy(x)) } #' @rdname s2_point #' @export as_s2_point.wk_xy <- function(x, ...) { stopifnot(wk::wk_crs_equal(wk::wk_crs(x), wk::wk_crs_longlat())) wk::new_wk_xyz( s2_point_from_s2_lnglat(x), crs = s2_point_crs() ) } #' @rdname s2_point #' @export as_s2_point.wk_xyz <- function(x, ...) { wk::wk_set_crs( wk::as_xy(x, dims = c("x", "y", "z")), s2_point_crs() ) } #' @export as_s2_point.character <- function(x, ...) { as_s2_point(wk::new_wk_wkt(x)) } s2/R/s2-package.R0000644000176200001440000000041714530411473013043 0ustar liggesusers#' @keywords internal "_PACKAGE" # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start #' @useDynLib s2, .registration = TRUE #' @importFrom Rcpp sourceCpp ## usethis namespace: end NULL s2/R/plot.R0000644000176200001440000000756314530411473012115 0ustar liggesusers #' Plot S2 Geographies #' #' @inheritParams wk::wk_plot #' @param plot_hemisphere Plot the outline of the earth #' @param centre The longitude/latitude point of the centre of the #' orthographic projection #' @param simplify Use `FALSE` to skip the simplification step #' #' @return The input, invisibly #' @export #' #' @examples #' s2_plot(s2_data_countries()) #' s2_plot(s2_data_cities(), add = TRUE) #' s2_plot <- function(x, ..., asp = 1, xlab = "", ylab = "", rule = "evenodd", add = FALSE, plot_hemisphere = FALSE, simplify = TRUE, centre = NULL) { x <- as_s2_geography(x) if (add) { last <- last_plot_env$centre centre <- if (is.null(last)) s2_lnglat(0, 0) else last } else if (is.null(centre)) { centre <- s2_centroid_agg(x, na.rm = TRUE) } centre <- as_s2_lnglat(centre) projection <- s2_projection_orthographic(centre) hemisphere_bounds_poly <- cap_to_polygon(centre, (pi / 2) - 1e-5) if (plot_hemisphere) { bbox_projected <- wk::rct(-1, -1, 1, 1) } else if (!add) { bbox_projected <- wk::wk_handle( x, wk::wk_bbox_handler(), s2_projection = projection, s2_tessellate_tol = s2_tessellate_tol_default() ) } wk::wk_plot( wk::xy(), bbox = bbox_projected, asp = asp, xlab = xlab, ylab = ylab, add = add ) if (!add) { last_plot_env$centre <- centre } if (plot_hemisphere) { wk::wk_plot(wk::crc(0, 0, 1), add = TRUE) } # estimate resolution. In user coords, this can be though of in radians # (at the centre of the plot) usr <- graphics::par("usr") usr_x <- usr[1:2] usr_y <- usr[3:4] device_x <- graphics::grconvertX(usr_x, to = "device") device_y <- graphics::grconvertY(usr_y, to = "device") scale_x <- diff(device_x) / diff(usr_x) scale_y <- diff(device_y) / diff(usr_y) scale <- min(abs(scale_x), abs(scale_y)) resolution_usr_rad <- 0.25 / scale # limit output to dimensions in input dimensions_in_input <- setdiff(unique(s2_dimension(x)), NA_character_) if (simplify) { x_hemisphere <- s2_intersection( x, hemisphere_bounds_poly, options = s2_options( snap = s2_snap_distance(resolution_usr_rad), snap_radius = resolution_usr_rad, simplify_edge_chains = TRUE, dimensions = c("point", "polyline", "polygon")[dimensions_in_input + 1] ) ) } else { x_hemisphere <- s2_intersection( x, hemisphere_bounds_poly, options = s2_options( dimensions = c("point", "polyline", "polygon")[dimensions_in_input + 1] ) ) } x_hemisphere[s2_is_empty(x_hemisphere)] <- as_s2_geography("POINT EMPTY") x_hemisphere_planar <- wk::wk_handle( x_hemisphere, wk::wkb_writer(), s2_projection = projection, # if this is too small we can get a stack overflow since the # tessellation is recursive s2_tessellate_tol = max(0.002, resolution_usr_rad * 4) ) wk::wk_plot( x_hemisphere_planar, ..., rule = rule, add = TRUE ) invisible(x) } #' @export plot.s2_geography <- function(x, ...) { s2_plot(x, ...) } #' @export plot.s2_cell_union <- function(x, ...) { s2_plot(x, ...) invisible(x) } #' @export plot.s2_cell <- function(x, ...) { if (all(s2_cell_is_leaf(x), na.rm = TRUE)) { s2_plot(s2_cell_center(x), ...) } else { s2_plot(s2_cell_polygon(x), ...) } invisible(x) } cap_to_polygon <- function(centre = s2_lnglat(0, 0), radius_rad) { centre <- as_s2_lnglat(centre) rad_proj <- sin(radius_rad) points <- wk::xy( c(0, rad_proj, 0, -rad_proj, 0), c(rad_proj, 0, -rad_proj, 0, rad_proj) ) points_s2 <- wk::wk_handle( points, s2_geography_writer( projection = s2_projection_orthographic(centre) ) ) s2_make_polygon(s2_x(points_s2), s2_y(points_s2)) } last_plot_env <- new.env(parent = emptyenv()) last_plot_env$centre <- NULL s2/R/s2-lnglat.R0000644000176200001440000000251414530411473012731 0ustar liggesusers #' Create an S2 LngLat Vector #' #' This class represents a latitude and longitude on the Earth's surface. #' Most calculations in S2 convert this to a [as_s2_point()], which is a #' unit vector representation of this value. #' #' @param lat,lng Vectors of latitude and longitude values in degrees. #' @param x A [s2_lnglat()] vector or an object that can be coerced to one. #' @param ... Unused #' #' @return An object with class s2_lnglat #' @export #' #' @examples #' s2_lnglat(45, -64) # Halifax, Nova Scotia! #' as.data.frame(s2_lnglat(45, -64)) #' s2_lnglat <- function(lng, lat) { wk::xy(lng, lat, crs = wk::wk_crs_longlat()) } #' @rdname s2_lnglat #' @export as_s2_lnglat <- function(x, ...) { UseMethod("as_s2_lnglat") } #' @rdname s2_lnglat #' @export as_s2_lnglat.default <- function(x, ...) { as_s2_lnglat(wk::as_xy(x)) } #' @rdname s2_lnglat #' @export as_s2_lnglat.wk_xy <- function(x, ...) { wk::wk_set_crs( wk::as_xy(x, dims = c("x", "y")), wk::wk_crs_longlat() ) } #' @rdname s2_lnglat #' @export as_s2_lnglat.wk_xyz <- function(x, ...) { if (wk::wk_crs_equal(wk::wk_crs(x), s2_point_crs())) { wk::new_wk_xy( s2_lnglat_from_s2_point(x), crs = wk::wk_crs_longlat() ) } else { NextMethod() } } #' @export as_s2_lnglat.character <- function(x, ...) { as_s2_lnglat(wk::new_wk_wkt(x)) } s2/R/RcppExports.R0000644000176200001440000003152414540324344013423 0ustar liggesusers# Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 cpp_s2_init <- function() { invisible(.Call(`_s2_cpp_s2_init`)) } cpp_s2_is_collection <- function(geog) { .Call(`_s2_cpp_s2_is_collection`, geog) } cpp_s2_is_valid <- function(geog) { .Call(`_s2_cpp_s2_is_valid`, geog) } cpp_s2_is_valid_reason <- function(geog) { .Call(`_s2_cpp_s2_is_valid_reason`, geog) } cpp_s2_dimension <- function(geog) { .Call(`_s2_cpp_s2_dimension`, geog) } cpp_s2_num_points <- function(geog) { .Call(`_s2_cpp_s2_num_points`, geog) } cpp_s2_is_empty <- function(geog) { .Call(`_s2_cpp_s2_is_empty`, geog) } cpp_s2_area <- function(geog) { .Call(`_s2_cpp_s2_area`, geog) } cpp_s2_length <- function(geog) { .Call(`_s2_cpp_s2_length`, geog) } cpp_s2_perimeter <- function(geog) { .Call(`_s2_cpp_s2_perimeter`, geog) } cpp_s2_x <- function(geog) { .Call(`_s2_cpp_s2_x`, geog) } cpp_s2_y <- function(geog) { .Call(`_s2_cpp_s2_y`, geog) } cpp_s2_project_normalized <- function(geog1, geog2) { .Call(`_s2_cpp_s2_project_normalized`, geog1, geog2) } cpp_s2_distance <- function(geog1, geog2) { .Call(`_s2_cpp_s2_distance`, geog1, geog2) } cpp_s2_max_distance <- function(geog1, geog2) { .Call(`_s2_cpp_s2_max_distance`, geog1, geog2) } cpp_s2_bounds_cap <- function(geog) { .Call(`_s2_cpp_s2_bounds_cap`, geog) } cpp_s2_bounds_rect <- function(geog) { .Call(`_s2_cpp_s2_bounds_rect`, geog) } cpp_s2_cell_union_normalize <- function(cellUnionVector) { .Call(`_s2_cpp_s2_cell_union_normalize`, cellUnionVector) } cpp_s2_cell_union_is_na <- function(cellUnionVector) { .Call(`_s2_cpp_s2_cell_union_is_na`, cellUnionVector) } cpp_s2_cell_union_contains <- function(cellUnionVector1, cellUnionVector2) { .Call(`_s2_cpp_s2_cell_union_contains`, cellUnionVector1, cellUnionVector2) } cpp_s2_cell_union_contains_cell <- function(cellUnionVector, cellIdVector) { .Call(`_s2_cpp_s2_cell_union_contains_cell`, cellUnionVector, cellIdVector) } cpp_s2_cell_union_intersects <- function(cellUnionVector1, cellUnionVector2) { .Call(`_s2_cpp_s2_cell_union_intersects`, cellUnionVector1, cellUnionVector2) } cpp_s2_cell_union_intersection <- function(cellUnionVector1, cellUnionVector2) { .Call(`_s2_cpp_s2_cell_union_intersection`, cellUnionVector1, cellUnionVector2) } cpp_s2_cell_union_union <- function(cellUnionVector1, cellUnionVector2) { .Call(`_s2_cpp_s2_cell_union_union`, cellUnionVector1, cellUnionVector2) } cpp_s2_cell_union_difference <- function(cellUnionVector1, cellUnionVector2) { .Call(`_s2_cpp_s2_cell_union_difference`, cellUnionVector1, cellUnionVector2) } cpp_s2_geography_from_cell_union <- function(cellUnionVector) { .Call(`_s2_cpp_s2_geography_from_cell_union`, cellUnionVector) } cpp_s2_covering_cell_ids <- function(geog, min_level, max_level, max_cells, buffer, interior) { .Call(`_s2_cpp_s2_covering_cell_ids`, geog, min_level, max_level, max_cells, buffer, interior) } cpp_s2_covering_cell_ids_agg <- function(geog, min_level, max_level, max_cells, buffer, interior, naRm) { .Call(`_s2_cpp_s2_covering_cell_ids_agg`, geog, min_level, max_level, max_cells, buffer, interior, naRm) } cpp_s2_cell_sentinel <- function() { .Call(`_s2_cpp_s2_cell_sentinel`) } cpp_s2_cell_from_string <- function(cellString) { .Call(`_s2_cpp_s2_cell_from_string`, cellString) } cpp_s2_cell_from_lnglat <- function(lnglat) { .Call(`_s2_cpp_s2_cell_from_lnglat`, lnglat) } cpp_s2_cell_to_lnglat <- function(cellId) { .Call(`_s2_cpp_s2_cell_to_lnglat`, cellId) } cpp_s2_cell_to_cell_union <- function(cellId) { .Call(`_s2_cpp_s2_cell_to_cell_union`, cellId) } cpp_s2_cell_is_na <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_is_na`, cellIdVector) } cpp_s2_cell_sort <- function(cellIdVector, decreasing) { .Call(`_s2_cpp_s2_cell_sort`, cellIdVector, decreasing) } cpp_s2_cell_range <- function(cellIdVector, naRm) { .Call(`_s2_cpp_s2_cell_range`, cellIdVector, naRm) } cpp_s2_cell_unique <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_unique`, cellIdVector) } cpp_s2_cell_to_string <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_to_string`, cellIdVector) } cpp_s2_cell_debug_string <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_debug_string`, cellIdVector) } cpp_s2_cell_is_valid <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_is_valid`, cellIdVector) } cpp_s2_cell_center <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_center`, cellIdVector) } cpp_s2_cell_polygon <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_polygon`, cellIdVector) } cpp_s2_cell_vertex <- function(cellIdVector, k) { .Call(`_s2_cpp_s2_cell_vertex`, cellIdVector, k) } cpp_s2_cell_level <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_level`, cellIdVector) } cpp_s2_cell_area <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_area`, cellIdVector) } cpp_s2_cell_area_approx <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_area_approx`, cellIdVector) } cpp_s2_cell_parent <- function(cellIdVector, level) { .Call(`_s2_cpp_s2_cell_parent`, cellIdVector, level) } cpp_s2_cell_child <- function(cellIdVector, k) { .Call(`_s2_cpp_s2_cell_child`, cellIdVector, k) } cpp_s2_cell_edge_neighbour <- function(cellIdVector, k) { .Call(`_s2_cpp_s2_cell_edge_neighbour`, cellIdVector, k) } cpp_s2_cell_cummax <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_cummax`, cellIdVector) } cpp_s2_cell_cummin <- function(cellIdVector) { .Call(`_s2_cpp_s2_cell_cummin`, cellIdVector) } cpp_s2_cell_eq <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_eq`, cellIdVector1, cellIdVector2) } cpp_s2_cell_neq <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_neq`, cellIdVector1, cellIdVector2) } cpp_s2_cell_lt <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_lt`, cellIdVector1, cellIdVector2) } cpp_s2_cell_lte <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_lte`, cellIdVector1, cellIdVector2) } cpp_s2_cell_gte <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_gte`, cellIdVector1, cellIdVector2) } cpp_s2_cell_gt <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_gt`, cellIdVector1, cellIdVector2) } cpp_s2_cell_contains <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_contains`, cellIdVector1, cellIdVector2) } cpp_s2_cell_may_intersect <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_may_intersect`, cellIdVector1, cellIdVector2) } cpp_s2_cell_distance <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_distance`, cellIdVector1, cellIdVector2) } cpp_s2_cell_max_distance <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_max_distance`, cellIdVector1, cellIdVector2) } cpp_s2_cell_common_ancestor_level <- function(cellIdVector1, cellIdVector2) { .Call(`_s2_cpp_s2_cell_common_ancestor_level`, cellIdVector1, cellIdVector2) } cpp_s2_cell_common_ancestor_level_agg <- function(cellId) { .Call(`_s2_cpp_s2_cell_common_ancestor_level_agg`, cellId) } s2_geography_full <- function(x) { .Call(`_s2_s2_geography_full`, x) } cpp_s2_geography_is_na <- function(geog) { .Call(`_s2_cpp_s2_geography_is_na`, geog) } s2_lnglat_from_s2_point <- function(s2_point) { .Call(`_s2_s2_lnglat_from_s2_point`, s2_point) } s2_point_from_s2_lnglat <- function(s2_lnglat) { .Call(`_s2_s2_point_from_s2_lnglat`, s2_lnglat) } cpp_s2_closest_feature <- function(geog1, geog2) { .Call(`_s2_cpp_s2_closest_feature`, geog1, geog2) } cpp_s2_farthest_feature <- function(geog1, geog2) { .Call(`_s2_cpp_s2_farthest_feature`, geog1, geog2) } cpp_s2_closest_edges <- function(geog1, geog2, n, min_distance, max_distance) { .Call(`_s2_cpp_s2_closest_edges`, geog1, geog2, n, min_distance, max_distance) } cpp_s2_may_intersect_matrix <- function(geog1, geog2, maxEdgesPerCell, maxFeatureCells, s2options) { .Call(`_s2_cpp_s2_may_intersect_matrix`, geog1, geog2, maxEdgesPerCell, maxFeatureCells, s2options) } cpp_s2_contains_matrix <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_contains_matrix`, geog1, geog2, s2options) } cpp_s2_within_matrix <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_within_matrix`, geog1, geog2, s2options) } cpp_s2_intersects_matrix <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_intersects_matrix`, geog1, geog2, s2options) } cpp_s2_equals_matrix <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_equals_matrix`, geog1, geog2, s2options) } cpp_s2_touches_matrix <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_touches_matrix`, geog1, geog2, s2options) } cpp_s2_dwithin_matrix <- function(geog1, geog2, distance) { .Call(`_s2_cpp_s2_dwithin_matrix`, geog1, geog2, distance) } cpp_s2_distance_matrix <- function(geog1, geog2) { .Call(`_s2_cpp_s2_distance_matrix`, geog1, geog2) } cpp_s2_max_distance_matrix <- function(geog1, geog2) { .Call(`_s2_cpp_s2_max_distance_matrix`, geog1, geog2) } cpp_s2_contains_matrix_brute_force <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_contains_matrix_brute_force`, geog1, geog2, s2options) } cpp_s2_within_matrix_brute_force <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_within_matrix_brute_force`, geog1, geog2, s2options) } cpp_s2_intersects_matrix_brute_force <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_intersects_matrix_brute_force`, geog1, geog2, s2options) } cpp_s2_disjoint_matrix_brute_force <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_disjoint_matrix_brute_force`, geog1, geog2, s2options) } cpp_s2_equals_matrix_brute_force <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_equals_matrix_brute_force`, geog1, geog2, s2options) } cpp_s2_dwithin_matrix_brute_force <- function(geog1, geog2, distance) { .Call(`_s2_cpp_s2_dwithin_matrix_brute_force`, geog1, geog2, distance) } cpp_s2_intersects <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_intersects`, geog1, geog2, s2options) } cpp_s2_equals <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_equals`, geog1, geog2, s2options) } cpp_s2_contains <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_contains`, geog1, geog2, s2options) } cpp_s2_touches <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_touches`, geog1, geog2, s2options) } cpp_s2_dwithin <- function(geog1, geog2, distance) { .Call(`_s2_cpp_s2_dwithin`, geog1, geog2, distance) } cpp_s2_prepared_dwithin <- function(geog1, geog2, distance) { .Call(`_s2_cpp_s2_prepared_dwithin`, geog1, geog2, distance) } cpp_s2_intersects_box <- function(geog, lng1, lat1, lng2, lat2, detail, s2options) { .Call(`_s2_cpp_s2_intersects_box`, geog, lng1, lat1, lng2, lat2, detail, s2options) } cpp_s2_intersection <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_intersection`, geog1, geog2, s2options) } cpp_s2_union <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_union`, geog1, geog2, s2options) } cpp_s2_difference <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_difference`, geog1, geog2, s2options) } cpp_s2_sym_difference <- function(geog1, geog2, s2options) { .Call(`_s2_cpp_s2_sym_difference`, geog1, geog2, s2options) } cpp_s2_coverage_union_agg <- function(geog, s2options, naRm) { .Call(`_s2_cpp_s2_coverage_union_agg`, geog, s2options, naRm) } cpp_s2_union_agg <- function(geog, s2options, naRm) { .Call(`_s2_cpp_s2_union_agg`, geog, s2options, naRm) } cpp_s2_centroid_agg <- function(geog, naRm) { .Call(`_s2_cpp_s2_centroid_agg`, geog, naRm) } cpp_s2_rebuild_agg <- function(geog, s2options, naRm) { .Call(`_s2_cpp_s2_rebuild_agg`, geog, s2options, naRm) } cpp_s2_closest_point <- function(geog1, geog2) { .Call(`_s2_cpp_s2_closest_point`, geog1, geog2) } cpp_s2_minimum_clearance_line_between <- function(geog1, geog2) { .Call(`_s2_cpp_s2_minimum_clearance_line_between`, geog1, geog2) } cpp_s2_centroid <- function(geog) { .Call(`_s2_cpp_s2_centroid`, geog) } cpp_s2_point_on_surface <- function(geog) { .Call(`_s2_cpp_s2_point_on_surface`, geog) } cpp_s2_boundary <- function(geog) { .Call(`_s2_cpp_s2_boundary`, geog) } cpp_s2_rebuild <- function(geog, s2options) { .Call(`_s2_cpp_s2_rebuild`, geog, s2options) } cpp_s2_unary_union <- function(geog, s2options) { .Call(`_s2_cpp_s2_unary_union`, geog, s2options) } cpp_s2_interpolate_normalized <- function(geog, distanceNormalized) { .Call(`_s2_cpp_s2_interpolate_normalized`, geog, distanceNormalized) } cpp_s2_buffer_cells <- function(geog, distance, maxCells, minLevel) { .Call(`_s2_cpp_s2_buffer_cells`, geog, distance, maxCells, minLevel) } cpp_s2_convex_hull <- function(geog) { .Call(`_s2_cpp_s2_convex_hull`, geog) } cpp_s2_convex_hull_agg <- function(geog, naRm) { .Call(`_s2_cpp_s2_convex_hull_agg`, geog, naRm) } s2/R/s2-constructors-formatters.R0000644000176200001440000001510514530411473016404 0ustar liggesusers #' Create and Format Geography Vectors #' #' These functions create and export [geography vectors][as_s2_geography]. #' Unlike the BigQuery geography constructors, these functions do not sanitize #' invalid or redundant input using [s2_union()]. Note that when creating polygons #' using [s2_make_polygon()], rings can be open or closed. #' #' @inheritParams s2_is_collection #' @inheritParams as_s2_geography #' @param precision The number of significant digits to export when #' writing well-known text. If `trim = FALSE`, the number of #' digits after the decimal place. #' @param trim Should trailing zeroes be included after the decimal place? #' @param endian The endian-ness of the well-known binary. See [wk::wkb_translate_wkb()]. #' @param longitude,latitude Vectors of latitude and longitude #' @param wkt_string Well-known text #' @param wkb_bytes A `list()` of `raw()` #' @param planar Use `TRUE` to force planar edges in import or export. #' @param tessellate_tol_m The maximum number of meters to that a point must #' be moved to satisfy the planar edge constraint. #' @param feature_id,ring_id Vectors for which a change in #' sequential values indicates a new feature or ring. Use [factor()] #' to convert from a character vector. #' #' @export #' #' @seealso #' See [as_s2_geography()] for other ways to construct geography vectors. #' #' BigQuery's geography function reference: #' #' - [ST_GEOGPOINT](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_geogpoint) #' - [ST_MAKELINE](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_makeline) #' - [ST_MAKEPOLYGON](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_makepolygon) #' - [ST_GEOGFROMTEXT](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_geogfromtext) #' - [ST_GEOGFROMWKB](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_geogfromwkb) #' - [ST_ASTEXT](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_astext) #' - [ST_ASBINARY](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_asbinary) #' #' @examples #' # create point geographies using coordinate values: #' s2_geog_point(-64, 45) #' #' # create line geographies using coordinate values: #' s2_make_line(c(-64, 8), c(45, 71)) #' #' # optionally, separate features using feature_id: #' s2_make_line( #' c(-64, 8, -27, -27), c(45, 71, 0, 45), #' feature_id = c(1, 1, 2, 2) #' ) #' #' # create polygon geographies using coordinate values: #' # (rings can be open or closed) #' s2_make_polygon(c(-45, 8, 0), c(64, 71, 90)) #' #' # optionally, separate rings and/or features using #' # ring_id and/or feature_id #' s2_make_polygon( #' c(20, 10, 10, 30, 45, 30, 20, 20, 40, 20, 45), #' c(35, 30, 10, 5, 20, 20, 15, 25, 40, 45, 30), #' feature_id = c(rep(1, 8), rep(2, 3)), #' ring_id = c(1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1) #' ) #' #' # import and export well-known text #' (geog <- s2_geog_from_text("POINT (-64 45)")) #' s2_as_text(geog) #' #' # import and export well-known binary #' (geog <- s2_geog_from_wkb(wk::as_wkb("POINT (-64 45)"))) #' s2_as_binary(geog) #' #' # import geometry from planar space #' s2_geog_from_text( #' "POLYGON ((0 0, 1 0, 0 1, 0 0))", #' planar = TRUE, #' tessellate_tol_m = 1 #' ) #' #' # export geographies into planar space #' geog <- s2_make_polygon(c(179, -179, 179), c(10, 10, 11)) #' s2_as_text(geog, planar = TRUE) #' #' # polygons containing a pole need an extra step #' geog <- s2_data_countries("Antarctica") #' geom <- s2_as_text( #' s2_intersection(geog, s2_world_plate_carree()), #' planar = TRUE #' ) #' s2_geog_point <- function(longitude, latitude) { wk::wk_handle(wk::xy(longitude, latitude), s2_geography_writer()) } #' @rdname s2_geog_point #' @export s2_make_line <- function(longitude, latitude, feature_id = 1L) { wk::wk_handle( wk::xy(longitude, latitude), wk::wk_linestring_filter( s2_geography_writer(), feature_id = as.integer(feature_id) ) ) } #' @rdname s2_geog_point #' @export s2_make_polygon <- function(longitude, latitude, feature_id = 1L, ring_id = 1L, oriented = FALSE, check = TRUE) { wk::wk_handle( wk::xy(longitude, latitude), wk::wk_polygon_filter( s2_geography_writer(oriented = oriented, check = check), feature_id = as.integer(feature_id), ring_id = as.integer(ring_id) ) ) } #' @rdname s2_geog_point #' @export s2_geog_from_text <- function(wkt_string, oriented = FALSE, check = TRUE, planar = FALSE, tessellate_tol_m = s2_tessellate_tol_default()) { attributes(wkt_string) <- NULL wkt <- wk::new_wk_wkt(wkt_string, geodesic = TRUE) wk::validate_wk_wkt(wkt) wk::wk_handle( wkt, s2_geography_writer( oriented = oriented, check = check, tessellate_tol = if (planar) { tessellate_tol_m / s2_earth_radius_meters() } else { Inf } ) ) } #' @rdname s2_geog_point #' @export s2_geog_from_wkb <- function(wkb_bytes, oriented = FALSE, check = TRUE, planar = FALSE, tessellate_tol_m = s2_tessellate_tol_default()) { attributes(wkb_bytes) <- NULL wkb <- wk::new_wk_wkb(wkb_bytes) wk::validate_wk_wkb(wkb) wk::wk_handle( wkb, s2_geography_writer( oriented = oriented, check = check, tessellate_tol = if (planar) { tessellate_tol_m / s2_earth_radius_meters() } else { Inf } ) ) } #' @rdname s2_geog_point #' @export s2_as_text <- function(x, precision = 16, trim = TRUE, planar = FALSE, tessellate_tol_m = s2_tessellate_tol_default()) { wkt <- wk::wk_handle( as_s2_geography(x), wk::wkt_writer(precision = precision, trim = trim), s2_tessellate_tol = if (planar) { tessellate_tol_m / s2_earth_radius_meters() } else { Inf } ) attributes(wkt) <- NULL wkt } #' @rdname s2_geog_point #' @export s2_as_binary <- function(x, endian = wk::wk_platform_endian(), planar = FALSE, tessellate_tol_m = s2_tessellate_tol_default()) { structure( wk::wk_handle( as_s2_geography(x), wk::wkb_writer(endian = endian), s2_tessellate_tol = if (planar) { tessellate_tol_m / s2_earth_radius_meters() } else { Inf } ), class = "blob" ) } #' @rdname s2_geog_point #' @export s2_tessellate_tol_default <- function() { 100 } s2/R/s2-accessors.R0000644000176200001440000001306714530411473013442 0ustar liggesusers #' S2 Geography Accessors #' #' Accessors extract information about [geography vectors][as_s2_geography]. #' #' @param x,y [geography vectors][as_s2_geography]. These inputs #' are passed to [as_s2_geography()], so you can pass other objects #' (e.g., character vectors of well-known text) directly. #' @param radius Radius of the earth. Defaults to the average radius of #' the earth in meters as defined by [s2_earth_radius_meters()]. #' #' @export #' #' @seealso #' BigQuery's geography function reference: #' #' - [ST_ISCOLLECTION](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_iscollection) #' - [ST_DIMENSION](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_dimension) #' - [ST_NUMPOINTS](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_numpoints) #' - [ST_ISEMPTY](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_isempty) #' - [ST_AREA](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_area) #' - [ST_LENGTH](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_length) #' - [ST_PERIMETER](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_perimeter) #' - [ST_X](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_x) #' - [ST_Y](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_y) #' - [ST_DISTANCE](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_distance) #' - [ST_MAXDISTANCE](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_maxdistance) #' #' @examples #' # s2_is_collection() tests for multiple geometries in one feature #' s2_is_collection(c("POINT (-64 45)", "MULTIPOINT ((-64 45), (8 72))")) #' #' # s2_dimension() returns 0 for point, 1 for line, 2 for polygon #' s2_dimension( #' c( #' "GEOMETRYCOLLECTION EMPTY", #' "POINT (-64 45)", #' "LINESTRING (-64 45, 8 72)", #' "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))", #' "GEOMETRYCOLLECTION (POINT (-64 45), LINESTRING (-64 45, 8 72))" #' ) #' ) #' #' # s2_num_points() counts points #' s2_num_points(c("POINT (-64 45)", "LINESTRING (-64 45, 8 72)")) #' #' # s2_is_empty tests for emptiness #' s2_is_empty(c("POINT (-64 45)", "POINT EMPTY")) #' #' # calculate area, length, and perimeter #' s2_area("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))") #' s2_perimeter("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))") #' s2_length(s2_boundary("POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))")) #' #' # extract x and y coordinates from points #' s2_x(c("POINT (-64 45)", "POINT EMPTY")) #' s2_y(c("POINT (-64 45)", "POINT EMPTY")) #' #' # calculate minimum and maximum distance between two geometries #' s2_distance( #' "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))", #' "POINT (-64 45)" #' ) #' s2_max_distance( #' "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))", #' "POINT (-64 45)" #' ) #' s2_is_collection <- function(x) { cpp_s2_is_collection(as_s2_geography(x)) } #' @rdname s2_is_collection #' @export s2_is_valid <- function(x) { cpp_s2_is_valid(as_s2_geography(x, check = FALSE)) } #' @rdname s2_is_collection #' @export s2_is_valid_detail <- function(x) { x <- as_s2_geography(x, check = FALSE) data.frame( is_valid = cpp_s2_is_valid(x), reason = cpp_s2_is_valid_reason(x), stringsAsFactors = FALSE ) } #' @rdname s2_is_collection #' @export s2_dimension <- function(x) { cpp_s2_dimension(as_s2_geography(x)) } #' @rdname s2_is_collection #' @export s2_num_points <- function(x) { cpp_s2_num_points(as_s2_geography(x)) } #' @rdname s2_is_collection #' @export s2_is_empty <- function(x) { cpp_s2_is_empty(as_s2_geography(x)) } #' @rdname s2_is_collection #' @export s2_area <- function(x, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), radius) cpp_s2_area(recycled[[1]]) * radius ^ 2 } #' @rdname s2_is_collection #' @export s2_length <- function(x, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), radius) cpp_s2_length(recycled[[1]]) * radius } #' @rdname s2_is_collection #' @export s2_perimeter <- function(x, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), radius) cpp_s2_perimeter(recycled[[1]]) * radius } #' @rdname s2_is_collection #' @export s2_x <- function(x) { cpp_s2_x(as_s2_geography(x)) } #' @rdname s2_is_collection #' @export s2_y <- function(x) { cpp_s2_y(as_s2_geography(x)) } # document these with the other linear referencers #' @rdname s2_interpolate #' @export s2_project <- function(x, y, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y), radius) length <- cpp_s2_length(recycled[[1]]) * radius cpp_s2_project_normalized(recycled[[1]], recycled[[2]]) * length } #' @rdname s2_interpolate #' @export s2_project_normalized <- function(x, y) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) cpp_s2_project_normalized(recycled[[1]], recycled[[2]]) } #' @rdname s2_is_collection #' @export s2_distance <- function(x, y, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y), radius) cpp_s2_distance(recycled[[1]], recycled[[2]]) * radius } #' @rdname s2_is_collection #' @export s2_max_distance <- function(x, y, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y), radius) cpp_s2_max_distance(recycled[[1]], recycled[[2]]) * radius } s2/R/s2-geography.R0000644000176200001440000001314714530411473013441 0ustar liggesusers #' Create an S2 Geography Vector #' #' Geography vectors are arrays of points, lines, polygons, and/or collections #' of these. Geography vectors assume coordinates are longitude and latitude #' on a perfect sphere. #' #' The coercion function [as_s2_geography()] is used to wrap the input #' of most functions in the s2 package so that you can use other objects with #' an unambiguious interpretation as a geography vector. Geography vectors #' have a minimal [vctrs][vctrs::vctrs-package] implementation, so you can #' use these objects in tibble, dplyr, and other packages that use the vctrs #' framework. #' #' @param x An object that can be converted to an s2_geography vector #' @param oriented TRUE if polygon ring directions are known to be correct #' (i.e., exterior rings are defined counter clockwise and interior #' rings are defined clockwise). #' @param check Use `check = FALSE` to skip error on invalid geometries #' @param ... Unused #' #' @return An object with class s2_geography #' @export #' #' @seealso #' [s2_geog_from_wkb()], [s2_geog_from_text()], [s2_geog_point()], #' [s2_make_line()], [s2_make_polygon()] for other ways to #' create geography vectors, and [s2_as_binary()] and [s2_as_text()] #' for other ways to export them. #' as_s2_geography <- function(x, ...) { UseMethod("as_s2_geography") } #' @rdname as_s2_geography #' @export s2_geography <- function() { new_s2_geography(list()) } #' @rdname as_s2_geography #' @export as_s2_geography.s2_geography <- function(x, ...) { x } #' @rdname as_s2_geography #' @export as_s2_geography.wk_xy <- function(x, ...) { x <- as_s2_lnglat(x) df <- unclass(x) s2_geog_point(df[[1]], df[[2]]) } #' @rdname as_s2_geography #' @export as_s2_geography.wk_wkb <- function(x, ..., oriented = FALSE, check = TRUE) { if (identical(wk::wk_is_geodesic(x), FALSE)) { # points and an empty vector are OK and shouldn't trigger an error meta <- wk::wk_meta(x) if (!all(meta$geometry_type %in% c(1, 4, NA), na.rm = TRUE)) { stop( paste0( "Can't create s2_geography from Cartesian wkb().\n", "Use `wk_set_geodesic(x, TRUE)` to assert that edges can be\n", "interpolated along the sphere." ), call. = FALSE ) } } wk::wk_handle( x, s2_geography_writer(oriented = oriented, check = check) ) } #' @rdname as_s2_geography #' @export as_s2_geography.WKB <- function(x, ..., oriented = FALSE, check = TRUE) { s2_geog_from_wkb(x, oriented = oriented, check = check) } #' @rdname as_s2_geography #' @export as_s2_geography.blob <- function(x, ..., oriented = FALSE, check = TRUE) { s2_geog_from_wkb(x, oriented = oriented, check = check) } #' @rdname as_s2_geography #' @export as_s2_geography.wk_wkt <- function(x, ..., oriented = FALSE, check = TRUE) { if (identical(wk::wk_is_geodesic(x), FALSE)) { # points and an empty vector are OK and shouldn't trigger an error meta <- wk::wk_meta(x) if (!all(meta$geometry_type %in% c(1, 4, NA), na.rm = TRUE)) { stop( paste0( "Can't create s2_geography from Cartesian wkt().\n", "Use `wk_set_geodesic(x, TRUE)` to assert that edges can be\n", "interpolated along the sphere." ), call. = FALSE ) } } wk::wk_handle( x, s2_geography_writer(oriented = oriented, check = check) ) } #' @rdname as_s2_geography #' @export as_s2_geography.character <- function(x, ..., oriented = FALSE, check = TRUE) { s2_geog_from_text(x, oriented = oriented, check = check) } #' @rdname as_s2_geography #' @export as_s2_geography.logical <- function(x, ...) { stopifnot(isTRUE(x)) new_s2_geography(s2_geography_full(TRUE)) } #' @importFrom wk as_wkb #' @rdname as_s2_geography #' @export as_wkb.s2_geography <- function(x, ...) { wkb <- wk::wk_handle(x, wk::wkb_writer()) wk::wk_is_geodesic(wkb) <- TRUE wk::wk_crs(wkb) <- wk::wk_crs_longlat() wkb } #' @importFrom wk as_wkt #' @rdname as_s2_geography #' @export as_wkt.s2_geography <- function(x, ...) { wkt <- wk::wk_handle(x, wk::wkt_writer()) wk::wk_is_geodesic(wkt) <- TRUE wk::wk_crs(wkt) <- wk::wk_crs_longlat() wkt } #' @importFrom wk wk_crs #' @export wk_crs.s2_geography <- function(x) { wk::wk_crs_longlat() } #' @importFrom wk wk_set_crs #' @export wk_set_crs.s2_geography <- function(x, crs) { if (!wk::wk_crs_equal(crs, wk::wk_crs(x))) { warning("Setting the crs of s2_geography() is not supported") } x } #' @importFrom wk wk_is_geodesic #' @export wk_is_geodesic.s2_geography <- function(x) { TRUE } #' @importFrom wk wk_set_geodesic #' @export wk_set_geodesic.s2_geography <- function(x, geodesic) { if (!isTRUE(geodesic)) { stop("Can't set geodesic of s2_geography to FALSE") } x } new_s2_geography <- function(x) { structure(x, class = c("s2_geography", "wk_vctr")) } #' @export is.na.s2_geography <- function(x) { cpp_s2_geography_is_na(x) } #' @export `[<-.s2_geography` <- function(x, i, value) { x <- unclass(x) x[i] <- as_s2_geography(value) new_s2_geography(x) } #' @export `[[<-.s2_geography` <- function(x, i, value) { x <- unclass(x) x[i] <- as_s2_geography(value) new_s2_geography(x) } #' @export format.s2_geography <- function(x, ..., max_coords = 5, precision = 9, trim = TRUE) { wk::wk_format(x, precision = precision, max_coords = max_coords, trim = trim) } # this is what gets called by the RStudio viewer, for which # format() is best suited (s2_as_text() is more explicit for WKT output) #' @export as.character.s2_geography <- function(x, ..., max_coords = 5, precision = 9, trim = TRUE) { format(x, ..., max_coords = max_coords, precision = precision, trim = trim) } s2/R/vctrs.R0000644000176200001440000000111214530411473012260 0ustar liggesusers vec_proxy.s2_geography <- function(x, ...) { unclass(x) } vec_restore.s2_geography <- function(x, ...) { new_s2_geography(x) } vec_ptype_abbr.s2_geography <- function(x, ...) { "s2_geography" } vec_proxy.s2_cell <- function(x, ...) { unclass(x) } vec_restore.s2_cell <- function(x, ...) { new_s2_cell(x) } vec_ptype_abbr.s2_cell <- function(x, ...) { "s2cell" } vec_proxy.s2_cell_union <- function(x, ...) { unclass(x) } vec_restore.s2_cell_union <- function(x, ...) { new_s2_cell_union(x) } vec_ptype_abbr.s2_cell_union <- function(x, ...) { "s2cellunion" } s2/R/zzz.R0000644000176200001440000000340714530411473011765 0ustar liggesusers # nocov start .onLoad <- function(...) { # call c++ init cpp_s2_init() # dynamically register vctrs dependencies for (cls in c("s2_geography", "s2_cell", "s2_cell_union")) { s3_register("vctrs::vec_proxy", cls) s3_register("vctrs::vec_restore", cls) s3_register("vctrs::vec_ptype_abbr", cls) } s3_register("bit64::as.integer64", "s2_cell") } s3_register <- function(generic, class, method = NULL) { stopifnot(is.character(generic), length(generic) == 1) stopifnot(is.character(class), length(class) == 1) pieces <- strsplit(generic, "::")[[1]] stopifnot(length(pieces) == 2) package <- pieces[[1]] generic <- pieces[[2]] caller <- parent.frame() get_method_env <- function() { top <- topenv(caller) if (isNamespace(top)) { asNamespace(environmentName(top)) } else { caller } } get_method <- function(method, env) { if (is.null(method)) { get(paste0(generic, ".", class), envir = get_method_env()) } else { method } } method_fn <- get_method(method) stopifnot(is.function(method_fn)) # Always register hook in case package is later unloaded & reloaded setHook( packageEvent(package, "onLoad"), function(...) { ns <- asNamespace(package) # Refresh the method, it might have been updated by `devtools::load_all()` method_fn <- get_method(method) registerS3method(generic, class, method_fn, envir = ns) } ) # Avoid registration failures during loading (pkgload or regular) if (!isNamespaceLoaded(package)) { return(invisible()) } envir <- asNamespace(package) # Only register if generic can be accessed if (exists(generic, envir)) { registerS3method(generic, class, method_fn, envir = envir) } invisible() } # nocov end s2/R/s2-matrix.R0000644000176200001440000001650114530411473012755 0ustar liggesusers #' Matrix Functions #' #' These functions are similar to accessors and predicates, but instead of #' recycling `x` and `y` to a common length and returning a vector of that #' length, these functions return a vector of length `x` with each element #' `i` containing information about how the entire vector `y` relates to #' the feature at `x[i]`. #' #' @inheritParams s2_is_collection #' @inheritParams s2_contains #' @param x,y Geography vectors, coerced using [as_s2_geography()]. #' `x` is considered the source, where as `y` is considered the target. #' @param k The number of closest edges to consider when searching. Note #' that in S2 a point is also considered an edge. #' @param min_distance The minimum distance to consider when searching for #' edges. This filter is applied after the search is complete (i.e., #' may cause fewer than `k` values to be returned). #' @param max_distance The maximum distance to consider when searching for #' edges. This filter is applied before the search. #' @param max_edges_per_cell For [s2_may_intersect_matrix()], #' this values controls the nature of the index on `y`, with higher values #' leading to coarser index. Values should be between 10 and 50; the default #' of 50 is adequate for most use cases, but for specialized operations users #' may wish to use a lower value to increase performance. #' @param max_feature_cells For [s2_may_intersect_matrix()], this value #' controls the approximation of `x` used to identify potential intersections #' on `y`. The default value of 4 gives the best performance for most operations, #' but for specialized operations users may wish to use a higher value to increase #' performance. #' #' @return A vector of length `x`. #' @export #' #' @seealso #' See pairwise predicate functions (e.g., [s2_intersects()]). #' #' @examples #' city_names <- c("Vatican City", "San Marino", "Luxembourg") #' cities <- s2_data_cities(city_names) #' country_names <- s2_data_tbl_countries$name #' countries <- s2_data_countries() #' #' # closest feature returns y indices of the closest feature #' # for each feature in x #' country_names[s2_closest_feature(cities, countries)] #' #' # farthest feature returns y indices of the farthest feature #' # for each feature in x #' country_names[s2_farthest_feature(cities, countries)] #' #' # use s2_closest_edges() to find the k-nearest neighbours #' nearest <- s2_closest_edges(cities, cities, k = 2, min_distance = 0) #' city_names #' city_names[unlist(nearest)] #' #' # predicate matrices #' country_names[s2_intersects_matrix(cities, countries)[[1]]] #' #' # distance matrices #' s2_distance_matrix(cities, cities) #' s2_max_distance_matrix(cities, countries[1:4]) #' s2_closest_feature <- function(x, y) { cpp_s2_closest_feature(as_s2_geography(x), as_s2_geography(y)) } #' @rdname s2_closest_feature #' @export s2_closest_edges <- function(x, y, k, min_distance = -1, max_distance = Inf, radius = s2_earth_radius_meters()) { stopifnot(k >= 1) cpp_s2_closest_edges( as_s2_geography(x), as_s2_geography(y), k, min_distance / radius, max_distance / radius ) } #' @rdname s2_closest_feature #' @export s2_farthest_feature <- function(x, y) { cpp_s2_farthest_feature(as_s2_geography(x), as_s2_geography(y)) } #' @rdname s2_closest_feature #' @export s2_distance_matrix <- function(x, y, radius = s2_earth_radius_meters()) { cpp_s2_distance_matrix(as_s2_geography(x), as_s2_geography(y)) * radius } #' @rdname s2_closest_feature #' @export s2_max_distance_matrix <- function(x, y, radius = s2_earth_radius_meters()) { cpp_s2_max_distance_matrix(as_s2_geography(x), as_s2_geography(y)) * radius } #' @rdname s2_closest_feature #' @export s2_contains_matrix <- function(x, y, options = s2_options(model = "open")) { cpp_s2_contains_matrix(as_s2_geography(x), as_s2_geography(y), options) } #' @rdname s2_closest_feature #' @export s2_within_matrix <- function(x, y, options = s2_options(model = "open")) { cpp_s2_within_matrix(as_s2_geography(x), as_s2_geography(y), options) } #' @rdname s2_closest_feature #' @export s2_covers_matrix <- function(x, y, options = s2_options(model = "closed")) { cpp_s2_contains_matrix(as_s2_geography(x), as_s2_geography(y), options) } #' @rdname s2_closest_feature #' @export s2_covered_by_matrix <- function(x, y, options = s2_options(model = "closed")) { cpp_s2_within_matrix(as_s2_geography(x), as_s2_geography(y), options) } #' @rdname s2_closest_feature #' @export s2_intersects_matrix <- function(x, y, options = s2_options()) { cpp_s2_intersects_matrix(as_s2_geography(x), as_s2_geography(y), options) } #' @rdname s2_closest_feature #' @export s2_disjoint_matrix <- function(x, y, options = s2_options()) { # disjoint is the odd one out, in that it requires a negation of intersects # this is inconvenient to do on the C++ level, and is easier to maintain # with setdiff() here (unless somebody complains that this is slow) intersection <- cpp_s2_intersects_matrix(as_s2_geography(x), as_s2_geography(y), options) Map(setdiff, list(seq_along(y)), intersection) } #' @rdname s2_closest_feature #' @export s2_equals_matrix <- function(x, y, options = s2_options()) { cpp_s2_equals_matrix(as_s2_geography(x), as_s2_geography(y), options) } #' @rdname s2_closest_feature #' @export s2_touches_matrix <- function(x, y, options = s2_options()) { cpp_s2_touches_matrix(as_s2_geography(x), as_s2_geography(y), options) } #' @rdname s2_closest_feature #' @export s2_dwithin_matrix <- function(x, y, distance, radius = s2_earth_radius_meters()) { cpp_s2_dwithin_matrix(as_s2_geography(x), as_s2_geography(y), distance / radius) } #' @rdname s2_closest_feature #' @export s2_may_intersect_matrix <- function(x, y, max_edges_per_cell = 50, max_feature_cells = 4) { cpp_s2_may_intersect_matrix( as_s2_geography(x), as_s2_geography(y), max_edges_per_cell, max_feature_cells, s2_options() ) } # ------- for testing, non-indexed versions of matrix operators ------- s2_contains_matrix_brute_force <- function(x, y, options = s2_options()) { cpp_s2_contains_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options) } s2_within_matrix_brute_force <- function(x, y, options = s2_options()) { cpp_s2_within_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options) } s2_covers_matrix_brute_force <- function(x, y, options = s2_options(model = "closed")) { cpp_s2_contains_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options) } s2_covered_by_matrix_brute_force <- function(x, y, options = s2_options(model = "closed")) { cpp_s2_within_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options) } s2_intersects_matrix_brute_force <- function(x, y, options = s2_options()) { cpp_s2_intersects_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options) } s2_disjoint_matrix_brute_force <- function(x, y, options = s2_options()) { cpp_s2_disjoint_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options) } s2_equals_matrix_brute_force <- function(x, y, options = s2_options()) { cpp_s2_equals_matrix_brute_force(as_s2_geography(x), as_s2_geography(y), options) } s2_dwithin_matrix_brute_force <- function(x, y, distance, radius = s2_earth_radius_meters()) { cpp_s2_dwithin_matrix_brute_force( as_s2_geography(x), as_s2_geography(y), distance / radius ) } s2/R/s2-cell-union.R0000644000176200001440000001170014530411473013512 0ustar liggesusers #' Create S2 Cell Union vectors #' #' @param x A `list()` of [s2_cell()] vectors. #' @param ... Passed to S3 methods #' #' @return An object of class "s2_cell_union". #' @export #' s2_cell_union <- function(x = list()) { x <- as.list(x) input_na <- vapply(x, is.null, logical(1)) union <- vector("list", length(x)) union[input_na] <- list(NULL) union[!input_na] <- lapply(x[!input_na], as_s2_cell) new_s2_cell_union(union) } #' @rdname s2_cell_union #' @export as_s2_geography.s2_cell_union <- function(x, ...) { new_s2_geography(cpp_s2_geography_from_cell_union(as_s2_cell_union(x))) } #' @rdname s2_cell_union #' @export as_s2_cell_union <- function(x, ...) { UseMethod("as_s2_cell_union") } #' @rdname s2_cell_union #' @export as_s2_cell_union.s2_cell_union <- function(x, ...) { x } #' @rdname s2_cell_union #' @export as_s2_cell_union.s2_cell <- function(x, ...) { cpp_s2_cell_to_cell_union(x) } #' @rdname s2_cell_union #' @export as_s2_cell_union.character <- function(x, ...) { split <- strsplit(x, "\\s*;\\s*") split[is.na(x)] <- list(NULL) s2_cell_union(split) } new_s2_cell_union <- function(x) { stopifnot(typeof(x) == "list") structure(x, class = c("s2_cell_union", "wk_vctr")) } #' @export is.na.s2_cell_union <- function(x, ...) { cpp_s2_cell_union_is_na(x) } #' @export format.s2_cell_union <- function(x, ...) { formatted <- vapply( unclass(x), function(e) paste0(as.character(e), collapse = ";"), character(1) ) formatted[is.na(x)] <- "" formatted } #' @export as.character.s2_cell_union <- function(x, ...) { formatted <- vapply( unclass(x), function(e) paste0(as.character(e), collapse = ";"), character(1) ) formatted[is.na(x)] <- NA_character_ formatted } #' @export print.s2_cell_union <- function(x, ...) { utils::str(x, ...) invisible(x) } #' @method unlist s2_cell_union #' @export unlist.s2_cell_union <- function(x, recursive = TRUE, use.names = TRUE) { unlisted <- unlist(unclass(x), recursive = recursive, use.names = use.names) new_s2_cell(as.double(unlisted)) } #' @importFrom utils str #' @export str.s2_cell_union <- function(object, ..., indent.str = "") { cat(sprintf("%s\n%s", indent.str, length(object), indent.str)) str(unclass(object), ..., indent.str = indent.str) invisible(object) } #' S2 cell union operators #' #' @param x,y An [s2_geography][as_s2_geography] or [s2_cell_union()]. #' @param min_level,max_level The minimum and maximum levels to constrain the #' covering. #' @param max_cells The maximum number of cells in the covering. Defaults to #' 8. #' @param buffer A distance to buffer outside the geography #' @param interior Use `TRUE` to force the covering inside the geography. #' @inheritParams s2_cell_is_valid #' #' @export #' s2_cell_union_normalize <- function(x) { cpp_s2_cell_union_normalize(as_s2_cell_union(x)) } #' @rdname s2_cell_union_normalize #' @export s2_cell_union_contains <- function(x, y) { if (inherits(y, "s2_cell")) { recycled <- recycle_common(as_s2_cell_union(x), y) cpp_s2_cell_union_contains_cell(recycled[[1]], recycled[[2]]) } else { cpp_s2_cell_union_contains(as_s2_cell_union(x), as_s2_cell_union(y)) } } #' @rdname s2_cell_union_normalize #' @export s2_cell_union_intersects <- function(x, y) { cpp_s2_cell_union_intersects(as_s2_cell_union(x), as_s2_cell_union(y)) } #' @rdname s2_cell_union_normalize #' @export s2_cell_union_intersection <- function(x, y) { cpp_s2_cell_union_intersection(as_s2_cell_union(x), as_s2_cell_union(y)) } #' @rdname s2_cell_union_normalize #' @export s2_cell_union_union <- function(x, y) { cpp_s2_cell_union_union(as_s2_cell_union(x), as_s2_cell_union(y)) } #' @rdname s2_cell_union_normalize #' @export s2_cell_union_difference <- function(x, y) { cpp_s2_cell_union_difference(as_s2_cell_union(x), as_s2_cell_union(y)) } #' @rdname s2_cell_union_normalize #' @export s2_covering_cell_ids <- function(x, min_level = 0, max_level = 30, max_cells = 8, buffer = 0, interior = FALSE, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), buffer / radius) cpp_s2_covering_cell_ids( recycled[[1]], min_level, max_level, max_cells, recycled[[2]], interior ) } #' @rdname s2_cell_union_normalize #' @export s2_covering_cell_ids_agg <- function(x, min_level = 0, max_level = 30, max_cells = 8, buffer = 0, interior = FALSE, radius = s2_earth_radius_meters(), na.rm = FALSE) { distance <- as.numeric(buffer / radius) stopifnot(length(distance) == 1) if (is.na(distance)) { return(new_s2_cell_union(list(NULL))) } cpp_s2_covering_cell_ids_agg( as_s2_geography(x), min_level, max_level, max_cells, distance, interior, na.rm ) } s2/R/utils.R0000644000176200001440000000440514530411473012267 0ustar liggesusers new_data_frame <- function(x) { structure(x, row.names = c(NA, length(x[[1]])), class = "data.frame") } recycle_common <- function(...) { dots <- list(...) lengths <- vapply(dots, length, integer(1)) non_constant_lengths <- unique(lengths[lengths != 1]) if (length(non_constant_lengths) == 0) { final_length <- 1 } else if(length(non_constant_lengths) == 1) { final_length <- non_constant_lengths } else { lengths_label <- paste0(non_constant_lengths, collapse = ", ") stop(sprintf("Incompatible lengths: %s", lengths_label)) } lapply(dots, rep_len, final_length) } # The problems object is generated when building or processing an s2_geography(): # instead of attaching to the object as an attribute, this function is # called from Rcpp if there were any problems to format them in a # human-readable way. Theoretically one could change this to only warn # instead of stop (error values are set to NA/NULL). stop_problems_create <- function(feature_id, problem) { n <- length(feature_id) feature_label <- if (n != 1) "features" else "feature" stop_problems( feature_id, problem, sprintf("Found %d %s with invalid spherical geometry.", n, feature_label) ) } stop_problems_process <- function(feature_id, problem) { n <- length(feature_id) error_label <- if (n != 1) "errors" else "error" stop_problems( feature_id, problem, sprintf("Encountered %d processing %s.", n, error_label) ) } stop_problems <- function(feature_id, problem, header) { n <- length(feature_id) if (n > 10) { feature_id <- feature_id[1:10] problem <- problem[1:10] more <- sprintf("\n...and %s more", n - 10) } else { more <- "" } msg <- paste0( header, "\n", paste0("[", feature_id + 1, "] ", problem , collapse = "\n"), more ) stop(msg, call. = FALSE) } expect_wkt_equal <- function(x, y, precision = 16) { testthat::expect_equal( wk::wk_format( as_s2_geography(x), precision = precision, trim = TRUE, max_coords = .Machine$integer.max ), wk::wk_format( as_s2_geography(y), precision = precision, trim = TRUE, max_coords = .Machine$integer.max ) ) } expect_near <- function(x, y, epsilon) { testthat::expect_true(abs(y - x) < epsilon) } s2/R/s2-bounds.R0000644000176200001440000000235514530411473012745 0ustar liggesusers #' Compute feature-wise and aggregate bounds #' #' [s2_bounds_rect()] returns a bounding latitude-longitude #' rectangle that contains the region; [s2_bounds_cap()] returns a bounding circle #' represented by a centre point (lat, lng) and an angle. The bound may not be tight #' for points, polylines and geometry collections. The rectangle returned may depend on #' the order of points or polylines. `lng_lo` values larger than `lng_hi` indicate #' regions that span the antimeridian, see the Fiji example. #' #' @param x An [s2_geography()] vector. #' @export #' @return Both functions return a `data.frame`: #' #' - [s2_bounds_rect()]: Columns `minlng`, `minlat`, `maxlng`, `maxlat` (degrees) #' - [s2_bounds_cap()]: Columns `lng`, `lat`, `angle` (degrees) #' #' @examples #' s2_bounds_cap(s2_data_countries("Antarctica")) #' s2_bounds_cap(s2_data_countries("Netherlands")) #' s2_bounds_cap(s2_data_countries("Fiji")) #' #' s2_bounds_rect(s2_data_countries("Antarctica")) #' s2_bounds_rect(s2_data_countries("Netherlands")) #' s2_bounds_rect(s2_data_countries("Fiji")) #' s2_bounds_cap <- function(x) { cpp_s2_bounds_cap(as_s2_geography(x)) } #' @rdname s2_bounds_cap #' @export s2_bounds_rect <- function(x) { cpp_s2_bounds_rect(as_s2_geography(x)) } s2/R/wk-utils.R0000644000176200001440000000760514530411473012713 0ustar liggesusers #' Low-level wk filters and handlers #' #' @inheritParams wk::wk_handle #' @param projection,s2_projection One of [s2_projection_plate_carree()] or #' [s2_projection_mercator()] #' @param tessellate_tol,s2_tessellate_tol An angle in radians. #' Points will not be added if a line segment is within this #' distance of a point. #' @param x_scale The maximum x value of the projection #' @param centre The center point of the orthographic projection #' @param epsilon_east_west,epsilon_north_south Use a positive number to #' define the edges of a Cartesian world slightly inward from -180, -90, #' 180, 90. This may be used to define a world outline for a projection where #' projecting at the extreme edges of the earth results in a non-finite value. #' @inheritParams as_s2_geography #' #' @return #' - `s2_projection_plate_carree()`, `s2_projection_mercator()`: An external pointer #' to an S2 projection. #' @importFrom wk wk_handle #' @export #' wk_handle.s2_geography <- function(handleable, handler, ..., s2_projection = s2_projection_plate_carree(), s2_tessellate_tol = Inf) { stopifnot(is.null(s2_projection) || inherits(s2_projection, "s2_projection")) attr(handleable, "s2_projection") <- s2_projection if (identical(s2_tessellate_tol, Inf)) { .Call(c_s2_handle_geography, handleable, wk::as_wk_handler(handler)) } else { attr(handleable, "s2_tessellate_tol") <- as.double(s2_tessellate_tol)[1] .Call(c_s2_handle_geography_tessellated, handleable, wk::as_wk_handler(handler)) } } #' @rdname wk_handle.s2_geography #' @export s2_geography_writer <- function(oriented = FALSE, check = TRUE, projection = s2_projection_plate_carree(), tessellate_tol = Inf) { stopifnot(is.null(projection) || inherits(projection, "s2_projection")) wk::new_wk_handler( .Call( c_s2_geography_writer_new, as.logical(oriented)[1], as.logical(check)[1], projection, as.double(tessellate_tol[1]) ), "s2_geography_writer" ) } #' @rdname wk_handle.s2_geography #' @importFrom wk wk_writer #' @method wk_writer s2_geography #' @export wk_writer.s2_geography <- function(handleable, ...) { s2_geography_writer() } #' @rdname wk_handle.s2_geography #' @export s2_trans_point <- function() { wk::new_wk_trans(.Call(c_s2_trans_s2_point_new)) } #' @rdname wk_handle.s2_geography #' @export s2_trans_lnglat <- function() { wk::new_wk_trans(.Call(c_s2_trans_s2_lnglat_new)) } #' @rdname wk_handle.s2_geography #' @export s2_projection_plate_carree <- function(x_scale = 180) { structure( .Call(c_s2_projection_plate_carree, as.double(x_scale)[1]), class = "s2_projection" ) } #' @rdname wk_handle.s2_geography #' @export s2_projection_mercator <- function(x_scale = 20037508.3427892) { structure( .Call(c_s2_projection_mercator, as.double(x_scale)[1]), class = "s2_projection" ) } #' @rdname wk_handle.s2_geography #' @export s2_hemisphere <- function(centre) { cap_to_polygon(centre, pi / 2) } #' @rdname wk_handle.s2_geography #' @export s2_world_plate_carree <- function(epsilon_east_west = 0, epsilon_north_south = 0) { s2_make_polygon( c( -180 + epsilon_east_west, 0, 180 - epsilon_east_west, 180 - epsilon_east_west, 180 - epsilon_east_west, 0, -180 + epsilon_east_west, -180 + epsilon_east_west ), c( -90 + epsilon_north_south, -90 + epsilon_north_south, -90 + epsilon_north_south, 0, 90 - epsilon_north_south, 90 - epsilon_north_south, 90 - epsilon_north_south, 0 ), oriented = TRUE ) } #' @rdname wk_handle.s2_geography #' @export s2_projection_orthographic <- function(centre = s2_lnglat(0, 0)) { centre <- as_s2_lnglat(centre) centre <- as.matrix(centre) structure( .Call(c_s2_projection_orthographic, centre[1:2]), class = "s2_projection" ) } s2/R/s2-options.R0000644000176200001440000001370614530411473013150 0ustar liggesusers #' Geography Operation Options #' #' These functions specify defaults for options used to perform operations #' and construct geometries. These are used in predicates (e.g., [s2_intersects()]), #' and boolean operations (e.g., [s2_intersection()]) to specify the model for #' containment and how new geometries should be constructed. #' #' @param model One of 'open', 'semi-open' (default for polygons), #' or 'closed' (default for polylines). See section 'Model' #' @param snap Use `s2_snap_identity()`, `s2_snap_distance()`, `s2_snap_level()`, #' or `s2_snap_precision()` to specify how or if coordinate rounding should #' occur. #' @param snap_radius As opposed to the snap function, which specifies #' the maximum distance a vertex should move, the snap radius (in radians) sets #' the minimum distance between vertices of the output that don't cause vertices #' to move more than the distance specified by the snap function. This can be used #' to simplify the result of a boolean operation. Use -1 to specify that any #' minimum distance is acceptable. #' @param duplicate_edges Use `TRUE` to keep duplicate edges (e.g., duplicate #' points). #' @param edge_type One of 'directed' (default) or 'undirected'. #' @param polyline_type One of 'path' (default) or 'walk'. If 'walk', #' polylines that backtrack are preserved. #' @param polyline_sibling_pairs One of 'discard' (default) or 'keep'. #' @param simplify_edge_chains Use `TRUE` to remove vertices that are within #' `snap_radius` of the original vertex. #' @param split_crossing_edges Use `TRUE` to split crossing polyline edges #' when creating geometries. #' @param idempotent Use `FALSE` to apply snap even if snapping is not necessary #' to satisfy vertex constraints. #' @param validate Use `TRUE` to validate the result from the builder. #' @param level A value from 0 to 30 corresponding to the cell level #' at which snapping should occur. #' @param distance A distance (in radians) denoting the maximum #' distance a vertex should move in the snapping process. #' @param precision A number by which coordinates should be multiplied #' before being rounded. Rounded to the nearest exponent of 10. #' @param dimensions A combination of 'point', 'polyline', and/or 'polygon' #' that can used to constrain the output of [s2_rebuild()] or a #' boolean operation. #' #' @section Model: #' The geometry model indicates whether or not a geometry includes its boundaries. #' Boundaries of line geometries are its end points. #' OPEN geometries do not contain their boundary (`model = "open"`); CLOSED #' geometries (`model = "closed"`) contain their boundary; SEMI-OPEN geometries #' (`model = "semi-open"`) contain half of their boundaries, such that when two polygons #' do not overlap or two lines do not cross, no point exist that belong to #' more than one of the geometries. (This latter form, half-closed, is #' not present in the OpenGIS "simple feature access" (SFA) standard nor DE9-IM on #' which that is based). The default values for [s2_contains()] (open) #' and covers/covered_by (closed) correspond to the SFA standard specification #' of these operators. #' #' @export #' #' @examples #' # use s2_options() to specify containment models, snap level #' # layer creation options, and builder options #' s2_options(model = "closed", snap = s2_snap_level(30)) #' s2_options <- function(model = NULL, snap = s2_snap_identity(), snap_radius = -1, duplicate_edges = FALSE, edge_type = "directed", validate = FALSE, polyline_type = "path", polyline_sibling_pairs = "keep", simplify_edge_chains = FALSE, split_crossing_edges = FALSE, idempotent = FALSE, dimensions = c("point", "polyline", "polygon")) { # check snap radius (passing in a huge snap radius can cause problems) if (snap_radius > 3) { stop( "Snap radius is too large. Did you pass in a snap radius in meters instead of radians?", call. = FALSE ) } structure( list( # model needs to be "unset" by default because there are differences in polygon # and polyline handling by default that are good defaults to preserve model = if (is.null(model)) -1 else match_option(model[1], c("open", "semi-open", "closed"), "model"), snap = snap, snap_radius = snap_radius, duplicate_edges = duplicate_edges, edge_type = match_option(edge_type[1], c("directed", "undirected"), "edge_type"), validate = validate, polyline_type = match_option(polyline_type[1], c("path", "walk"), "polyline_type"), polyline_sibling_pairs = match_option( polyline_sibling_pairs, c("discard", "keep"), "polyline_sibling_pairs" ), simplify_edge_chains = simplify_edge_chains, split_crossing_edges = split_crossing_edges, idempotent = idempotent, dimensions = match_option(dimensions, c("point", "polyline", "polygon"), "dimensions") ), class = "s2_options" ) } #' @rdname s2_options #' @export s2_snap_identity <- function() { structure(list(), class = "snap_identity") } #' @rdname s2_options #' @export s2_snap_level <- function(level) { if (level > 30) { stop("`level` must be an intger between 1 and 30", call. = FALSE) } structure(list(level = level), class = "snap_level") } #' @rdname s2_options #' @export s2_snap_precision <- function(precision) { structure(list(exponent = round(log10(precision))), class = "snap_precision") } #' @rdname s2_options #' @export s2_snap_distance <- function(distance) { structure(list(distance = distance), class = "snap_distance") } match_option <- function(x, options, arg) { result <- match(x, options) if (any(is.na(result))) { stop( sprintf("`%s` must be one of %s", arg, paste0('"', options, '"', collapse = ", ")), call. = FALSE ) } result } s2/R/s2-cell.R0000644000176200001440000001771414530411473012377 0ustar liggesusers #' Create S2 Cell vectors #' #' The S2 cell indexing system forms the basis for spatial indexing #' in the S2 library. On their own, S2 cells can represent points #' or areas. As a union, a vector of S2 cells can approximate a #' line or polygon. These functions allow direct access to the #' S2 cell indexing system and are designed to have minimal overhead #' such that looping and recursion have acceptable performance #' when used within R code. #' #' Under the hood, S2 cell vectors are represented in R as vectors #' of type [double()]. This works because S2 cell identifiers are #' 64 bits wide, as are `double`s on all systems where R runs (The #' same trick is used by the bit64 package to represent signed #' 64-bit integers). As a happy accident, `NA_real_` is not a valid #' or meaningful cell identifier, so missing value support in the #' way R users might expect is preserved. It is worth noting that #' the underlying value of `s2_cell_sentinel()` would normally be #' considered `NA`; however, as it is meaningful and useful when #' programming with S2 cells, custom `is.na()` and comparison methods #' are implemented such that `s2_cell_sentinel()` is greater than #' all valid S2 cells and not considered missing. Users can and should #' implement compiled code that uses the underlying bytes of the #' vector, ensuring that the class of any returned object that should #' be interpreted in this way is constructed with `new_s2_cell()`. #' #' @param x The canonical S2 cell identifier as a character vector. #' @param ... Passed to methods #' #' @return An object of class s2_cell #' @export #' #' @examples #' s2_cell("4b59a0cd83b5de49") #' as_s2_cell(s2_lnglat(-64, 45)) #' as_s2_cell(s2_data_cities("Ottawa")) #' s2_cell <- function(x = character()) { new_s2_cell(cpp_s2_cell_from_string(x)) } #' @rdname s2_cell #' @export s2_cell_sentinel <- function() { cpp_s2_cell_sentinel() } #' @rdname s2_cell #' @export s2_cell_invalid <- function() { new_s2_cell(0) } #' @rdname s2_cell #' @export as_s2_cell <- function(x, ...) { UseMethod("as_s2_cell") } #' @rdname s2_cell #' @export as_s2_cell.s2_cell <- function(x, ...) { x } #' @rdname s2_cell #' @export as_s2_cell.character <- function(x, ...) { s2_cell(x) } #' @rdname s2_cell #' @export as_s2_cell.s2_geography <- function(x, ...) { cpp_s2_cell_from_lnglat(list(s2_x(x), s2_y(x))) } #' @rdname s2_cell #' @export as_s2_cell.wk_xy <- function(x, ...) { cpp_s2_cell_from_lnglat(as_s2_lnglat(x)) } #' @rdname s2_cell #' @export as_s2_cell.integer64 <- function(x, ...) { storage <- unclass(x) storage[is.na(x)] <- NA_real_ new_s2_cell(storage) } #' @rdname s2_cell #' @export new_s2_cell <- function(x) { stopifnot(is.double(x)) structure(x, class = c("s2_cell", "wk_vctr")) } # registered in zzz.R as.integer64.s2_cell <- function(x, ...) { # We store 64-bit integegers the same way bit64 does so we can just set the # class attribute and propagate NA values in the way that bit64 expects them. x_is_na <- is.na(x) class(x) <- "integer64" x[x_is_na] <- bit64::NA_integer64_ x } #' @export as.character.s2_cell <- function(x, ...) { cpp_s2_cell_to_string(x) } #' @export format.s2_cell <- function(x, ...) { format(as.character(x), quote = FALSE, ...) } #' @export as.list.s2_cell <- function(x, ...) { lapply(NextMethod(), new_s2_cell) } #' @export `[<-.s2_cell` <- function(x, i, value) { replacement <- as_s2_cell(value) x <- unclass(x) x[i] <- replacement new_s2_cell(x) } #' @export `[[<-.s2_cell` <- function(x, i, value) { x[i] <- value x } #' @export unique.s2_cell <- function(x, ...) { cpp_s2_cell_unique(x) } #' @export sort.s2_cell <- function(x, decreasing = FALSE, ...) { cpp_s2_cell_sort(x, decreasing) } #' @export is.na.s2_cell <- function(x) { cpp_s2_cell_is_na(x) } #' @export is.numeric.s2_cell <- function(x, ...) { FALSE } #' @export Ops.s2_cell <- function(e1, e2) { switch( .Generic, "==" = cpp_s2_cell_eq(e1, e2), "!=" = cpp_s2_cell_neq(e1, e2), "<" = cpp_s2_cell_lt(e1, e2), "<=" = cpp_s2_cell_lte(e1, e2), ">=" = cpp_s2_cell_gte(e1, e2), ">" = cpp_s2_cell_gt(e1, e2), stop("Arithmetic operations are not meaningful for type 's2_cell'", call. = FALSE) ) } #' @export Math.s2_cell <- function(x, ...) { switch( .Generic, "cummax" = cpp_s2_cell_cummax(x), "cummin" = cpp_s2_cell_cummin(x), stop("Arithmetic operations are not meaningful for type 's2_cell'", call. = FALSE) ) } #' @export Summary.s2_cell <- function(x, ..., na.rm = FALSE) { switch( .Generic, "min" = cpp_s2_cell_range(x, na.rm)[1], "max" = cpp_s2_cell_range(x, na.rm)[2], "range" = cpp_s2_cell_range(x, na.rm), stop("Arithmetic operations are not meaningful for type 's2_cell'", call. = FALSE) ) } #' S2 cell operators #' #' @param x,y An [s2_cell()] vector #' @param level An integer between 0 and 30, inclusive. #' @param k An integer between 0 and 3 #' @param radius The radius to use (e.g., [s2_earth_radius_meters()]) #' @param na.rm Remove NAs prior to computing aggregate? #' @export #' s2_cell_is_valid <- function(x) { cpp_s2_cell_is_valid(x) } # exporters #' @rdname s2_cell_is_valid #' @export s2_cell_debug_string <- function(x) { cpp_s2_cell_debug_string(x) } #' @rdname s2_cell_is_valid #' @export s2_cell_to_lnglat <- function(x) { lnglat <- cpp_s2_cell_to_lnglat(x) s2_lnglat(lnglat[[1]], lnglat[[2]]) } #' @rdname s2_cell_is_valid #' @export s2_cell_center <- function(x) { cpp_s2_cell_center(x) } #' @rdname s2_cell_is_valid #' @export s2_cell_boundary <- function(x) { s2_boundary(cpp_s2_cell_polygon(x)) } #' @rdname s2_cell_is_valid #' @export s2_cell_polygon <- function(x) { cpp_s2_cell_polygon(x) } #' @rdname s2_cell_is_valid #' @export s2_cell_vertex <- function(x, k) { recycled <- recycle_common(x, k) cpp_s2_cell_vertex(recycled[[1]], recycled[[2]]) } # accessors #' @rdname s2_cell_is_valid #' @export s2_cell_level <- function(x) { cpp_s2_cell_level(x) } #' @rdname s2_cell_is_valid #' @export s2_cell_is_leaf <- function(x) { s2_cell_level(x) == 30L } #' @rdname s2_cell_is_valid #' @export s2_cell_is_face <- function(x) { s2_cell_level(x) == 0L } #' @rdname s2_cell_is_valid #' @export s2_cell_area <- function(x, radius = s2_earth_radius_meters()) { cpp_s2_cell_area(x) * radius ^ 2 } #' @rdname s2_cell_is_valid #' @export s2_cell_area_approx <- function(x, radius = s2_earth_radius_meters()) { cpp_s2_cell_area_approx(x) * radius ^ 2 } # transversers #' @rdname s2_cell_is_valid #' @export s2_cell_parent <- function(x, level = -1L) { recycled <- recycle_common(x, level) cpp_s2_cell_parent(recycled[[1]], recycled[[2]]) } #' @rdname s2_cell_is_valid #' @export s2_cell_child <- function(x, k) { recycled <- recycle_common(x, k) cpp_s2_cell_child(recycled[[1]], recycled[[2]]) } #' @rdname s2_cell_is_valid #' @export s2_cell_edge_neighbour <- function(x, k) { recycled <- recycle_common(x, k) cpp_s2_cell_edge_neighbour(recycled[[1]], recycled[[2]]) } # binary operators #' @rdname s2_cell_is_valid #' @export s2_cell_contains <- function(x, y) { cpp_s2_cell_contains(x, y) } #' @rdname s2_cell_is_valid #' @export s2_cell_distance <- function(x, y, radius = s2_earth_radius_meters()) { recycled <- recycle_common(x, y, radius) cpp_s2_cell_distance(recycled[[1]], recycled[[2]]) * radius } #' @rdname s2_cell_is_valid #' @export s2_cell_max_distance <- function(x, y, radius = s2_earth_radius_meters()) { recycled <- recycle_common(x, y, radius) cpp_s2_cell_max_distance(recycled[[1]], recycled[[2]]) * radius } #' @rdname s2_cell_is_valid #' @export s2_cell_may_intersect <- function(x, y) { cpp_s2_cell_may_intersect(x, y) } #' @rdname s2_cell_is_valid #' @export s2_cell_common_ancestor_level <- function(x, y) { cpp_s2_cell_common_ancestor_level(x, y) } #' @rdname s2_cell_is_valid #' @export s2_cell_common_ancestor_level_agg <- function(x, na.rm = FALSE) { x_na <- is.na(x) if (any(x_na) && !na.rm) { return(NA_integer_) } cpp_s2_cell_common_ancestor_level_agg(x[!x_na]) } s2/R/data.R0000644000176200001440000000367414530411473012047 0ustar liggesusers #' Low-resolution world boundaries, timezones, and cities #' #' Well-known binary versions of the [Natural Earth](https://www.naturalearthdata.com/) #' low-resolution world boundaries and timezone boundaries. #' #' @param name The name of a country, continent, city, or `NULL` #' for all features. #' @param utc_offset_min,utc_offset_max Minimum and/or maximum timezone #' offsets. #' #' @format A data.frame with columns `name` (character), and #' `geometry` (wk_wkb) #' @source [Natural Earth Data](https://www.naturalearthdata.com/) #' @examples #' head(s2_data_countries()) #' s2_data_countries("Germany") #' s2_data_countries("Europe") #' #' head(s2_data_timezones()) #' s2_data_timezones(-4) #' #' head(s2_data_cities()) #' s2_data_cities("Cairo") #' "s2_data_tbl_countries" #' @rdname s2_data_tbl_countries "s2_data_tbl_timezones" #' @rdname s2_data_tbl_countries "s2_data_tbl_cities" #' @rdname s2_data_tbl_countries #' @export s2_data_countries <- function(name = NULL) { df <- s2::s2_data_tbl_countries if (is.null(name)) { wkb <- df$geometry } else { wkb <- df$geometry[(df$name %in% name) | (df$continent %in% name)] } as_s2_geography(wkb) } #' @rdname s2_data_tbl_countries #' @export s2_data_timezones <- function(utc_offset_min = NULL, utc_offset_max = utc_offset_min) { df <- s2::s2_data_tbl_timezones if (is.null(utc_offset_min)) { wkb <- df$geometry } else { matches <- (df$utc_offset >= utc_offset_min) & (df$utc_offset <= utc_offset_max) wkb <- df$geometry[matches] } as_s2_geography(wkb) } #' @rdname s2_data_tbl_countries #' @export s2_data_cities <- function(name = NULL) { df <- s2::s2_data_tbl_cities if (is.null(name)) { wkb <- df$geometry } else { wkb <- df$geometry[df$name %in% name] } as_s2_geography(wkb) } #' Example Geometries #' #' These geometries are toy examples useful for testing various coordinate #' shuffling operations in the s2 package. #' "s2_data_example_wkt" s2/R/s2-transformers.R0000644000176200001440000002425614530411473014204 0ustar liggesusers #' S2 Geography Transformations #' #' These functions operate on one or more geography vectors and #' return a geography vector. #' #' @inheritParams s2_is_collection #' @param na.rm For aggregate calculations use `na.rm = TRUE` #' to drop missing values. #' @param grid_size The grid size to which coordinates should be snapped; #' will be rounded to the nearest power of 10. #' @param options An [s2_options()] object describing the polygon/polyline #' model to use and the snap level. #' @param distance The distance to buffer, in units of `radius`. #' @param max_cells The maximum number of cells to approximate a buffer. #' @param min_level The minimum cell level used to approximate a buffer #' (1 - 30). Setting this value too high will result in unnecessarily #' large geographies, but may help improve buffers along long, narrow #' regions. #' @param tolerance The minimum distance between vertexes to use when #' simplifying a geography. #' #' @inheritSection s2_options Model #' #' @export #' #' @seealso #' BigQuery's geography function reference: #' #' - [ST_BOUNDARY](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_boundary) #' - [ST_CENTROID](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_centroid) #' - [ST_CLOSESTPOINT](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_closestpoint) #' - [ST_DIFFERENCE](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_difference) #' - [ST_INTERSECTION](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_intersection) #' - [ST_UNION](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_union) #' - [ST_SNAPTOGRID](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_snaptogrid) #' - [ST_SIMPLIFY](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_simplify) #' - [ST_UNION_AGG](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_union_agg) #' - [ST_CENTROID_AGG](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#s2_centroid_agg) #' #' @examples #' # returns the boundary: #' # empty for point, endpoints of a linestring, #' # perimeter of a polygon #' s2_boundary("POINT (-64 45)") #' s2_boundary("LINESTRING (0 0, 10 0)") #' s2_boundary("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))") #' #' # returns the area-weighted centroid, element-wise #' s2_centroid("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))") #' s2_centroid("LINESTRING (0 0, 10 0)") #' #' # s2_point_on_surface guarantees a point on surface #' # Note: this is not the same as st_point_on_surface #' s2_centroid("POLYGON ((0 0, 10 0, 1 1, 0 10, 0 0))") #' s2_point_on_surface("POLYGON ((0 0, 10 0, 1 1, 0 10, 0 0))") #' #' # returns the unweighted centroid of the entire input #' s2_centroid_agg(c("POINT (0 0)", "POINT (10 0)")) #' #' # returns the closest point on x to y #' s2_closest_point( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' "POINT (0 90)" # north pole! #' ) #' #' # returns the shortest possible line between x and y #' s2_minimum_clearance_line_between( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' "POINT (0 90)" # north pole! #' ) #' #' # binary operations: difference, symmetric difference, intersection and union #' s2_difference( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", #' # 32 bit platforms may need to set snap rounding #' s2_options(snap = s2_snap_level(30)) #' ) #' #' s2_sym_difference( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", #' # 32 bit platforms may need to set snap rounding #' s2_options(snap = s2_snap_level(30)) #' ) #' #' s2_intersection( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", #' # 32 bit platforms may need to set snap rounding #' s2_options(snap = s2_snap_level(30)) #' ) #' #' s2_union( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))", #' # 32 bit platforms may need to set snap rounding #' s2_options(snap = s2_snap_level(30)) #' ) #' #' # s2_convex_hull_agg builds the convex hull of a list of geometries #' s2_convex_hull_agg( #' c( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" #' ) #' ) #' #' # use s2_union_agg() to aggregate geographies in a vector #' s2_coverage_union_agg( #' c( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' "POLYGON ((5 5, 15 5, 15 15, 5 15, 5 5))" #' ), #' # 32 bit platforms may need to set snap rounding #' s2_options(snap = s2_snap_level(30)) #' ) #' #' # snap to grid rounds coordinates to a specified grid size #' s2_snap_to_grid("POINT (0.333333333333 0.666666666666)", 1e-2) #' #' s2_boundary <- function(x) { new_s2_geography(cpp_s2_boundary(as_s2_geography(x))) } #' @rdname s2_boundary #' @export s2_centroid <- function(x) { new_s2_geography(cpp_s2_centroid(as_s2_geography(x))) } #' @rdname s2_boundary #' @export s2_closest_point <- function(x, y) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) new_s2_geography(cpp_s2_closest_point(recycled[[1]], recycled[[2]])) } #' @rdname s2_boundary #' @export s2_minimum_clearance_line_between <- function(x, y) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) new_s2_geography(cpp_s2_minimum_clearance_line_between(recycled[[1]], recycled[[2]])) } #' @rdname s2_boundary #' @export s2_difference <- function(x, y, options = s2_options()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) new_s2_geography(cpp_s2_difference(recycled[[1]], recycled[[2]], options)) } #' @rdname s2_boundary #' @export s2_sym_difference <- function(x, y, options = s2_options()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) new_s2_geography(cpp_s2_sym_difference(recycled[[1]], recycled[[2]], options)) } #' @rdname s2_boundary #' @export s2_intersection <- function(x, y, options = s2_options()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) new_s2_geography(cpp_s2_intersection(recycled[[1]], recycled[[2]], options)) } #' @rdname s2_boundary #' @export s2_union <- function(x, y = NULL, options = s2_options()) { x <- as_s2_geography(x) if (is.null(y)) { new_s2_geography(cpp_s2_unary_union(x, options)) } else { recycled <- recycle_common(x, as_s2_geography(y)) new_s2_geography(cpp_s2_union(recycled[[1]], recycled[[2]], options)) } } #' @rdname s2_boundary #' @export s2_snap_to_grid <- function(x, grid_size) { s2_rebuild( x, options = s2_options( snap = s2_snap_precision(10^(-log10(grid_size))), duplicate_edges = TRUE ) ) } #' @rdname s2_boundary #' @export s2_simplify <- function(x, tolerance, radius = s2_earth_radius_meters()) { s2_rebuild(x, options = s2_options(snap_radius = tolerance / radius, simplify_edge_chains = TRUE)) } #' @rdname s2_boundary #' @export s2_rebuild <- function(x, options = s2_options()) { new_s2_geography(cpp_s2_rebuild(as_s2_geography(x), options)) } #' @rdname s2_boundary #' @export s2_buffer_cells <- function(x, distance, max_cells = 1000, min_level = -1, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), distance / radius) new_s2_geography(cpp_s2_buffer_cells(recycled[[1]], recycled[[2]], max_cells, min_level)) } #' @rdname s2_boundary #' @export s2_convex_hull <- function(x) { new_s2_geography(cpp_s2_convex_hull(as_s2_geography(x))) } #' @rdname s2_boundary #' @export s2_centroid_agg <- function(x, na.rm = FALSE) { new_s2_geography(cpp_s2_centroid_agg(as_s2_geography(x), naRm = na.rm)) } #' @rdname s2_boundary #' @export s2_coverage_union_agg <- function(x, options = s2_options(), na.rm = FALSE) { new_s2_geography(cpp_s2_coverage_union_agg(as_s2_geography(x), options, na.rm)) } #' @rdname s2_boundary #' @export s2_rebuild_agg <- function(x, options = s2_options(), na.rm = FALSE) { new_s2_geography(cpp_s2_rebuild_agg(as_s2_geography(x), options, na.rm)) } #' @rdname s2_boundary #' @export s2_union_agg <- function(x, options = s2_options(), na.rm = FALSE) { new_s2_geography(cpp_s2_union_agg(s2_union(x, options = options), options, na.rm)) } #' @rdname s2_boundary #' @export s2_convex_hull_agg <- function(x, na.rm = FALSE) { new_s2_geography(cpp_s2_convex_hull_agg(as_s2_geography(x), na.rm)) } #' Linear referencing #' #' @param x A simple polyline geography vector #' @param y A simple point geography vector. The point will be #' snapped to the nearest point on `x` for the purposes of #' interpolation. #' @param distance A distance along `x` in `radius` units. #' @param distance_normalized A `distance` normalized to [s2_length()] of #' `x`. #' @inheritParams s2_is_collection #' #' @return #' - `s2_interpolate()` returns the point on `x`, `distance` meters #' along the line. #' - `s2_interpolate_normalized()` returns the point on `x` interpolated #' to a fraction along the line. #' - `s2_project()` returns the `distance` that `point` occurs along `x`. #' - `s2_project_normalized()` returns the `distance_normalized` along `x` #' where `point` occurs. #' @export #' #' @examples #' s2_project_normalized("LINESTRING (0 0, 0 90)", "POINT (0 22.5)") #' s2_project("LINESTRING (0 0, 0 90)", "POINT (0 22.5)") #' s2_interpolate_normalized("LINESTRING (0 0, 0 90)", 0.25) #' s2_interpolate("LINESTRING (0 0, 0 90)", 2501890) #' s2_interpolate <- function(x, distance, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), distance / radius) length <- cpp_s2_length(recycled[[1]]) new_s2_geography( cpp_s2_interpolate_normalized(recycled[[1]], distance / radius / length) ) } #' @rdname s2_interpolate #' @export s2_interpolate_normalized <- function(x, distance_normalized) { recycled <- recycle_common(as_s2_geography(x), distance_normalized) new_s2_geography( cpp_s2_interpolate_normalized(recycled[[1]], distance_normalized) ) } #' @rdname s2_boundary #' @export s2_point_on_surface <- function(x, na.rm = FALSE) { new_s2_geography(cpp_s2_point_on_surface(as_s2_geography(x))) } s2/R/s2-predicates.R0000644000176200001440000001306614530411473013577 0ustar liggesusers #' S2 Geography Predicates #' #' These functions operate two geography vectors (pairwise), and return #' a logical vector. #' #' @inheritParams s2_is_collection #' @inheritParams s2_boundary #' @param distance A distance on the surface of the earth in the same units #' as `radius`. #' @param lng1,lat1,lng2,lat2 A latitude/longitude range #' @param detail The number of points with which to approximate #' non-geodesic edges. #' #' @inheritSection s2_options Model #' #' @export #' #' @seealso #' Matrix versions of these predicates (e.g., [s2_intersects_matrix()]). #' #' BigQuery's geography function reference: #' #' - [ST_CONTAINS](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_contains) #' - [ST_COVEREDBY](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_coveredby) #' - [ST_COVERS](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_covers) #' - [ST_DISJOINT](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_disjoint) #' - [ST_EQUALS](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_equals) #' - [ST_INTERSECTS](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_intersects) #' - [ST_INTERSECTSBOX](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_intersectsbox) #' - [ST_TOUCHES](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_touches) #' - [ST_WITHIN](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_within) #' - [ST_DWITHIN](https://cloud.google.com/bigquery/docs/reference/standard-sql/geography_functions#st_dwithin) #' #' @examples #' s2_contains( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' c("POINT (5 5)", "POINT (-1 1)") #' ) #' #' s2_within( #' c("POINT (5 5)", "POINT (-1 1)"), #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))" #' ) #' #' s2_covered_by( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' c("POINT (5 5)", "POINT (-1 1)") #' ) #' #' s2_covers( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' c("POINT (5 5)", "POINT (-1 1)") #' ) #' #' s2_disjoint( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' c("POINT (5 5)", "POINT (-1 1)") #' ) #' #' s2_intersects( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' c("POINT (5 5)", "POINT (-1 1)") #' ) #' #' s2_equals( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' c( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' "POLYGON ((10 0, 10 10, 0 10, 0 0, 10 0))", #' "POLYGON ((-1 -1, 10 0, 10 10, 0 10, -1 -1))" #' ) #' ) #' #' s2_intersects( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' c("POINT (5 5)", "POINT (-1 1)") #' ) #' #' s2_intersects_box( #' c("POINT (5 5)", "POINT (-1 1)"), #' 0, 0, 10, 10 #' ) #' #' s2_touches( #' "POLYGON ((0 0, 0 1, 1 1, 0 0))", #' c("POINT (0 0)", "POINT (0.5 0.75)", "POINT (0 0.5)") #' ) #' #' s2_dwithin( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' c("POINT (5 5)", "POINT (-1 1)"), #' 0 # distance in meters #' ) #' #' s2_dwithin( #' "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))", #' c("POINT (5 5)", "POINT (-1 1)"), #' 1e6 # distance in meters #' ) #' s2_contains <- function(x, y, options = s2_options(model = "open")) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) cpp_s2_contains(recycled[[1]], recycled[[2]], options) } #' @rdname s2_contains #' @export s2_within <- function(x, y, options = s2_options(model = "open")) { s2_contains(y, x, options) } #' @rdname s2_contains #' @export s2_covered_by <- function(x, y, options = s2_options(model = "closed")) { s2_covers(y, x, options) } #' @rdname s2_contains #' @export s2_covers <- function(x, y, options = s2_options(model = "closed")) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) cpp_s2_contains(recycled[[1]], recycled[[2]], options) } #' @rdname s2_contains #' @export s2_disjoint <- function(x, y, options = s2_options()) { !s2_intersects(x, y, options) } #' @rdname s2_contains #' @export s2_intersects <- function(x, y, options = s2_options()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) cpp_s2_intersects(recycled[[1]], recycled[[2]], options) } #' @rdname s2_contains #' @export s2_equals <- function(x, y, options = s2_options()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) cpp_s2_equals(recycled[[1]], recycled[[2]], options) } #' @rdname s2_contains #' @export s2_intersects_box <- function(x, lng1, lat1, lng2, lat2, detail = 1000, options = s2_options()) { recycled <- recycle_common(as_s2_geography(x), lng1, lat1, lng2, lat2, detail) cpp_s2_intersects_box( recycled[[1]], recycled[[2]], recycled[[3]], recycled[[4]], recycled[[5]], detail = recycled[[6]], s2options = options ) } #' @rdname s2_contains #' @export s2_touches <- function(x, y, options = s2_options()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y)) cpp_s2_touches(recycled[[1]], recycled[[2]], options) } #' @rdname s2_contains #' @export s2_dwithin <- function(x, y, distance, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y), distance / radius) cpp_s2_dwithin(recycled[[1]], recycled[[2]], recycled[[3]]) } #' @rdname s2_contains #' @export s2_prepared_dwithin <- function(x, y, distance, radius = s2_earth_radius_meters()) { recycled <- recycle_common(as_s2_geography(x), as_s2_geography(y), distance / radius) cpp_s2_prepared_dwithin(recycled[[1]], recycled[[2]], recycled[[3]]) } s2/R/s2-earth.R0000644000176200001440000000120414530411473012546 0ustar liggesusers #' Earth Constants #' #' According to Yoder (1995), the radius of the earth is #' 6371.01 km. These functions are used to set the #' default radis for functions that return a distance #' or accept a distance as input #' (e.g., [s2_distance()] and [s2_dwithin()]). #' #' @export #' #' @references #' Yoder, C.F. 1995. "Astrometric and Geodetic Properties of Earth and the #' Solar System" in Global Earth Physics, A Handbook of Physical Constants, #' AGU Reference Shelf 1, American Geophysical Union, Table 2. #' \doi{10.1029/RF001p0001} #' #' @examples #' s2_earth_radius_meters() #' s2_earth_radius_meters <- function() { 6371.01 * 1000 } s2/cleanup0000755000176200001440000000011414645664261012165 0ustar liggesusers#!/bin/sh rm -f src/Makevars configure.log autobrew rm `find src -name *.o` s2/data/0000755000176200001440000000000014530411473011511 5ustar liggesuserss2/data/s2_data_tbl_cities.rda0000644000176200001440000001572014530411473015724 0ustar liggesusersBZh91AY&SYgjnq@z=;n;aDRuQN%qT& =2z )馆Li~%?OU6bOOI2jc@ ##LLimxL&C ziC)44LFP &CI)iziЧ0)j~)鴞S4dɦmI4lQzL$$M0žjHiT@4MOBSmS)#h&ԘS=CIрКc='F4h&OP4&M x'=O M~OThOOTTFꇩLCS=Qze=G=Ldd=G=GidmCzM4P444EOCD4OM44a4 M)M FC&jhi@ 4ɴ@@Ѡh 4z %L)h4zm&M 44A5=MHѠ @zL4= &4i4MI$ДSRىBJ}$ $96O§HN.666cm hiq6D&Pm6&Цi 6(D46&mJ!6  p' !&!Bm l@ؖ j!CGII2UTӢ)R6.zٴ!QA4F{+ b,1qk-I *hz,XG``/Ec-7rB 1}@Fnlb%9VڏrOEuhe{}ukN1TT7FF<1m`*YXhYSKY E_e̊]Lma' 6wSi!%_e8yr`&H^.lPe$ 3DFTNb+L8a=Qe>0+~70^}h5Pd)]VN` \!,Sh+;53b^ LOZ4+C |A2~ P̼ FF XC'AI))I I\|SV>Pe뮑\OOaü%Dw,~O ̣i$<a-5-[:}t/^Ҧh ռAu:.dIJ*I <'u,J2jSPMC*f$,iZݰSyl5`.γ f*&.6,.1БAj~B]S]Yvr0 ˅vtӬB(HmheB8jd!v=e(jRv FpgdBʽ &6&./lV1_X8\]]r_>V*<,u#a8m %лI}f{Ez0LP"$aBR] a6%5۹ѫ?+D̊2i1'۔*>oz =Q)呥^RhZOٴ,~TK)x Qګr&Yb掮eCMg+;fjD)x8!P\l#ʲEsaL[ApTIt" !5Ys>,8Ң,BϽZ~*Z3NAVFb^Am`{<77oa(Mjf/8-=\ʦV'1ew 8ɮz-wԉQϾlϕW\V_c[+B6x1ݘs-);Y~箲ƳPIMSv*E/Fpi[&%`EFMRک?+;Ro7|qK]]`pmzO)Ʒ4'^k|!hɘwSAzƟ*USq=$Sa\u2) yJC,dﰻ:Uا]*8"w *pU?Ԑ;0W^ӛT,"6fQwW"P[4J4eد/e=Wb /k68q3r\Ғ#ёy1s*[)Y]k(Ql(b9)bFm9׭#L }@A͢XyhQvCI9F HU߂i^/mfQe>~bT^Yi];vɸ >zȨ3a%hSTl͎[ç )C8sPD7zpsID>?}&]GWY>\~y|;NoPiIHu6w̺ҸNǵ1g誸{߱-Gsݝ {{G[YQ9E܇Khtv%Z8 ^iOT1,.rB`RI4g3 W櫒ApHq_4n1l kJ[%r˖J;\b.7 Q[6FK`tDw-]hzK*qأE :ܡP&%;qlH“lDQB &b:b96f90]յ4k B"8 ̳NʺnȊXή>o|│lc^ =YۄBa0-۾wЬ6ykچ5{؆6F<ٮZ3_ G[wfaxW$a>@Ỳ-R*dzKۚ;<8c=GE Iwwk ?LU0N=v|>Tz+( gN;}3?OAxY$/ISmՎxH6!Aӧ)o7~GqV[^h1uZS8QfCۘ +ޢ!oi]vIJ tGLY[aUĕ9Ws#26pҰC1a t DeW*m֯ͭ l-SO 6#Kl:S\yz=|ЖcŊ"{9@Gw,9͒m1'>b<{MJ:LU³g҈y5E^l.)9:GGab@2~#z-ylשׁ+bȚ3H"B Qx琒$uB%FP2~>g31]_;']ൿZQK}W8Hf_'_ ( 2<Yp YiXo\f@gB &a@REv5~v3zYy5d#X4L[bǕVy(oKщu|ozx3~$T٨Vr!' $b -`g"fRM+IN:CA_U5]n~4T JVմ#0I\ƙqH}Nv:oW=ZR=hI7`# ) i?q^2gY#W Os m9Naz;MZeѤ֌EV =ΫؐTlcLҁ W ^G}\Wcrs(\,qiѭkl9+ڮh0Il8`j<K20:h! @œD_:ihklp)+T*墻ho,ۥA0#X1r׏͘ Vlj1D:Ok}̻nWiſ ~wnhb"LRS:zԫRvڢ꣹#3jsM}"y+ZhYŒv:%0\\P3e eGjEAZ82ksֺ"fF+8U3xti"Cc:-^ȍ-DF2!hGcb~ @TR]P/@\i;@^4c7 ~ҳh݉6M>om8`l[˰2e29GJ:!k6b DضTd48Q6' CMnRa(,\S' p;1|>1 "~4#$8ARe03q<ēbd}%~νch~GS֮ ']00(؁kbW8"ORYJAjI t%뇩 ʼn|,+a3VJ0BU FNZ \V& ^kj"$FZLV^>| }e4<5¿ mŤC硂^bꮡZs8{a@rp (rə@;uTǮێ7᳣[Q;GMZk90S:˘W8Ulzze^ #ffןh$6u!Ƣ)c$EI8gS_L18USoanQvDJDlWj>IOkȦD|κ o\;VI2EO ~.?r-}|8Ț>Y Fzu,6i"0F09݂hqLOb&FM>ф ٤Qr&wOF ^]I:|*l/Bi LFnw#f߱}Gä 'jx_ A V}pSD=M=YRh4?Balq8՚a$\*&p\Q2jrZNQ/%#A9ZkJv /M-U8y`W|h,4orArU#bJ \Xbvcg-ڿ>:+4mr.s<7}߻ lU3 /K[-Ga>"/]T32oҲȦݎ&;aZ ӳ GA'tr[oKj(3\byzN2.B|1ͿUAE o+9=NmK3H~]6`r 70Dfg* is e1_iZ@pP(;GNu8slu(tvhHց|Le\'(.ږ_[^uєQɋɦ"5{ϵQ(^}I޺fn. ێs7wv\t` )PPnOUl׋5ݝcp)nwODp =2_wݤ}ڕXM֛f㕅I/]c3Zq̔Xa;`Rp x|*1g_b{hWR<+BVmPDh2+׮GjJQW̲H2f9A囋jfdǫSY^bE [![nZNe"xKMM`IT5wZ)eYS֐|j"-eCR<,F;ASfs)tV43FHcn#grӧ$vrۤH΋v{pC*ǽri{V!4NeY Sc /nunr1P*L! |Vwv/ 4Lc:g #W- Wn`5q|8dL:33jܸ۳eU;Z^RL>Jsh9&{54jAoLw>1pMe<`B6Nj=)䰃> y|k(b˵Gaf2 mdY#aos<Ab ѰptX|uQsNdkf/Qun#6FH Ch2.ł i5MA^s GX5nk2 ˀ31Ff0ku5V{_[:TB  Fi6RMY[b4lڱ(+2VձX›FՍF+fPbLYfe2@Յf[Vjf`m[4ea[ lՊmYe1ZյPj+VZef̭[5FK)bV[j›2jaU56+շͺ̾2>,+8}^NxUjYC3UQ)eצQnĥ7YyHir Wӊ80kiYøkVBmei3[qĭUd*WB3K4ieQuaÎl p)e6#KY,iiͮuR/Ls\ľG]euZQ1Yqu)v֦؋:uYJX\+FKfUY.嚋/Vq6"\X͸ӅJ)CQs**ŗn3YiW:vR+釪ǒEݣQleXA*8 5PjN:Eq}WYԨQ]b4!H[UTJm$NϿï,u,]iTJD-(MSYIi0ӻiUB!:Í=X"+yk5]jϚq!߻v$%U],"ZmcN+Zfv7yET<̹Ybr盗^D-&"VU<+-ˎ!:O8mEJDe#3]keIHvvN5]7=D8 ೋ"RT".ϙuuYtZ6ˬ2y8a8VVq"JBS,=Y)c.TUFiש{#wWVxhӵvܭ<(D53W4yw""JcU8\-7Y`3]$K"K&HVKe-&E˟_咬euL8b=N\:pA"!. UZۋV.UY8iu68ejiڄJ\qZ0Y=U'+5wCb%Dב^ymVR,ۦRXzX0%RI*2vgǣufDDuz+auz"n3J*R)^RXqy[Wl:FZ.ˬufM ue"a"J"D, T6ezaw ;\`q5K]VUyf)n04;kY&,TTk$uvXuaʳ:J%iTDri!1e.YQ 2qg뎺Qǝ,eגA (ʰfYfeyuZqDB%i dZG|8O_6su䈆]ʹ^a7Vn)pK4 nO 4mz ZD!im&J'vĞKxx}ەP˙h`jE2JF=@zO1(Ȗ%kK1 Pe)K(R$Ai%60%D,-)DQ )e \b1J8A1DˆhTDB8s:8Tsi;smۖ-bt:7WSv.=:Z7 Vrmt iHIUXHDEQJ},*WZmW3N4XrZD*ID$$}?4I%5 ي窻,6JH-*1f[l.FOEV][VZb*"7,dU*g+8ˏ8ɶa]Js(#NzxxYRiiekdmQe-×[YBDʔիJJ"jF+㪜Jsy HS̺.;b"Z6)UaUDAֳɪxum0RUr]36BDd[FHFXil8ӭyaYr]ڪ$]I,MbIZ$E֚{tgNjʲ [ Xm))oZu%۫z١mqiȔJEZ/r-dm.6 ̸l.84gnmsNʑW[:G{_Ғ%K[ImZTmuܱt 1O>4؟ *:jؒ(#:.4=Fc]WZ䨕%Dΰ1He)`l^y3H%s'i-&GEخyyg ꭪¢+-c,+'[duV""! )Q٬⺳ TLjԳmWWO['׋U[*IPuqYhTeavO?;OQx~>gY4;i$K[!U%tܪ[0%q^֬m.V$DJiyuDkev˳+P"t2r"˼iEz"%Eb6.̖cFז{oUYjͬh%'yiyGz󨫴ۧ7Z#"$G+ 1 ucY\eXB/VjVqf8ˮ$Ty]oey=PFy[.7^<٨^N6ѧze. ƷY]̢&%")lׇn]7,Z-TWugav2/ZqzD#O3hȉQH:%{^2jʔ֙$ַ{N>O04yCvq bUnE7Zuܴ2*,ݝ#3]T[(-4KZq=8vw|+M6ѮUGQʴÆҬmηZۭ}qDD3WZ68zN< A2DDQ 1$Ɋhh0bM4 P(1MhSh!JhQ `,Z$1LbWkzn봋CL~a~LwעO*f\ER~sqqHR_]Ynv`3}+_y`ע2z43Լ[ްI2i Rg@GD̘vS`r$7K@Ky{Ve)HʪڕUYq֩g,Gzi_PS}|eʮ HprԢ\g pߜrM1s(B+o#De˧ I..`t-!,A^jyVHեk5iZZVYTU*ot8Yfiř%X{"%DyVFSMҭ̷[tIRDHi+:cO2}}{IkǛRP$sԙNF1Z1]rʸ8kd+K=P͎hrO7dFXTe|qM/^DTRmŞG^hՖ*ezu:r%JD,iI,:M!I#gknQ̒<ˬ4ôߥCz3U,:|.#<̶בҽ%Dqwۗ/_"M㻳k.:YbJFXtk*{ wRI$_HZ|Q{*,zhȚII&ǯi8~w约M\"ՕL6*R+.x2˕em6M4ʔdqbi^yںĈ:kS0YWD:yoSG8o$dQty6"UDDJieQye֘.>H,#3շ5:-/6/eUoUg*em]W=N"UDl;uwZeg"*Jegz2V#e0"ymRMSylY.u׈䈲2uu5Z,,.yFokdLΫEͺLugDvë=aFbά6v3yn3ǣSiHf,egZp=^ӗY4KdMi*ԩ$)YaeՊRIOq먈^*!"+q[aƝV˦eg+u*%R$-^ǫ.]n3J %ie-XD<=b]H%D^nj]q0qZr/2,w)ii#HZYleժDua*2DIQ:l.{:.v̬($J˺كinu.j4PY.ST,1YrquԈ%"J5rէ>_)N*8nWiV'mQ*DDBm:M2xn6 "E! zӫ0M:2m7xWyaʉR5ΰZmRav0y&S"V61O.Yv-nmm4\j6[Ç_KI%j%yʰ%ZqʫmvHM $[㝜Dh9zyǘW 0ݭ%6\un;["JN:LMr¸rOTTDǙ4p.!f] ḽިJ7qa,e-U뭛<D[[믎;*5Æeg2y㔎V+5ں㮼uQ# :vaxvQRwKuYE|eF]|r|,..aV_wGʫ:zDSXU *tbFiu5Enr__DuΥH2n÷7ҵ"̤"RjB/NPa2e"0\sb"R"66}V6:W,.YJIJDkKIqZQiKSYdCy[qxL]jj󊔈2i2b*xˊmuj.ykjr(Esŗ^z, +3]Vx[< xMӼ-42ymWhYtDmli^uy0iyn{+5ڲY][ѣ+aQ:n߷[XF,$eWvu9\zif.Uvq~ "YB%ex "%$JE͑qsuwif`]5vly*%IHQjjn {%AWt=TpWp9 [$SVfP›P6Vֶ)l+(e1k5H4mL]mێu,r>a_2:8aIUYizu{eqk:Ii]nk8Y%B%TBT,n&o'YkԉQZyhYʼnM<[A0`uez&ܦe",SYGo,4IbDyuÈ2^nPˮv(UOs5k˼ dTHÊY-LDr;TDv\a4H;ٕn\y*ޮiu˭y:-%pۍ %{O;X{:p2XWgI$:k26Y-bijydGujoP6 4Z:eHmq)cM7^V<TBQ!v92㎪^Jqe"*:х""56R,=FT^YǫnVnzr~w-%4"Jiyʹnq-B DHY*8Uθe׌3S.|De{*Vu3Ś0i<*M; SVZuJIQjNN#l82q!ڕsѕag:I*DJe%44Oǯv"-!dIܹsDI*U1]TG\7ȨQ$Um qd>5Y.+9]zqX,+UJ:꼨eb蕢iM"DaRmzMqDJ JFkZi-^2UyZ.ˤj0x^aƙvmъˌj8g")wo2dR$RҒ'^+m/2T%pGz㌘h*"J$m "9x7\a \ci$}dI[|pgDM-"R#U6L;khwk'[_H 9\3jۏ+Kטx.]|vEjũ{~ȪhI"JDDYc9!wlǛxդe}pHo# ]]ElC=/*ueJDJ0Ǜ6akBD^}yKD\}G",ÇM!ܚ] t4"hDmŦ-Z$Zߞ:}x|zEgU=iۯ#+12.,! ."PJ"J+NYӺ+[JM:e]Z<2j9]W2XadDEDT6ԾJD#`;8Vi"U^H3M8ejay畅̶uHaVff!# qaΜPd ̉hЉ(OCh DX6 FI-VHeQ9pc_'v`yjTE <[y#ǗG*ZHIk=w_eeY^G+Jk֍P,3^B:f㯚I -4[ÍRXHqu8q*θҙJUmq+<{VC XYfW՝ude\|Q'sEZɢYZ;|pDDTQPuv74KME%(ODvK:,2knJ<M<ۯt(^ƓήHuje8Yj-=GT睵u>h;|r8;MJ$Idti~'|pd0_vy- _.Q#2SsM37- iNahHJGyW/Ykquydmg/V6TtѶVy縮,jlG""ik>dEMz󧯅i8m0iݫ,rwq Wq]s2YEB"Hg-^65K75Yr0ҼUy+=Qn1QWqng+uu$D񵗐EZjSn'r~:jSk.ӕۮ4ӎV]ūO"ki6r>2id׎gNKtx{*86vĨWuc*\4.2.˩DDAçZ.eqvIP8-*J("yŭ+,WT"Vq\uwuf6V]2k4^ɗ]q)Ry3sIZ]V3rZXujvO]>9E$Zi$Kh$IIԲ֪)c68oˮvifYUI;[-6s*KunU\!BF]tr]j˖O]>w-KEYUq۲74m+d.-VkWUj Q˪ӆ0xzi5EHpZ0ҬϕBk+-__TY7UĕںaVJEn_R:MS-ǗqӋ0x㬜e*1]6琈ˎeݢhEhFĈ"%"RfgnX^βNVe8mev1h(vFmzUDTb$Rl(bcJPĦ DPA Q&08`ƔB(hb L`(Q bŎ`!A@0װk{#7TZ~6` -׆F,]U[YQAzf]i8#L#TzDdá7`oY}"ѨYE#x/x?GN") li֝tZS[*RŠE<,[ugόTp4՚a8U׳-O/QӵjӬW,ڝehb֙s$manwM"'{vmT-,g]%!"*26o8n J8g]^GqlQEIL񳫴nawU,)ZjVEزDIVR$_ɛbq8gdUUez>4)YHI4%/;m۾HWimDvgmi^Uy(*"8nZ:Ja.-vk{-K,cegN0]ͽ*Dݮ%UthjWQ.0<㨽r2N:۔DZPDGʪuY$[MeڦLY4lBDet˵Z;^T,lm?~>:x{TU4ȵÓYw<*q-$B6{ireZeI $t󓖒5DI$[H:%jR2gyHD͞$EJm+.ׇʉN:e-6kJ8z;ƶX"Rq*[-rdHmj])*UDB,̭09LLp0emW4,yeѪ"TI *˛,jiM$Id T%ۧ%THJDv]ճNMwzgEHG1jaõNό+5vI%(D[BD㍸huMbm+m*$X.۫MTqRUF,33\7)uN{y*T%TL]cs , mmuj"jj$Y*%˽ۇݼWQڬj+4,[VYeJDRM˔z;\)÷EP5כ[wy4U]\#m f ,5G{!m:JLR"ی,N+δ%jB4l,vh"DEYDT" HmۧWe;dJZƞ]Dњ~Sucw]XIDD"\>:񍞬2e]%C+Sf.9^ XUcl ux$}jzdIdHIevtfmbS&*ʈD$Y%m"Ieq2 zYa!,Y\E٫.G HDVn:u[.w^qxdDe+:u\ۮ=Yنư$<כt~""Kd$BVT8 #e]UJ #.yq#K6yJ"%K]?<&ȕ 0 YiԳnq)+;<ѲWk"!*0㚺< Vmjq'դRTiӟ_44[-iU)*:9XiafK#k2jޭQ"V_ #2ujZum0DHIHVbqG0n,aP^Yjӝ]tsˢDKS.zن"! ԱrZDAN^$i"BZeT b[Tk4m;^+ VJ`Sκy.Su7hK"IqgO4Ou*.ӯx]y$"5iM9fF%._ S5MJ xw0)@D"0D0 3{7S5U5'A{r< o{Ȑ$`ha@ a#vtS̩Zz DץRM5BQQQEXLa}#&-3Vhw)Uo?:F_KKE'uvk QȾЇq]1D AD V"2F.tM8j+;8 !0 |9srVֽ6A -okckNj!#1(Q&ffj㛲pFP\ AhH b^s2/data/s2_data_tbl_timezones.rda0000644000176200001440000171530514530411473016470 0ustar liggesusersBZh91AY&SYL!{yu tX"5vj)%]t$ ;j(R4ml}t`@OȀ hѠ5=7딡5SگJT޵R 8yE '3(f49`;޶hyRTЀP@R8)yޕv4 @s H(@nc@Z VRݵ]IUDF@.@ $۲e ȁBvQ{E  @0Us5Jn@r ^NOXw(QRvˈ@.hh8\h H^OJMB$^Q.`3c#c{m:;iTz7:'ovTAAr&VteN0GtХ;,m$=᪭C({j`zxwO2W0Fy@\N ^e!{gxyx\HP4QDA7yE{aT5H=Q`=7;Chݟm/FʫOuM>g P HV{-n^v6{5ֺw|H$ (4[@(PP ]P4 @HPP( = *@X|\Фƌ4=g\>0:uPK3@:e^0uޯZD:@$PP: 4րQ`tt@T0`(+LUP6@U$ ljP42RUBzr)IT$  @0l( mT6U":p( -&$*I `@{`4(֠h( ӳH$UNXȄ(PD٠E"0(vpU@(eT@h!TR)tp P@(3c@PZ%J")i Iق 0((, N JZJ!tր$k @))j)4+eE%TH@hhlh PCT4(@Tt7`4ـ (&[lP(4"M&RM@b4` 5, P5C@4ɌF(9Bje wt@["C(@PPCc()D JPj-P#@ D@M t4 JwrF}x(+`(I@ցJY 4*(VիCB)" ")@4mBh WACX(P4ZBӀ@4^ʔv¨!KL@ *Uݸ U`zkmҀBHP P@@v.`i(P( 4PP(((P B{j`IHlٌ."-PVZUu…a@ZPK fP $(%ԣ@J$zvPdi `@HJ m  U*2EM@G@g5tґ HR(EZjȠ @҃a G3E :j((%H(  30.Brj4 @uѬ [jPP:@P@]t0( SP( UZ R P RBPٻig!!Sl:$ %)pT](G6MӮmH@4 UTU;`UOFL&`M2 dh ihi  4iɁ2` &2  5T4&M@0LhLLba4т`L0 MLF!4&&*`@ 242&ɓ M5%ID=JRɥ!Ğ˽JSho)r bIBB 0k[Z&hR^b"!WuVA\()Wjő(zVJU VR_=X[ӥ+%S[TFG?KBI JR0eRa2En¬IB`Z> $آ+ZLA)TJSpgE,Ee+QK ,M))AZSDU DUKKc/THCM&/ 7RWPd6dRwgl:ŬODh^Ւ`RB "ROR&T P*UTV%*PKN g=d؛UNbuOu$ؔQ!RPTAOe*J!(BP6 .r^J4HR 3HJ!/ J;55ٴT]BU-m ]Tѽ%m(q) LUI3I* l?ѣ5`ωo6~ Z͸xuR.X@p2 5`H-CnHUẇK!N͝$DM )'jƠ^e;" S2]jڧЅ.xB!MqB-\DQVunYܓKKzpkj*G^%8-6[AtN 5Evܯlzb/<(t 9E&ߑޝrY~GK,plX/7~S`Ys_x >!]F=U1 7b~:DX'i 3^ ukˉjli~ѝG lժѣރ ך\XuLJnHEJ^BQOudW|+ سHFGB!G#[7`yyDcl(hjGGHxV! I?*W-N=+6tď^-t)T:G? ,}l]^>b_^..fhfq6؂)ET&ydڴ]ҟ' \XiZJXg+hAq/.AjޖyPt"S.jRܲ]R],ͱV[_l\"B,b,b^l絪f]k6)ʻa(K?Br7lk.+^k)lob/M!7tD^u16dR|'U,~ڛ"T)pi/b?Z^3k":U>׊XE8TW%CI#{N ZU *Ts(s͑ϱ~C_ l,T땥?Xr!P\KReuZXDƀVm[kEVۗ$U4eJvSbWUBSJlCĊ8v7kT1D4 ) M\KVSc]_<闍:t%!QVa2lUiosv~ZTޅTQJP3ROA))JBRajPV >S׆=ZjSD0ەJBNSCX-Hbi*ih0Ƽ U]r( ZK.ұHQ琘CJAM L86+js)JBQIMUUP2SE&RdJvUJ҉tHHE(FiSNIa+JJ) Q)!H^*3E SUD J(uB:T+. )+Il(PA eaT7 ؚRƘZneMEjO QNmLRB E!%'[RiJ[ .8I>rJsma4aBi(ՐInsE 3`V5-!RD!HksS,g5Vֵ4B5QIm+(%+E؇J\JO%%PƠʥ!!b* {lBU ?p!y!ؒRJJ, M:4,:]UH!Q ,CyJ9t$ӠoKTh% 1)JR1JB>Ρ(u-*rԧ%zk( RJSD&TJcЪ < ]1 \OƂߐP692pȥCfU;4$r@ґN,g%䪄QX)! ^TDSbސAU"I3vqYaLj4AJ$̈B0BRB&*,E(U BMfV5^fSG01-y|SB yiAKsIo ) [JBjU] q䴖=x]Cho _9{ K9M6)Z %A!Iv Oµ*/#Y5]:!e"7tme\ߊ IshC´U bLZ)/AF Yc?U_<-r*D%Ia K՜oD ڰh^ :T=jSB] Zt*zTAC@I?oUEh`J% g2Jrҭ! IA3}͋jUGqЂ(b+u}<%[b0.%cZxvyqF[e(JiK1 )#|[qe;& *&GzPB*B(ӷYJHDQ (PB WG"DR!"kKW%iJ)ICijZҏ)CpT Z*=$ՂH)ܖQ,R`(G8-ȢPЧU0HSzXhM>43šS(!JhI %)CA4C?E)TΕV,OLYThz,*M!5D0,h?R0)%-AЭ)vTJJ!B"hzڼ5RGv唒C[b7D+8RSAIE6v٭9̊4&E5fY&HA$`Xйy(ڇ]J)O~Nt.0 Z3бM>FN}*#_38R_wU+b`w=3 jW:&.E)3o9QNs4Z[קq+QH#oUncZ7ƴYE_S)З[(U j[-/_`Rmpv5QxE,{(ƧQzOPK BbYaq4i' 3BqoHB(?󉰇" z6Վ<<@ippN(eDƑEP2CJ84Vն]e{*L1TW @Oe;&!"FTR!1K6QGR` {-?QZPyoXPYFbq=@ݷۯ,~O{EfƲ x`5ӌu1BڕV#nhO!# sZ47.ym/#r7,o (az+ޱk{Ϋ9K_tc$>tUP^wYZ3_"uQh> AhR86hu5ezL~2;kbK -}Y_Fc_=7Yz=zu~qR;k!%ײcQUr(.KKֵ|t7lc_{ ]^M:N+D8N+?})"ECՏ0jsqobRԳ xxemZǠpQ2UҮO/OQ_ǀT̍t'=eBtY5ilnzUjTX![7Jn&NWT()5aXh%&&S XwyTbs)GsTn9 [~JɛCI삏*K06QbqhlsM4PK=U-FX-TNѤ3VHVbR˟,R]5l_k, fUkTK.ϹUهڤ#>H+ mg,+~߃/Bԥ򄑆 $T&PRL+l3H5se>\IbMsiFko{;\w\Y(gS[~inΨ$P3]dMIw].KbK/Y0{(F˰:EA =7-E\Q1(}vM3 Z6Ӝٴ. Qޟ307},e:R=-*ϖz{|,+rQ(g2mSr_j]#Zc);풺:kCCr&QcNџo|  ?"3vHpYtWnOVeK@H!a`cbNl VS[Q/_Hwt™nj7G ^qMybE &vG Q0HJ14q+z&Cg,%^)38DR* 41$#=#gj=A4bq`iP,B:9{ aZ^;16 סaT_!]tNB+S_"~>4v{sݨ~ t5Y PzֶJ >`RAʼfG%]]|1RtSD D[ ?Gx]$ɼa_NA yۛ9/%3PX^YK"rrkT) lN\֗#YQ$]U̢Ժn3GWfN>׾E6QkZ-(iMͲeҵW[d? {)6Epț:7a^wg^c0`5U_[N_֩7A>a '(4_W-~{o]F,b5wJ* 1p},[.Rݲl~g/̱^gt_ 5S:$^fh\0/=F 죿oʧHZ]Ik|bddeRQG'8f2jBo k'1Hu+[tS8iҹڴ'y4#s_N67$%JC@Z l)@edQƺN)EF()i¿WK($%~UM2Wie;vީ(7( CDF1xoG 8IfVGpj6ٚ(4W{i;=?o53,mo!-ĵUt=7#M3N>VMuQ饮X^"շ ZɨK.e.)I9uE6W<)eٍ^VuQD+U}^ԳDZNQӔ&^^ Kj)o㘥CQ2lCF2 Kْ Qh}o뀇qygARq/٧Ipe{Nݓ wi._[kF%KAjŦXg_&޲nmr+ /1d(ךO}&I3H_s\'EQ"#>uJcVn,f]гz{,@~4]M~P* gH2W;[\Y*L8L{Is$I_}9)Mgm )X`Bt_F}8X:uJ " /Kpʓc{XD.b"I[WM3q-FЅ'1 Le+#|2b5[ѩL@-2iҠ$QtU_ù^0 s;Ed u.5OeP1Kp7ħ-unQM_苽SbսYfC2߱ZA]Ԗxm^ hk X42˱ Rq;x&UoFLiLcx$ucv TTe)|[[' =9PR È5lU%D.]R[_:koWfo~+hɼ*BJW6.,vtCX21FX&ʫNoug/4r/}%NnwI8P^WYJQPTv-*T_R*ʉP|]FH'<)PlJ.ksZD6FJ7ȿ_O};!O1xHؾT=XXev͠XU:<+OQh/-%M*vtiGm{+5w ƨʯ#wO -t9:ψ$xQ1/?T} Fak\O]VͿ{b2caf ag.uF9D!RF ;8r]1'jAt@^`QT\qx#U>-2t)\ʬqV2*.YaCZ2 ^v.(U2i3- l> A;zN)WRiNkB=!Mz-bt Un˽nu ȍjrWg\ןX(vu%6Z;Px Jxvj;V@}侔q(<\`6J~gqg!lW`Jxe+bP\CKZemI%uˠaJj%3KS*t1Lod Ԃ1jt(8-r$d(Z8/`'H2e,::Ə}۹v>tZ=EfBrRⲌ=J0tE3v`RTƇ-E}oꖅY0ҫQj3l{g:Xɨ.޶EǨQkPSV>!IDJ^~4t5zGߝĒ*\VaE}xLc4"zZEir}y ܗk)q5.]vЗߦg ?`%W[S8'>nݥc+GA?Zs 0םz{n*PW>Z{,ӫ%>ВQ;C=Z7#r-rp,܎Ɇv 76a˯]+M%6ZiѢ+G_EXqbS0kZӤ{' @mxB RCE(~ %kM$[}CUuQ&k;CCtovE)좞ʲoyfKxRkp,bאZϺGSWR`dVUlY*b<6ګ18O/OoG~\en2:=3lH8.5%ORQG]sR_SYؒP|jo캖иȯ(z(_=Z>ܱ-H݌UkSD93lSwK n.Vh}Ib^;d`YY'uE9'Ԩ)K)4;He=b]#98KI5q8LQCU]OE :2di@~r !az;1 =œl|V#n.2 ҠE_H*>9J@au .i 1/3). <\Z{l;w! 1*݂БFN\|F%ͣnݲN/6 RjAuoRj[R6:ϙRTƽWoԔm9gŇ_SO/ An,\v*[8.l3O5[%{ r4dBw&Me? zK2[shaue})uKDF[6͎xT6oQƦBK4rf 60 j l. 2hg 9]`4o89V-y/8y9hJ׎|NaUi^(%l\ ب{PI3HC[0BȤR-WlwLA7P"1 2ֺ'ԆN&WFSeqAsUͪTיWgIBi(sN`PcPD['&͖!ҳ׵Xeݺo0EE/MIflLfQenM.Pf$_f|H9"bCq`q E(^o/QE]#MG%Vz'ps/Oii'{{c,ٞY6%ܿOyWGEI_G0;k[H:h O]'ϔ?6ypE?Yػ.C kK8#fU*˒ڣbi) } ȶJN').nRn{IʁO-} %BO'W]G0E=&5Zad8}I?2ݍ8Cyo=ZybEøiE#%F% H;MuYW#[AcqGN&?8sWȗAx,=Ne_L箒PT*j5Sͤ=r=[1)&μ,-ziWQxD6N"5O8+sz6k^Sw\R金iU5;MJ:иK XCG"%*ٯ)fyAt-S]}I0+B56T^4xL儒 aBȼ4~Q}*z cc1<_\+V)ҟ8H>%zbY:5֕3lA;u w6x63b@^CCF=gJ}"lTv!YLdU"C\R)FDK. iTK:x3 ~x%yXe`D Rxի6*vkzlM>%J†䳌I~7Gg;ώ4R/)H#K+;fM{5k6cؼL# C:lZ1fRYT:YR*S&+#c&dQNDlCh~& 6Ů{z @7K*SdAPq*K$ʐXۺ+6MUTa?C6G2J 5{Kt#|iaI<~\[&)YՌfXgo)/&|WUhi]>K ܧ,A Y{/U =S;ŭ{rK6 @:.0_D%Wr|@fHR8vWZJe2fmF^(~.Y7|/;.'ڲӀVsjT4;2c慳Aw{$,/o޲&DE7nQ"p6e00pjүѬ% 7跥.oZ#$B*m9UQOϳZgQJ׮DLXfHyKp7:a_ܜZ1jR#֪[s* (LzU>eҸҬ|*Uu=D\˿ߢߣE}^vnZ?DS{lP[pzDVE<-ȼ4 IOJDe[I%\glOT%6n|'RoYG)~k5 ~m4E i`$ҩ nZIBeGPyFٿ:JGz ث KwԞ,ɻ`- %|'e5z⎖ $$,qT:ިC[]UMN*RY˽Hq 6yM:+ēydzvDu?We{6ۻJ+YBg` W6)Ⱦ~GDm촌铽nh7Egv iHW|vq*wSRY0FT|I8m-nkkzs3/}j"v=5 ]\EZBZ4,jiXV4Zb쫼f^ii_l|IťLdR \$m]bŨhQk1z88u{fji&]iiyh}^A~dqSoI~fpQKU,Mdߥ+)M/ QOB-vRWDP<Ǩ۳zNW`C-.l%ˮ7R ? Gڔt^Zz_kvv<ֱ8&Q8SD ՁGaM)dEB!ykۻU =!Ghj+i?[Cr. ҟy ѭUlHxR-zU+ ԦK k$M-״b5zC<9xeeea笰$ϔ F'|7X滐n{r\ʭCoFEj6G?Ac-pЧ?n;{a:~j?G+qZ3Y۲\p .!  аcBׯ&u=U~um;,?OdDJ iN#ӈ=B~__"ۉk}!xYfӧqZh۲g.+IVÊQ4otiVYd).a\/Kl$6yŁቒ H \ x5imϭ~Z-7߳un*,B6=U!oT.W7w}\b_dPHS|Ieĭ4C׶q]o_%ojU@N͡KeMV r⨥!BeW5Ea9֟e / _s\k,"PtVc%Lvk|fUogJ>hpNܹƒL_Cx]ii ~$Lk`ijn,/E[a֝BxfJfD3*ZSa[B:/X)9eu )"M8qyXh6+/4fK(j_OxM2w|,$4 9ߓOvb\|ח̫eKaGXUL-!"5hx6GMEj񩦚qѰ= ['%ZD"su+[hfZ[YB{Þ̹A=Z+Jmgu THðQ 4)Wȅ2)kuEb =F d/T%٪5*Uz"k/Q2B 77Oy>O>b>큉e-梖-Ƽ[7 g8+̓ᵎbr_=k͖54m |qeǾ:4ŕu&"&Yl_'cqSFhR/:|\QlcP),ͲDТ`ΊҪQG=HRMʚ)yi,oúzM@۷G۝,">!}5WQv?鿶O@'~rU9 /BӛNt\[%,QY Nz-XJv >wHD44QaKA+ [`|(6p~,%QnL{{1p }h3M;fuMfx_vB" 2e5؜hq~) _T&TP~XG|"(,CX2P 3ުLdp:D\nf^Qtǔ+xXSkr'Ie]0UQ^9m?EDE>["=s{zRYBftܸ}isˡUDY<,#RcAr/ .܅2ա| t_+Z7*F Ь.b!&(s=w, MřXc S6v[W=5 hú[4p4* u嬩3$RJA0i4yOXP%]'J%]Q+G%SM?uQḎ.v. E=̔j O=}-~QO1Kr^2A}RA˫G|GSU䓊1oIS 1PPM+R W$f2&p]Ѵq<@L(CY2/',gQCahFfF9?Չqx:Wiݡ{!WmVn!4qւ%vSXksm}3]?ۺǭ j]\]DM/Mjմ*ceQ_Iwg|\@k?Z;Un40؅u;TWmY)t$k5OO~LM:.#L[~(HcMj'[뒚i//o%OgSC~B3LAOf2BI|^165. 4sZcpSe]^3bةQw!Fj_O3 B*2zԍxRJ8 U]8#")(T\eUO殿uazUţ{dVw۷823l312x4E Zkzr__yMNkvCZ:6ʪKFLO{I|/ } 3!YAˉ0dESc:UHʊT!*ou/QLcME1YC:'5ۓ2<_ѢΞ(uÅSɍwH> q̼6'6p)4j H0$8c "_cdWyP*χu /X5N ̌sA%d7}UWa.Ě\v(:C9P|z>m WwUt9+MȾ SYJt_Q AT :h_-vO:0dIeoX@nsJ_ˬI[|߿q&FywFK۾<Ҵ\6 5!nM12<ӵص-YcOokIk/G]>+"$ KuBw_uњiߢMEVPr6Ϧ+o1uWa[wi$plY=:Oqøj]Yyse"CR dno" 1 mTYfОR7ĩ$5A=L=7&;C (c6ϵ|K}@Gn#U7iRT1^֙cpM,iH>TlʱZ[IOuٓkCˣT١p]E5[)ADKEkJykAS~Ml=ZWNXg9 [bYVi]h]R%,yvI~ 祜6nQ{zˣDl5Ol%{ qJ YLL)7 $ %(j+U,*%T*Jҵgv -_NZ^yذ f/ucbZBSѨ(QkmQs9o%2xcrPTʏQ$7ťUӛ:v$NAbeqڥ+JWCή)"|6*Ʈe]Ug ~b7 jޮ/@/UL?|4 eU645 Ļ+IN;MJ`:gՋc nMM88HCũGK<#X*,5F­S^cßѧB:Hb71D@ڈq^G9vu"0Kߎx-8Z79% @EgEI5BY|ډyE"fD%d1Յ[ʃ{&iįԂ-( m!V5~{% Y=9-ة$֪V ϪS(yk3%轜ZHm8j6KY~>1$ՖˣiQ笸==LI'{f{.԰K@c{i-FxmH9DTw\-YZN#&yMTZ)m q9t ysX}e3 _0i+fW"Ƽ=AH2mDRBD0Jxڛ>mۥlGNVe-.QbإUIMJ zk5i# Z=摒]`e|梾Uё`եC6eOe 5?J'Es %5~pQlROOe]%S)>E-\u^ p+i{}j)anVE.Ƌh1`YrH 5^%Π|^rXA/1xZ0 X`f=n]ZM_uԕ-S*o(|@lr}j%RAuɕoڦХfe "zPy^SbгyۋʔΝ̛-F0è/EEҘeVRxe¼xMj^L|OqYibWOlnߢMKN62'K:aR:j묻՛j9祐 OʼnSH?__7N"SvyL1cbV3ۍ*ho7ʤ Tă1.ΩP$jES uzH<}r lc% <)zwDt6uY&OK:]i磵޸6-1-jzzˌUtN0RZ)FD- J ҐaU5㴫a=-nh;Q 4Tl QKV_ z5h8o4[h9ߨZ}(t=gB_ V`a4 % /o&l_ rz7rY>DT7r5JskEBt2IR\W%YPlBkǖ]U'IlҌP|1.r)F%}G m/+7bU6lj$JS.RקKA,H)d]j3`9iRVK 0ӷ'l?U~˾U-=f{DtٜW5= SK1٬WQNPdEc YB=>ũS:ae^Sy2f x󥜰C79~Rti`2bǕ-v%j[c.hm*-a"WEk |m5~vߙ%VjK"i'vY9&Lsc]DmW@DD_6jR1X)4p onzk\QMQxgnZ.0L{"X].y ?e{RKM`t"4Qm0gǀr2<ؖ[JBJE *Q%U}Vя{QQ~K#v\zW|13GT6ߙ2,*+~`fUEƺ*]8R)3 nҲW{oI[Z%"n__hZ*X2W5}bEֆW# "bt.kSa xH*r=f! q WԀDSNY{](N.HFu!ơ`6a?ֵVElƚOؒ '؍{8;,i$%4]4yXFU$vRFZcQuϠ؝Zu:m%ΕlZ8LDLID$_PC'L@ JQbΖxN/^ZAM!>$DO:tl#v0IȰnb˹ !ߪ-Ak]'Žˠơ4 +(/SHEpV1WNtG伵MI5'ƥ'H:d+.3m"w`3kf}NO0kdU}ZM(cvt:7.JLjž3*eHºBп{ƗQ#,Gݾ%hȃ9=I#F~.){ rbf\!U^co j)(9.Ώ=䮻GA^/,"lxþ˲/eYV6UpBAKh*M&:cyo}&SkE 9̓8<'^U}{尯xq1m^]=^ț(n2x"Y.2m'mհylccò2d}TSbۏXE;fR&BXUT&]Ka`MbHUr( V=K&1nއ Q[WHVWI@9k'-Z*o6YV( Y&k=~}~`IKf'uHUؔȴ+΢|^rK\lz1z;,(p۾ m+y SF"b 5j2Kz$ 8}6&ͭ2[ӆX2ȟ RN,zܶfcRJ`6903 (fع)$5Nhx#ggFK:n FoT49Vn1a*+ C ).[A'f%m~Q.*VUu՜^4bݾ3ϵN97Jˬ4/ {kO!RqS/U-(uUeUladūdՂ{вd%EVY H{ 쥽nI7n y3ʇp2}nTր%UCb=\Y!S-wO%<~rfz%MжW[%ekH=t%Ч^& :?gi`اm'*-z6iTW#XҪjb_WG=9zK^’OYPlkXJI%(/|ω|?/Z ZRK PC껎|N+и>sߦ}JAk2  {+$WtA=J촊V=d CK)ϝ07r:օP^W4Ovت2\Ґ&,d+ _K ]q-GȌ?cѮ8BQ?߆ ՞"iƃuQ&n^nj.BޢӯG/"xu{Ln/X$GPʕ Iyw}%;di;y]DyH)J%9|'=l'0ʚatkUwƣ3vpR*bȝK-&St\m ;]^.'LiTcWK;NJ\t(}'tPbڴv |qM-CYUDiT6eRWvD`[~|6LZVվq0'7jóKiԥ7fxսgRf%JiB;*D+(c-~(]Z"̬A>g8 b2S:֗⢮}.*֙dmvK[B[7*BdUJ1{Zt(!% v9{$Rt."R)4Ц uk~j>Z.8w]Nihn%."o+F%ǰSKÌK"Bagq_u&cNhIX$^qU6a+*`vxi/HQý?GYO(+A,@"ۡz_H!Wːj#Lw*Xغ<Ŷk;dޒȞ*w͘ޭfCǢptC7ȡǥ%t(m5>@h &Xu兦׮YI8s 5ar]#%*&NaArnX yjb;b6y|i-5nQg-S,}]Tޡ~ 20./^?3f"45//8g0|?dߓ˕JNͼ"lߓݘ;Q;VPlY:=!لt2jІ փXV8_u_.{gcK]u*cWlo^w(F˳^^i7 ̾G q-/i3NC{MlwKoW>;Kcxy+iҼU*ƒ8mA,b}#l!(c]eULsh>?j7n?5Ssq,c! Kȧv|Sa4\ˎg - vaGF9릟p)%GZҵ8"TK-JM(ۥyCuXUW6 ٟ[&aA"_㰎DCT }Y6 Ғ)NBHҐ;*t_i ZiT@WQ3'P#&|U!R)L3&{,lkƮuVѲ3%q!cJYt%C0|wTH1~jq(mn{~V2) -4(XQ4UCuͥZS.vZRsISD3)37aVH"5$$Z`8VQOJnV*nzK !L *}M(^d5B MT)RHx).<-qU|OvsХʵj)p0QQT");WT"QC+Y[o~'W,^c /["=GL`M2h552qpau)(zι5E/IAZ\\fr&YobŔlS[EpiMh9iSg2QNI9ئnNهZj{TeͽUVgVcVь5W+\ ]p ly3QcIRERvEYEn3?X/[>V"Z2+Ӭ:x?^*D5~x,~D^BAB R"h$V{PbD\Uk.;mECU"?ScdK*&+gKXC8Qbdh،m,NB.J%6k^2 ~*UEF~5lCЗ‚ *3>i+7D7׀`z-ZiѪR)J` cPCQJkS^5rUŒX H)8OiHn\;,ʐ_ՔtH C[ߞ49\F{{K-wJwxYwXM..CYU[ )8,lh7-*l퍓WW݂w;wP_=+*iIWRzv!+[|'8 |1cqO[{6o4XGSyEٝB,"RE<Nѳ-9&L:-wVq[6ޚ|6+g_a$8XǺ.)Y"^Q,#e8qR|&+ #s2I)*]EU^Mݴjjq @ N몢ԭ e4*tJk{|iE&ziPS,Eڨ%6}i,o]jSr ,&%%[.K| e^0j:f8[ȷM@5 ʞk")%݇X_dTJ,S^kR%+QNjkl!.$߁aqT[ugeKA#JJQǬ K40w96S^{{!/BeM~Z+Y1/sRj41mpQ'/SmHȍ#OO΃lG2e{ODKs{fnI G4dCX8`w_(庒T"GVit.PX!67R.1J}lst8.m#8-7?5esG-N޻m_WDsmfu}sx"HAΰG QJG IhcO x !s!Ȋj%Ÿp`_غ|l (35YC@E^ >48@eǽ'_3Ѿ_ ;2HnŠt uahV]Z5 'b9ˮvEI11`}+^ZFE|Hi{{~sȲv~>Kl2bkz؎9{$"ٰn/B"'SMF1KRy$MI3'ym^\Zwk;ok:|(qz"޾[q@%ywn5YpgM;B'ͽ{J?e]dȶCDB~+yQDG m^evgxQث;v5C/Һj X)y}t3 T(v1Q~veWK23I|p&9/eW麢!#6[<tt?G) ң, ,lp?9&KVa7NZ#T4(EN]F=>\P=4tIoyfjw {-qR7\QVU|{xA8b }"K/9CZtʢ1"( GlŞOoNb# jKbz4dSh5uL2֊`j"i.ʊh[uPK-2$(ʲʏFwS3[7${.-FNP4d(n1̋a[TAћνQbcԲ1Nzg]ƦUGy*j((җU]H$S Fu&{S3Aſ V:Z4B#ߪf_zeɡVoC)&9iJTk'=龯я`Vc9j8tXw_i %fSTyT+en5.Z_[ :u/"Vɤ:("9>Ptpo낌fʃI$<絙^\T{ﭩ7{a&a(4<6߲XT՜: , 쌚Rקjzv:fX2t@P3_X#DHfm԰-J \ʯ"O,}\M.`oW<&s㒚OV ]=VUbT*cjrf(#-0|H ^0D=+G :dnFsҞ JYrFP)'TLK@5N{R` -JB(v܍l 5c콯>&jNCzSQl9iL\bଥ4Ke;+O1"T';ja"҈.Zļ/ef]O f aQ:cZP.b 41_F"X_3tgiojP|q`)աFB|,wFLPS 1x7$UeeW  #9.J=&]V*Lgل+r){&ELl:yjʂ>ե c&⟟?G]K?*wbI?;nk`Ojs Ko.=/D"ǁte93M ِxS(=h`^Zs$)Ky:i ذ^xܚIa[Yu町1Rm1fzڼ ͛^/=_T6ċ6d^KqTYI0:9o!y#2G!Ӥecd_c^x"@P5T"E-4'1Oԥϕ%K+XNih,eWo`uX,rh3tyԬ5',%1k26ī+-Ax˹6Ua `Sݪv9c,g-0>&ȷ r4p1bȇFAr[g_֐n=3mk:2SSF*X'<<4W%lRH ޠ֚N,{5%~[M\*Fʥ Cc15=֧篕3njA:ȖЩvZ*M 'OWn,ԓRA@WƇ RUQC _ eO${>/**hCNh^*y#jD!{gC?.ߊUUI:5$ b)k%uA)OYZ[]ݵ heO#j=&7_b1vO3JmD@M!:,=qOfԝ[vK)`R^Ӆ^K +:)[.yqg`׸"q>0-0,=wyǸТ1(ϸNPYWF2X7$Ζ[ }5ۣ]}wĿr4OBqL:_ɭeZ*^Lͮ}ՀCkXѨ2).t\|dA0CHQ5 4wt5(UU(RڇcheH% 5'޿Yd]6U6|znރ:{iާ(o\95QCN<lj2 ieT\ ޱ VI-Kdڒ׼dn&u9bTwmIt> ؾƙn/Ikz@ 뎻*OՇ3AMQ߫v{ezPM}*>YYYByiM7<̀Cc ó/A}kMP1jrO]W:0;oqθ/pD`M4x렠4S+2EӋAqFF@``:X"T{/LǸ^3w}- b $RI i>e~v[:gG ]?V"%y,Y~$ -/Ҕn ~7Y'+_D uSB*r֟# ܷq )1#A FQIKc"ʲr} B5tDs=hu0brjIߊއB`:aЖ^i&V/=ZpACsJ4Q;Q҈e\"t8TnM/sztRļI_"$T4E}bTL귶8 mOkPw(s1Iv*jqD/k#\ɱm;|FXf3lx΁,B{ځ(qx-o3k5M֗aP>}zub Kmfq-m9XOUp')$x?~%%MԊQ_1&Uئ_D6.hⱨHA4c`' #{}0+cyẻ?|^*aEv6t d+K?mv;|M·ă[.thm !xI6v_ I'Dv.E`5Cȣ_-cO?]æ',e1<./Yu Ĺ Ȅn@:M[]w7KXla|kүN:ՑO}RNhY pTC#ըn&]? ڒjݥT,j;\M3Q!gEN:,+ %|P]F%seO(Ub\ʂ|ѫ_w(L9ܬ8Nʎ.J{[=F'igsH€{>޺~k{ zϦ 9 %4o rؤ|#/pdZwEor) _gLt]BP*F;(R, q{fxzXXodETbN_ijO?TIR[҅@P?G=Zʐ.*,kSƝtՇ=U^wUĪ]MSbo%/5G/) WiiadI]-pfgG˴7VQtGުԢk, 2a-U4;|vRP}Ϣ;l J;BTcP3M-<=bեbgܴ/WUSS] w:kŅ%/O6'*1=E 4V#JNOUIG\ N򦓷>UQ sdO#xi/_L57<ᶛ5K eNV83ia"i wgZ~!^4WɤZ5-*}]tٵU8Xj 3Ɲʈʹn*)kK+W-afJt\fOxYt'"22hݦ]Zr˜.M\^ 9%6;3ZaԮ)?>a:~`VұH !&7K/v=x'305F23D. `J`\s*'Tq^ gQVuSxj,^3 t.,f;*^AGBlÖ?Nnjl1hh: "sȻ&!?G^Ȇ#@XF!h(/8)FH"#t1>%޾Zd9!i]Bzc|U>FGp::_v|@Em$ttfJdu<~"Cc9](C:GGj@nxCU׈Բ9_[CT[BNM7S쏷rv*襥2ySf̩ EDxk(e0>ǒB7X.̪$t&iKsD0WåJ9縏Dã;T1xV9G¶H| ry5->HhX"{nr<~m6? z;ӣM]VEx^b'kM%ZkHM:uFeFA@G#t4yץ׋N#z#qoOJgʺbָ3=Q6x.*{>S*$s5+B&ŗ`6 C(rxh,RV1gnbƐCZfͩ ԡL$f,SPwW8XGCu7GJ.$$SE/ˡ\Iё?ӣO({,,]UF Jz-5⪸'iCkwRJ@A- TpGmAe<0 ':gk:{Y+e.)cLpL# +KRC@CI,K(Ϊћݴ+B˲mQ7@Uw9XJj+(rh{x%PilV'K*5!yVNo掻/qTZY\Qj !)T6EU^cnf֯kbl*jZ57ƎוipUus /OʺHBa!C}d߳b]YJ5F=,ԕIzUZ\2֎xp8]m+hg 85Tn.sW?&Ԇhl 9Ua:䦙rR}G^R+%oYEyX^b雪^?1A4/Ρ(XҚ-QwCRmX_NgY]q=wzq{nN˸?a VXi,'t D]\0JJ)ÛI٤!U/:IZw_s;FvxqFHo". f&O]pљF vZ|,X*0ırL6уu {|bЖ44G[ jc1ZbƖg"o=Tk0횮6.n%nVj(v1iU>czT_#ohB߳꼔O`@OQЭ ]9iP ]Q3]{^] 4.Z=+QTI]TG4:EheHGz#DS MEJ]2 1#"Lֈ^#Ȇ PE%́Hlxn (,%ȒWlO8gKo=_#Ʉ8|\0:9L&fWߊb!3D\TV!cQ+|:nrk W]| h1BWU鴁Q}O1d@_Js?vU2KNXEuUU%JāA&⺼%!;m L3lYJ#bc">Is}@dРڜq @ZgGvh{dURJn`A.DREI[TJ Ǥ(,w,;ߧێua/a> ,6#EL Kܛ"vHr|[ZT VO}1jEQh_CjB=YQSu| /L!{nu){'i25iZFU$'(͘w{S)-.vMݪO]^ AE |ipn[<]}lk\=+[b02 \Ԫ!ACV-?C~xΨtJ+ foѬw=%;.M SߺE T,k65:gDhP]y݉O@|g=BQ/$Y#w,e /VW\ˑo-ʲiR &;׉zǭ.I56l6L:ui˺ 3ݗQkaeKL *i(ZжMR^%q}CRG:Ǻ!D0S Z=Y$PB8*r A\`T%sXŽ+Z>X=6x|=)GMU%P[zetb$ 슈OwEEDe(402U[i 1|ˆU0an%Y`6YR+cQD׼d4NM_EW4(==&8 DHG qЮ(>4+cGDhK#| 0OT8ghA\mMN#dEQKuY )꿰HqBs@;]A|[lnIc}_|{WI,jo 5/U4Ž @Wv:xj: 'o_\"5_,Ǝt'hɴx˹*45v"1ͨ5yG|٠uNo;j/,CŶΚTGYf}ggͳ+c)EٶXL\6YNhi%]KRJ׊tXUӁ{ۦԯlzdc'OT/J:#F!!=d{hÿDI_Ya{oTIwh:!STu=wx/)؃F@6r p(!QgV> 8W5cM ,pLdifyTB=H"խ~·gNյݱu}#'fAG[ql4h"1z \y m\QsCtlBca?6ѩ)Ӭ9Uӥ*EeRZ\r%)>w-KFR%9NZO&U^ { 6G7M0*viݛ&I}_罏2%9nb1ۮ7E>%co"+ MOV&K:ouiYF̧2-7;o:Kj2r[1'!|M2Hlq歷6>;fwH|~vxTu1 Wk`ܔIR%)j $ VP8 +Z~K׻K"z 5GۤmB㈈j~MX2(Ím䆪Mǖ: _.CQ™z_қScBDl* 0MdwIIsե'o#KSp}']ÅP"Gkw/1}ɮN@kSnsg?Aj4%b"ɣ7B ɵQcmi{RP5LbmϠA0a ZRCx *ذ @_wŦlʺR?1-4<>:4[ueTS.҉Ry>cvm}`&q.rO]U1eG(q8 kȕ5-Tz˼Zwp'vv*\'婤ljT<w\W^bފ. w|:*>vIMHӜ<lj]ϋ#\HUCmE~IfYx}s>Y+sNϯ1YE}~NMw&ѓKQl4;8° w4TH+ZU9_69SQmZ~5'\IJ=(L$ UEO4$?QOyL"x r_.W5jLH4C) eK"/ 蟮IdT*YN@ K{иngi+)M=k3C^J,q @r ZED=Ly^ iS*4蕀T5Ҟ54?[{W&)/Mm8sY*ǹUx50W!T$39Uf|˓/aEvSQGJy24_"5,/YZ㱸b%. 6y 歵WT! MhZB jM}ZȖq'.;!a׋ǂ7)j&hU''a,JC  lEGХ|,Zʤɸy-o/wCCksvzVmu!F7ћ&ZVttRu%Dt6θ\g‹ZSz7Po"t:7ϞbrXl+B@,Jֲ_3}v weM~7vY% ; v1!0O\BUCGܺgf kC=o)ukgfCzH);M @s8(|J*|Jr( 85XF+ Gؕ@-Z®HO*%n!ݴ:|S݁5ueGĥَ|*;HK_Jcc#'Y&]Q!$OQfj!#䜞Ig 2GS5M*Qd@^:) ģ#ѷϢ}|rr*4>=NWBD* & "[t ScyipKaxHz96+oޓY\35mV7`yv峋Zd:ji+M iz˷WaH;G7)D1/YT_r誻IF. B(uA\gW%x^_]Z)~Ra*:ȤPY^ExBx$v"dwrOϠߒ(!;W!*% #JG@ap3EsH^-v@}4KcALDC6Ǵea\k]XTF݁O0Bm=QBdӴ,k,|SkK[h13nup_4i^P0f>1W.h *Q^tFR@ME3EX3 ];W-(~R!Ss~SJ) ]]39|hI k1-syO9q$>Μ3DBj贮'msnWKX׭ VαE/Y2gEA?v w9֩]/PX_/E@L]Il*4YG"^VB)dsQfG( y\jAh^DBl'oCIŏ;- )CΫO-s^Jb$9OW&]d]š2/Ay|xu5^zo"*+>VfOpRfpsL3c^R:P xs\Vĕoib?t]sB@]{XJ/Ha"c@ Jd dG C#tzF&4~f.%%W9q{}z'k+R֖Gh==)kP 6U01B^*.BpyLrϸtϡ- DMkIz/i~=\ӭΑ?~]3O/&{t;XkJJ/k}.y L=9PnT+E9a%UypEf>u~! i"}*l ty&c:ڒSuVۿ5ac~ӧ̳]#THn.KQyQ@Cb2Ԓ*oR+Oq&þuM0J=DRSRJ6um_K06kcIR5٧0 -)η)3藾X 1%VHQPEkoɽ[_*k&I YExu@F-G줯cY$OHWAKĺ;L_Y,J9*a IެidT)7|9b#&~ 3䃠XxID5; *k&#E}u[#SQrɭ)72,xЯl׍ @5B<]9EJ7IJ+3Q3gxSP8)O*|Mԧ+xSIemH+X5{e),nKw pMmr)SX-3 ;aOb~l߲`*sZ[N%!>|+$9\Yoĥw_JLn=ckN!y`a 4c'UJL]o X- .O;U:m) 7huNvgn6leinWϭSf*VW糮~Ωg;~j3ɵTGJNe xEL6n\)]BA(y ^-h궙%_)m;*%և>Ce'D'ݨ zCHI)~E""uGsm?\c$JIFMm=8O z3vRXQJHM`?%G ^S⸝nKRu#TpJ2@-BD8Av2{k._04D2Ƒg0ܮ SSSh?2E+<%۩%pLf5I9z V>gZV5Q|2ʺ>ʋh5SI1K1Ъk֍@U$k2HK5e@xuoU|;]3ɻUVb IE%'U]щy*2NuFSiƇdOQD(ga\hb{}ZN͜3}^f/YNڞ ;T],U'A:v|xն˛-|S\YƉE)*-|~HdeBZV)$јU)@\[N 21PA|(0\sLʌ(ջIcd)U x✾}շ 3.z>BfSt?毺n)O[pZy`wf +"Ƭ= c1(PpZ*@@Zs0.&w^)^^fkZȷ1Ԡ G Tcxy9JǠGClAfTl˜Lڄ̵m3 jC̫ =~-h|P9;jh|$ƤJωti~VD=>:Q]fzVڼB.S,$,ViTL:qs\Y,vYCRZv,;kdc8GotS:K{ N_SQdmfQ4Īݳ"1AF"6K:>4bl: bUI|4P6҇!T~)(R٥-%WZקn鋪ZB7 X)axdLP!B-D!/[ %`_be7DAb1 [I9Ar©VTI `{ۭ:$/sBu֑Zth8tQ=R`#a>%ʘ1mf\z4%Ia(B^,jLRRq2+!&O±Wz,YG- (x:h[U4Ɓ- zD RdešY3\-BsJ.1uGa45Rz],mKiqCh~Y;rThx#!j1mOW>ⵜ1j56^RDQfrˊp в'8iWEѮ'ʕfҥh2l[)k]NZq\]cWϭZߕhf֒?kdu2.Q^R@1 wR\&am~P9`"RUvDʵ,*rKbd}=@li%3P37֔_j]x`WotZvB^/{ꈐFƮ0HЗ|X.)fڭ&IP ݋Vܸ†x/'eZ+xtNS\|P+T|fS6VFh^{.\[fZ,h(?&&hrn*ގImM >EQm)o]~ x-$jV=I0cROgH< S%&E.+LzDljWs3lɻ14!گW"[HgtT!dѪ(C{;;J*ޤUI/=%n0 P"z$*~HJ)BPA!$T$ջ ñ/3ĹʼnbE:>mU8TxR!LԼT֥}iH4H\U(: >q KABXmI&!Pe])|PAá: dN!ujBJiq+Y$t汉"/)Hf_:^ ToJLY-;jBeLzYEbCUFcU=%iK^\jdT𐗢6PtCLG6RG""eouR2(BZRB!EP&!e`)RR3} R!MLR6tp9YpQuHx+넄"_~-ST3 1+pBi^o᫩Fu )\)jFnXacl~ ]mTCxXEe\f N\D=Y'{=rE.G!'[E)+ qAN^S"Bc9'LPKmUwd, j%o!4̽S.D6VIpӞz|<œLCYF]E*pT^BgW3*9^`RC|D4f@Bс9#i&I&1Q6$6[i#--'cY>|2U/Q=A1i(Pu=%vQYe^o"S(ahPˣAI*5g*͕8ϘCWyO\Pыo/ѧB9)2ƿA3-rznQY1oGaUIF}UȤ˘ϩi)X &{UkE :qͱF4fha?!4du_0B1ȧo' :By3|mRtzlE)FV]Gh֍UWdD0i1bI1:f6JsgXޫh}A]NR8UBn!Q]G9Qs á?6l6Dʢ$JdCDW5Ϊ*lTEU_dd=苵qA@v! \9+zuES-*# 9])ouR>ZYdJIn@)*žϧ)TJ%DGV nFOT[cNW??Ҡ_XG^ļvI̮<);tSe);~oCv׬Ud";I&U"[yM8 66H!fsfXP3rW$*]o`0%+kzQ2<͇M䞻פȽķC+ $t9+|טg=E9Taq~{7J1NRĝJ!'U, {e+2$[eM&VJjCsMU}ֿ>\0uO,E( y}eFi8ێ8RvE&hh"=6&Ⱦ= s]&OV.P-W:6 ᫇B0P8?ΒE1i';s(X4H_P5aMQk b>m-o;ǵ[6 WjeLje~HjT^Fc(I=QO1<͓1,b uN]^"8O>#yt'R+)ɡcV9k *5 ((IeQjhNUE)TEmx׶i.n^:D'kfWi-;NTjvἧ?}cP-OrL #XE%)ݪ4 H. Uڶ |?QƟX%r\5ail4®uȨWz -M RM{D:!9OM1N;_%8z 3SL թWm 93tD ]ۛM} LİQlW;79 Q$8cX+%%U};X4 :V |>9޼Qp.K"~' O."˩uX)&Gk2z>|Am؞"=fOT׶RgĜ2OE{2۹/!KA*=b4IT}ExjQk{˼<5j IaʫY,o]*0{+φ=AWTUęRF<ύR#{lz$ UGZ!&ĬMna'ʡ:Mj?&-I`IY_`*^C\ЕY3(lMߖL$XE 䳙u0ߧߪuu!KLآѝEM,)C70{t1 "ĢTuKn1vwq^TmkBzJ֨+\AQ't%Ws:V?eXxPv12ꂌɿUN iTRS*L"DC%O1Z< (MI)ht5"?B"&'F3Ann7W&!HE:3Jc4øl}8vnuy*SM/%#v:&|0'Zdb nwF=f0yAKMVUc7{Yca_Nŭ@qBSDz\+=' Qa6E*F%) pƹji/wfVQF$| CmGɩ\v#bJJB{ iKvFsbV_ůz(i76PQPS)*^:/ňY${lCehT'XVu&_V,"Y#w}tCo:BQNҒIlGT c*%y7Q@pk\ӤQnm&45״t2wmur-n_nsİkINZ^a:͡9""y}>yW{ov 8831wX1F%(`z'#x<pj#Snrv9~$$78]fB"PSK1( Ѯ0Ƭ\~7Lez#Va^)Gn!٤M[z*iq #g} q^X.e{@qW4fH)Dn̊=tHpEqU(Q])%`2qJ\]* 1w \£߻2=pWxjsfVwiЉևV\ *=C[gaH`8;"$2HsԙUc\l'<ٚG0JuwD4/45,+ɢ%!C ”0HroyE1dD» BU SXZN%~` qKQЮ+ɫ뗧s(2Etbo[8TPCF"(g6=sa#wki#(wRdzQQw:i°۬5ߨքtaItԧF"MOkCSQVQI þ}7:ʇ*CJK-y~>eAznjdQ+G3XIMZA`QŠEҨ)7Ї/*s+k+=>X$,埲0Zx`XH .It2<-b(R" ]<\}J^}>7wcԳ(0-@ /m3e.FO,Klqc@τsDa0}WE^YVB *ӫeg,NA=tb>a\|M%kCtd{_RU,>7"a=2B˲ Ww<ԦQ+8 &q]]ٴZ V!"}f%!  ,ʤ͢q"uӴ& }* =U%D m}F Y-ĽAA9m:s4".ޥI{}6q!opQ;mME5^0H:`\R:bkш1fӡ, PW+rdUW-0CJg6T?_ ;m[M:ocl%Ix6%K6 ;V>:ot~10(8m;!~j4i9lqr֮}.ݺy>ߍ0t4E7;$ՅS_5>tPbB s^[2[BoZ5[[Z9*pޒFaSq9.0%@Q[Lx!4hrc"mF͕c]W$߻ݲUgm|]CX}xn[QFdSx$ӶXWֽʣv7ĽWW&$5*2i©[[uve|eU_oM9[w9y?wk!'Ԃ>EwoYVu!#lQW\_wrkr+OmqJ2%JnGu) "c$SQpEz l㽳y&<е8j-d ?"P;f2<."|ꔋ]Iٻ$ˡ\#йvf]i!Qa(XF[ jyp-> D>c!zt/vu8Mv~?u)$@OQ!Aͤ&_TTU 1aLg!umKJx2,Mq,r$,~T]X{uCHeQJ %Tjh_D ԭaԡaݽ.u>h0?ʦܒwfH/&N+F좔!%3]#TF*TFzpdFDd9IAۉ)]ŃobDݡv>v֮-b:^q^ x|l{}5ZWn4E(-J^f ER݇YkZՅ]n|Od@C-itR*}G=7ԃK䘮,!DؙR)[`-#HPa~n{!.y|ɍ얣\3B"|\Fw+Zd!1>lilO5%Q!ߎA\<1DOs~N)1:[W34}Z&5^(qM#="XClkhX׭C; GY},d`]q( Ϻ ^;q?-9> ^L /p2t7¹D>Odtw_1Ԟj=yl_^S(lnuEM> ̯6>܂̱*آ='>M*߽"QLBpmoݤ5vpi|hU'ƾ׾-+zdFͳ_i?vۤs NSHKi<)`j$ݺn"ҿ)pBҹ}ieT}K5zjֹjܾˠkaaP"|5J="ǢGf!`SԷiI-`WťLQ}2jP|2M^Y#}*4&zqʡ:Qa6N4f__L5O漢ҫ:TӆYFQEs0ԧ'-rؠ 3ns, 00g(œyGO Iǘ{=ŧ.`kBf`|Nb8kԂDn?Ȓgm d~U %Z)h~=>J>f!JZI&:cRs3Jo+j:TXhomT]M*5Z6pRHm$Y]]gIE"פ9g{F=,ޡ2FRn͛3*[n]Q(5I k|v%$) I I+f'<3|~Vc8&/L{n!;k[yM@r芎 o5B`ZihCvTfeJIYt}>fyOlcLJRwG2_'&Uz5J1CjJqȼ-jΦ4d6%Pr#3Kqs[B89v%OxNTnwX^7g"?G-_]a\x0Y9U2T_Y*.0w2ǩonfۡ(8A15nBh1P_S*Dw?1Ogt\QqG8wMHCD=e7ފl\ۭ2bO ɚp CG)++1ڏlm:JY@Y\np_^ Ѯ4ˠA=GmBz tYp/ȗ1j5RRK}c2)'LT Hw˙.GĊe~]kmdr,J[;c4^jN1XX22NtmkFGÅ>?3>jk;ͭg؝g?mx^~Yqˋ]k_sy-UT'f”yI('Mu(ƭ*RDYiy{ U~uZ;zȶ=f[CEyd=%$ț1)Kb4Rx|Ep^6\G%#U7̭$,b]۵Ke93cIXN7|,۟4?rR+hᲯr-{LU28'/$REWrBQ״L>OI&CmٙTNo16IP!Ѭs ֨D p" :,ޯ^)y^iӐ8,}W.&CnO~{pLu }~P nsU$` x-~Z 魞/.tgdnڽ _ ;_4.B'*i?k?^E:5qP79U>bq^q&mU]ۭ7X1SD5-d;|sȋ tɜc=o(5Kl _tHNq:4ܪ^fc0mFE5\"96pPM:n }A>78m`6nA(H+(2B>>Oe3'm( jǣץ-M0n:%ʷ#HJp?EvH8_2}u9Yg Zt׶oR*);jׅO+jIK7>T5 :{ 7تfiR^* 7P늦|-l_;g4~ ;HF؅ҞLl/L ^I*.13!*;SeuKŊaZW|zWWükm.X`6N%si)ӼFU)?ji^)\eߥهnb喜bRK-! )m ueks:Uv3jB|EƦaXeVJUy3"| $kK&ϽW_Q8rL\yRtΟ񪦮lEkHvJZ]Ӟ\9JI4՟ =,4ޱE/QLk} z ȧ1̢Ʒo-`μ栖egkK]Cu$H!7HUe>r9^% NmCB?& ~ | 0'l="ˇHApa?oeҺ{VMd5Mn+iexwʴۗM'OY*m > 6b&K)ZB>$q>#PÓ{m*+'L{,}",swT:Mشmh;]cuC/\-k4>F%t;!/9TD7E)"&ԽbxK:_Y; Q<6豺xXWVci;*԰h~]T,]B !MUeM Z[NiV}eb s"D  @]ET\r.][9UγpF1vi1jחXoFy[4:=Κ~ (.׿t'=iҸw/8zvw,mMо:3 R)aI t[;"h :6EVҗe֜K*&!ͳiɭvӻ-޴oȇE4`[3=u-߻H  LT\DKܰ.6,'La-='JwKͩZk(\|^SJ)o`)K2^kYO>QWՖ9)ı6QݺF"i/kŵNY,L2Ʈ7'&yMaрJ!%1XZ MWAEQ%!WY%V&\: T%-UkuZ~(Jdr~gUQ dz%zvoe*A¿Kf۝y2iگewU.rȡdQ.Q4*Vz7^woʯ0ZCdK5EwLnmKU]M*ªϖ:?JsU9wUhɺ٘^]W0nÊgMzἲJ! )kUn/]_Euߜ^AgB OĬJ UjΪ!-81h7d굶X3k$o]߯~i7eԡ%O6)|RFO3qز m;VZ#Z}^>$Vu+e/.2Vv u8Q--U]]>ʓL 'Ĵt*i ~4$BH/~tQ1sHVmWԧWI~3 +P:k/(Ԋ8%4e%&׫cX3*d \8Ax@ HX˦j)'A!BƟ*J6֚6U+ikɥM4"?\zEԥrUZպ֋{FªuOgm^[NXdVثhajI`ש3Q0KT*Gx9a[o`]#U=X7$ ʥA«KZlZKvgL}kf D,fHĸ(ִ0S@Q^ԡ& eXT)KubUKel-,;Wb૜"P4Tc_;dfC6 $r]׮e2SϞ7TXa~, _wM%897^kfʮB`%(tG'j_=|B>lӪR.XP|=Qe YQnOa9 ͞.ĆGҐBhcȿ.Cۑ PK2x %Ljfŕ椄%_K#EI/90q.G2LL&Ī>H(:uղzퟭ>ܒ>w[wDטi/}sڕh[UGC_ط~/_FY[r%Sa38x)bЗD|ɉvf gJcQ"wo aʱaI*84 ͚B.zSь*J'i<h[ösop~+h^ykw^ZUvrV%$_#"OuM1y[\ǘܭnK*)EmR)v+=F)ui}qS0hKk ?aϱLc}KSikﴶSOո}~TfSaw:5CiѴ;C: %s|};R>|2ԺΓG9mOmLz +hVŖ}]WD0 Tr]Jd3eӿ%6'},NIGdxlKCe 㴭c`2_|YE'~.U1;%=ͬFEds[tB޾VG$iU1x~%*&IUu9 o^+*(O2.4q*]yApU-)L0dR_2 @Ü[;u?ZVlJY,u^eE 0(7z,$^W}5w' j>mZK(udM)t oIDOSE^J!+lԧ psEUm+cE,ʧU\Hd2IGYgMN b|@n[G(Od5wE5+JVmN}bNHLhZe**j |xꋿ_Ud5'KZ{ =x妛6[+`y4"5)kgIQQ]nٺ;YwQw^+}.Df0~aodk,Xe/?>wXDꚶi6yCf+:&7IHN ]5iq.}tw,mmצ!_UVu<}gͲ(׬mrl!PZf/Z,9)c&&ґ]0ۺF>4sRQO&ˎ<=|},JG_pH•FNmQKc 0ynS7PUYٔ#ry)JW®UxS$hjc^p[uf+OמvTN``V{eTeis]7;V:NL,V7c!YȖKn$|`ġ='"n)u0?[Ql)Zt1 ^ Ub%?yx{E{ ¤oԝ(;i%.q)*!6Oxx^9"|?\ QHF02_Fȼb76}2{ـ#zE$!T흤f_FY[P9X{3 Vپ+0 )Txi\Zڰԟ,'I‚I(Dv\Ro *J^Ksnlji*۪fK؆ٳw&;b~pZ]mRkYGXVw)@mU˻%akMpWUFoT{+:1w c42UUrjv6mçbx]N y1d9dY=lϹ !ʡh{t%9/ۈM;@rL(j=Z4)p뷑iᡥUFIAK%g]Q tS#VdCEYX^*|SeSKw6mཇL5 _RyߢN_W_9K_כ_1 XeaE"Z8O9 0uѬ#r]BP?/L}H0+i\kJJE]j%BhWDaDeFHGZ6/4Wf*/}wua +(T9ϺHB ] c+9AP`̒́Q3UKOgrVV* = vmP&'ٴ&MPo!X ܣ҉!AJR$̉1TFzMyFQu;:֥}ejW0<+ T#Syum }ܵեbp#R S *L"䰳/epHn AhHw')QMB^XHݠlr>(^x, m1zQ _vNxҎvQI &OaGaLpHLmnh@Bq?>梓KP1 [E% 4M2K$Zfgn6{AvĬl(oEd"LbDea6]CG!۵8 e砃i _Uy&2ptG|6*csK X/%g,Prd*)cgx7Ef3K,W4 C0J.ab&ܷW"eRUddm-*l R!XF) 5(̴cfիl&.,=? aqp ϩg74M<3ݨkj hݶqdLLkP,0']xRe|uUI)/]B;w.,!ק`G`oʟiŧzrnc~w䗛z)x,~d<²,Kr Mi/0m?6TE'ڊ*7(Ue#qk"V1u7NqqVv5خ=/d\D2^R!p@h4{-"9J[q\hh21̊*bٔBcUe_qZSxCWk)b#m͵76A6.5ʿ 2[$|N8/ػeK,7H7\d®㵰toB|$F좔%zM!W{2nly 筬R=um\UuzЧTW1뭮c07vu2~Mv=HMs- 8G9んbhԗ'>^,D#dlbUޛ$GOcz䞼x O1(u%+$]qNvd3BvHM8l 3$($[ #n`R%z+QHDTFKTPRHdȹH mpU34,P(8s^"*_Ž)km5qžmD!%ڒ2{#§;0@.zgJll*nDkGB+o_\:rt]#31:iƼ=Jb{^St.k$)Yz{Kz XLK]7~(.)S]tQ8{@+I xCnJIu=E^lpdt1w=$g+5m%GNPiN+ڠT J[䣄:Zʻni$ٜui<`W8~7)*i5kvׅEgUg'$!.j9QtI- y_1gWK"-l}L+-%gmz&ZX-m;Ȼқ$2 !b:KDTQgz체=Q؇)ZN_R-Jm*[]6E8[Q%~f;Se&}BPljbR rX@O3.Skb&MM3)uQ]b^3C~N ,5@SigߩC kK"/LY&5M:r X͂ ✝QTI:iZ%emVmY~fš\XG}ͻ¦7)EAX  r9V5FV6# mh2~ su@IӵOuL7ㄍDrXcP0lz M2v6QҾ\QFR;㟸RT"X )6`lbYzɧUWIT]T CTXVIvLʐdMElM0!KIOyJmC խF'U#}j:z cuGУȬchףPVGUdI8˳gE^^ɽy ѩyĚ; !wۨ #CRD+* N~HԲ̩ tBaFz,UR[ GN2f%ZY}>Fzf]Y~oq2jCif_np{jTtޮ[sD}[JAT3R̿d++PrJЕD݅RӗX)kQR.} Q+L?EW媪 Bp c.w~GnKO78]HT9Ob>U\6f)򤱫b4Ă0/~Ujtv]_]8M#. =t@F8 QMӔKԇnEr]beщ6F6!_iFUN;|/&]qќDD^AQ[_h*w?غq jyKՓ $՚ez,%w׉)9UOKL}ro- _f_YdFr1#c [D-a'Zi㳀PGS:Z{xi hdU?A ONׇ# XN4N[|\r Q,ҺwͰLaehԺЬoPead~Us ry a$ccc܌2+kl9Ɨ #P<7aOܦ-;X׷{ÒfF XdMI(9Rkf(Ӆ+T.QY{nߪ|8 ġ9 \F`5S{ Dua !Exh a)V֍=bRnUZSCO`|Nu.=r} 6}lK % ;&A#ٷ}Ie{*f呠:LR5FĔ)4RYwg+rKQCt Q~pq>$}i^qWGq[+XgyMA}U&I~5UQ8f쒨 ѼjS.q$qQQ^>ݎ#:Qu&&妺zS~JYT*z :]#5 e90(! UpRؽM,Қoؗ8Y-U:ԡHc 44D8/zRĦ oVR_  ϜUꦖ CGX>u=^k-ɫM!O) /\RUT?o~NpYy^![SpҧZSVU*a?GqYfкn %8g"F9-QNvƯTzߝ@mC5ń!%/X"Zu4Xw"в+$ _COf:c}ke/ISnS7-ߤAOOtK DՍOUÒG+ 8K8Œ̎'Cw3.-N^Fwr=w*ޡo_>1h9PQA=h92FL,K;W :mO DL)a;\p3xe/N*H2'1g"̬^͕ܹ)jUB`[&F՝)lSn.I9ˮ ƼJUzʵuS8ةcHUO1kͩ6-uq.RekG:mv{Rc#g[^gQ<-yK|p%զ7mkJan BD5z&7/U/![E~hP2HxyI`y Sr K<@Vf י$9Y5ruɲ2A^}JKCqnϚ$"Hs?ѩi.E$dgK(ߐhȥ#wp)Gp|\q !{cvE% xpJ-*`}T^S"ze3fٔgDV]Z,E$DFM'tѽWK_Zq/O_QʽԶϗ9:zbfh*^?Qd_I!ue--è67ɠagFvCĜ}3ȑBO1k}$㝽oeٳbv9ƬrS&*5.$$ z ü934F׀o9H(gˤ++y8%Z +\@]`=pq eTϋ;2^w|xC#Э3N1ه¦+ZcDHY& I&~K+=ExcquMn"ĖQzY\=|5$QR!=dE,D7 "Z٣QJy kj[ /c~FҲ gUxR^R/=E)" EE@s̚Ũ;)!TJTB)*F< -.2J\4Ӏ )9/2-ⴒn@~e~a5e>.&Q zԻ<+^z0 b@%q[(Q>)Nn-JP ;pK3ܗ(A"޴NLSK5A>D*@(߬/VjɺLD+mv9')XQ5\n_;ֵ@uca"rC:hA~KY_i".mW=8Yv@RkcEԿK 9vAk>WOqUNC @!ҏ5gjO,2X8,m fk`y)!PN跍C)8݂¢ƒ4F#uo-&p.P@>Bq:=yi@h/Gu{$$)퉉}@i؊.g"'km<r 9j"Pn#(*e I :Y6Vm6s˱XՕ,Lb1ksx1I6 O`¦*(IwZ&!\Sʺ!5L jxP\rUYSwu* Y]UY]lQ&WH5৶4z UH\TڅcThKRz%f%.(IRs~ ?PHٿPm544|IZ3\q?F`?e-m[yˡWsSQ>vP.J/!oWS)|WM>fM kaI)WXt4l-bUv,Mה?5D='4֩kfOTG%Ny8u AO>5}F1QDtJTNVYֱ=Uevؑop_7إ潷EXq;~X9AenNEbY]d0ቬ|O0/={z?:'vldǩ p^G%g0?etK9g63m5(= }" yH3FK <r ƺ,S}}6<dןCOѺ\qf0цp?Ak0QRo+'u`G+5V3LǤ9ρm}::s[ tFFscX7UX.kM*00+*\砖UnuQMR؃ !]yzo,ƸPW+dUQ}P K*QltMO^%9=8BV0(fܰn|+{W!>q*rt.촫 Mg~/jfU%R[GꙸI?8[ȡzΙ h!"a=,-{mX7 0_n! tK~}QEm?f'scx֌\Z#c=:LR2>K F*#2L d} WWMx@?~Z .!7mYQ=[9mF]%~VĒrՓ߉(e^I,@f %:G*nx3](O|F3N9۾kߵlv+ӻZp sQ;y Se؍5it%) &%꒬Zא>+]RF(|3'x|FbDKVSf_;;LJ{+jz(HuqvHiNL|ª)o~^z5@J;7áxͳ'R_=q_RId5?\\"ܭ{(> ɥvbQvjŭ'JW] ~w3V[RM|Esf`4e|OVP-ՔCzSuRc֔cn>a&q)7U}_#[5)OJ=4I5N9wj﶐w|WbݜYdSL<;F [zHmH!36amSG`;N"QlXHK9eLi&cS zOKw}G:4bXX<7J "81S_.B[4pynImzR7Aw 7tsc '&9&&x]r inLL+1q ێx fϣ*aܵ-y^"Uvy=rhTljcX+i?9{QW#mY[mU'C"5_Rcb]l$ W]7-w(JFE5p/T}BT'geZ:͢t z"겜.KĪ?E@ ^+.) 1+O)&3v1UFHBV.*&LkJn[D6U"-B`'# %Q I^+g+nZ2}d, :JI[;=KmIyʪ6qNMn8 !Jc.+tqzAซbx)1M'-ڸ2N+iz!\6;4w;|ljO^r^XUL y|uG,1FdJB`W7'LV )B50 Wb4"ߋ=;}NDJ># rlvHb["c~R6׻\Jɝ &_0{کVCHh"N0g] 0@]l u~|Lq!\ la.gπ|1Z'--!;ڌM<kPw=+\|X?q˅R;Fe@ꉁ _aDtP_csy^ PD>5 ͈Wt;Ƴ8jɇIQ 4֥䓌xf'Lc G_#HeUceU!L9M/CPL:EpMqTҍ9:O7b0=ˡ!cs%% +K@I3qs+^J H DX_)kJݧgB?|J5p%% eor_Fn!G,)šA%y54^#o.ISuMa/a]ފyr~5'ĒmІZ==ٳ(ER &íXVſ4wGl#%sa?\,wd%6ZT?~.L ?qj ,i ,6z* 3ƖzoNr| s>~ϭHZܗ\X%AXcQ_Khj6&#VPYJ닞'EzQ@t}&$e X CV[&o&NJ$<ʧH/'ţѤkҧIjò]UNyrW;NW azɗ}Ja]5Hsܸx!ݴ ZTg <+e0(hRMRpU棭I`K]#9\E;byeaCV`-L klHta\L͟lRj&*Ҋʖ=EQSGZ:-gu!) [ֲ_cF#ufzFW2/)dр54-(eeTzMVaZQS,|D#2eOViGR'@EmKH+K]EDRPT 2YkC*MҵEGw7UydJTV9 g|(,HKk3HUVS+q[grӴ^nkpsUsy^{O]PV"U$ 5HL膡=r-nUa"g#=o&RKy>gz5aW_uQ_n-șû`u?Rc(a\ (>s7gǵS#v,s7%1;#O+=M*m+-C.40g >CZB&\yO#8G^08"ueAz;bV&u2n),q+: c Œyws# Z7#}[V7>R3Cퟚ*xߙQ,3S^mbY>1༳}kȉՙ|Cr_RL`#i(>HctoBG|WzW;0%vMZ9 [rW\˚)RU*ZZޫpMazdlN+LPsML>K>/ K`e&rkq *Oha7Ҋھyx(|sL疣)LKt`HËuhuRF;YJhÛh @%ͫڥafTq A8&NwŪQKD_h0kiFHѴ7Dh z8#*/=غ>HuA,t`V2TuХ@f}V1~Ϟh0ruЁ{_[[ ye$YH">3ڒI-3jBa|svYv/"z;=73zzȹ2?4F}&8 ʅu/Z=o1YQP#nu&5(KVd\9l{])X6 ^[MxoSA}A{CYߪk"l.lv>_&(\exv fHױ3zWc2йM½2lאj!=Q%!q<4-7S cߙ&Sm Vr,r Y?7ޙ[e4>xU.+Xէ2"t9O;TAeuw|mG5mg TϜ]cE*ź>}l1R)H@hcP⊙3+rkF"]9;L.8<b^T)XФߛu7u w;K|jM2-wgj*͙5Lun I"w."bGD6YVSQvS%(Ǿҷ @US[l>ijЙ2n4%pЙ: aP[BIcqx_b>KE]uȿ[搻)n cS%ۧqhHVFaAJh\=U QTٝzp!v͂ƈnJ)/_A<pllbv, wř\,*.ZӲR(Ō~/D,mz >F}Bal"l8 iE^3ҷ ISmw5j0' Pj|}-1_rmS%f8 z\~E0XNh ̦ 9j#i#2Mqr<Ď%/4!1a,#2:~dB)<QZB_f#HߌѝR(5vE?(a!-|̫_(9ϵhq6Z{fږ$r"8&0j!FIc;U۞y Ԓ99HB1?i(RݱAan\Ԝ}u]v%Ѯȑwݛ9wWC* ޝ_喰)?TM8nTĊzCz8]5*.껇RK6t(as)ӭ:T!n6:H!z/(Ct԰FŒ>^4\S XO W muA[luWWrٰd6hs jME|!|}ʙP76*N +K¬98S]e*IFFCB jI^Sȴd!MVģp{*CCG) *ԍB^՛o ET[{,-IM7ReI~^S#q<'Xe fJdR\@gJp37$(%R Q-q?{4`\PuG<@a xZUeARZ-n kUzr?^󘔻XЯznǚO)ED$Q. -mmcv~@&T Yp 9.ܽ$SQDj8nU$ZЎXV=uf%*5 V>€yf`R(*pQ8H*4&ݳ-]Ƕ**o[KB8-NSSאuVeopW{ .HGr yLj߹M14" gdեe=sZ5JSxeNU֠ =4nr?iFM&qvg@?żXԥeFr Z~E=IDq© 7UgM6 94f4ABsY4R ,Z$UXfKv%<2#qkx2!4>ruNٯ˩5l|Z'kpYz7aQP56k:=V/+g9}('~ YLS% M87zH.1)z"}CXY(ݹfWEEb5zNcFae_fG1$\Gns3<N\u6ŁUg GmcGCaOaii: #N+u[͖(iG83]=us=lÚgf$QVL2ǀ,ϒ##`bL\[֝qAP R(/ڏFDf#kJBN+CS H+#L6uY@Aβ~96+B ^xFc -šMROE1ubע;,WY{GsvW~-!HFgꚳ3cElA6Kpzy3*I\jj&&P%Age%Ob_>i]VYSWݬ*Rѣad-\7;!7RJrMi?p9fAWeqyQB'iVm%ZymzڽW吞d"Yy2ᖹ u=2 O"2ƢXK+pOcnN>0-?Jj2),Dh}#KXi9Ou-LMh$A/n1/y)0C[ -iݮ!xΏ,zF7 g^/VqӼHbE77r\'nj<[,X_!q7,D\rMV̦3q΀;HXi .50QUy""e$}?&>F-BLشCarԌ4#9\];)vWr+yiW*L)nF_Mհvx,eA.t]eNg}I#?jm]`QY+bfEx%V7:<u:ڡhc;$wuqT$0ƃҐY(u.=,,8R( cNe}"]}> 24 hF#bOr >v# }Ko6ezm_?Fjg[o^i<|E%ʒM(GiV'Q  XKԧ.dFՙ{(A{2.  5x+ |,#jC #RE;"SŬr/u { se+ޣeŴɳзQF!0Wvċ\&TToW񱏡_n{κK굸ȷ5||3~e\=:Hpg,WUz֑9JؾޞbQw!sԩ(?5b>{7+e }dkjϣNRR n59a_9wvw]QsjGVJ+|A+Ӥ7Jrd=!}IwDҰo3Ƈuߺ/O)WǸɕ2 V 5oa)U%uwBJW'ȸIſ`Ut'WT;γO pWa [oeVݧZVfA\[։JVsVqK- D?ls(c$5Jyi/yÉD,N n$6ĪNP`g6Y,WmGhae Re $[0-2m 9GGDucwP<ђl͒[c^ζ R^%XOU^֔紫#뭭ZK( 09SI5[&Wyc-Gl;x$tmo|L&00*H tX8xy P{t'3O%WNyBAOr0c̟Q/d|蟂R'=XS`?@ɖv՝5s;/#;C῰K}. j6z :+Бae_B;8LGlPvO8yGb ?ڮcp*(GOqOԦNJLȊ\51 5'xjwz!cLexGr=mOS=\19ƫ9Q4[aQHGy5.X?;r[EN*mORل An6.;žssш;ƋV|'SzQ39/< BH7`1R͹=Q1T[_+MM0D-SN=p{pb!Ї A2sQP!IEK`&rRnFJ7 @F]ZcyDgesgcpF l} g?ȓ@"SwXBg]1h;;)]kcb|GBE#2/aVgkI+> ~'`B9u bݧ~b NbL呲߫& wbݜN*﫵V!0:6OR)7Kce,r8⁧ArrH|wЉס,ڌl3 Q뾋6z_,Om?6Yrɱ5x2eiizoJ-}ɵԺLpL=UVŞx:sNgҽZGK-j{ r_Up,uik+BA8=? =mKh>GYuCdCZĿ) XU #2OwC;s1()7| fۅ-j=Xa,*&(~ik1qo1u|a^zܩG^(*t a!$J,byu_5A9T?߭)&I^U=ciQ.-C*%]NZE-P#dL1,a<lSl~ae߇C;c#ݗk㾃EpY͚S n{rh0{r?`7,pE7뎘)rM} qYE8-DAH-P)ȉ> {b[UYU/7}Rh]K62 oݫpnL42n2!T^<%W?sxI myz6:ƃCv=(76M7M*gIVtƬimub ғCVқ2ComiWp}WpIlpҴ5z3ޫvh "EԿ3?~U=v]*7}uZ5l,Յ V^v HjQʆZ8floj:67hJ^!N&ZTא':dT*=JYd9cXcv>YNQdc^Atܻ[X YM-lb[-Tu Zuq|L+Fk )/%V5叡b~teFa_s Y9,2n1ٶt9pP5R)&-婿E@@X/D6 #C]47-c8*;7_]'ϔYK}'9a4Wf_ b:x.͖5 p`wMc[QIu= _{Mtgmߛz1C;hidچ&dў">ki,MkZjKy?4|c-"rJhXۨ;Gr T??؝ܲ9vjŨcAɖ0 RN jn`WS)@3]Q\ \QUnӚm~!1 -Y~[8p@u6E-h? 5ᇼ {Ȇ- kcR:Ѥ>I;?T{Hs`IF Qw.V$,I4ma{)*쌜$ջ;r|XONS@Wfp. Dnec[ᔩ%#]Ǥ0|r>V#@0ڻEX;q$ۺ+( #1iyޕ&гN3"{J\񫗙y}t8b/$WYP; A4~+tLw,桖@0"2I$u-p~{D,".հL.%t/Hᩕ3`,m!yJHAh/yk^oyNO@ܖh2$ODV?xj"«Pl} *97٢6 nQ8~2ljK5}K"~E0%l;Ja J]jXa+es髰 R2 =egHݮhbT9d9ţWmu孾'/onp3iDӶA{s$E&nP+jo]xGj ˶_)>׾ڼFڛ^v7q\[\Z7?vhi :?upYjak6beMgϰy$:..pW{z2iuXO9M`ш̺Ow eJ SE>_jwRzդ2bjx\QBSHA9$JcY4 3TXrmj|4_0ȬE(WPҔ*:M\w ]$DB!* }~spkLEyRM|2Q“ՔỶ]_ gbTNN[xǀ B5DeI6oOXn3I '7vO!2 7S5E_@U7-ALR[1W GĬSl-Z%SFw Π/G Y{]s~:F B(!!M`e0)øxopKP:}V/І[]rʥc&z,Fv$IPSnk-:64'3j4hP#FCTKiUTCWFte'P7%1T*ۿ1Ŵ]-.n[#PPB!%ӷSFb/V !$ d斴7X4G-SKtP" uꫝ{wXכy2-|N/y(v%]4b=>P0)c92Sz)/} '!}rN[-6+0? >uH۠$ߵӦUf > P{>0JOU1B{Ӯ>tPdbw=3khY4G[^R^ Yw¨Y߳ ]*=4:^EG'Ҍ{rauU.´G^Zr2Ũ Z^Sr? 5Cl`L0e S+g(dD( ‰xv_[4%7N̤=mkN + QR ġ(#kB4| #Hn 7Su%&  Oձ"^!,7Gh.'+zh/<*c!/X0=hyB#a]7-0Ɉ>{>mWWCsʗFXĽ~LOco!4cDyJTF @9tk?+m$;!n%<覰^e::r=$,vL۳//,)y ~lt]0[S4y`,3uF]$иs L_`%yIN@uD0t Æ19QRX&O1wXH:ddqӡdG $: \DTf\k@s[u^ΗMiT]єd󏪛v~؝`\smA`XO-bı 7cvCqDcYVhxOVŔPj-鑥AL'r(SGc1HKZ]׵vd]Д 'JL У{OiZsQ2%f#V|V67&Fx-Ҹ YW$(%:HowN.4oQO8-NhP=p Y NFIЏIv$+z&8gڞ^D`)H#q#LJR tiL`>_]p]cu.z菗dzⲠ=f\~mƐ>yH0|QIӗDljΙ7nݮnO_sgsj8ͧ+m'5X_Ɗvɾsvy)[]mF %p\#-jJ5#L]UMS&nyYsZvnb]M56̺QXY?<[ S̻uINS!C' X"ީʈ-2ҭ~i}j+<šO!5a}4"i"ݤa*:1i|q\n9͌}4iPT퇦"ߍY|QTzpTB.ifA)opbTkY0Vu(-J Z8Rk6e @|ROy" 7;K}2$ s+U M ɕРK6Rp̅QWX?pXnEcK^cPF{x$9&Xj詎\d߲3HO1P߾]ZQҌ6-qͲP]%u$ZҵJ^<]UT s]j" [EPdct*ג$w0 H+mO-+Vj;8yKySRD^TZvԗXO]:lk\l;k؆m5|K :_[wTn[׻a5rFLB֡#^:t@ mW_%YF7ͤF+Қo,h|-@S/a1֕Ba쎾\]2=ĝSi[pWU4g?Ei RPޣ 8ln'HP9 fIJν}c58ߔ0Vj*7s# g)tکl>`7H 7Jߟ>=co\}y|z9~FR|f\S@-#3?Qodj$ƈeS})8sv력ƍ NO`^9[q=-GE荞Qr.:i7SH>l6QT/2GB *jd3u3)ibyΒ:lm4Le.89\%웉Z?^;Tՙ'S|'QZz2X$F6q)K"i,9y;&w5eڤ(O/<Ŝf5%9o.m'*Q 1'<q8M|J<X!+"j6I0:q1LВ |İBcq0I];|20Z+` G"L8Ew8 #0 fF$O)#v Qipz}~Rdk܆yeQp2 G]3,,Ϣ.RrzJRrU|j8ja}9H ȵ+-^_H?Cn1Ը-F *L{@e])뭊`e0*m v(S,]) bF-r|EBU;Pʻڀoʒ*դUf%{I 0O" 0/Lf1fU_ROTYOM\F?uVUƽ^BV|TSrJ(mz'‰YzN~rK|Vyih;oZ4Kxj=3zIQlBhzWTγTԊRE򪲞R}0e XU#ARid}m`1qu _s4R?oUլwESf`RפSHf/=ezX7o陚Vpjl&sNCb )- 3\P&n1-8&IEAFpVnAZW}ׄrajyAfU7OC*V)[Xu 'wcϜg˰*⸎;T̤,4;R<ڐ_"U满&8\E:b:G;k( | dzm,!(ʯ*9N|%5ZpV5=XTӆYV[۶NgUvu?R1ES_cBbhQ IA5% wL;fܵMqF=$ZBAVKsiGߥHpFF-Fܾ?s:&"%U?L}BV,iAqE' 4 Gsn,.0-v5c*D3CLI?m_~ dOM6VWa8df3nM#;xO }OPskˢ&:=d}b+h7ְ"bl>|WD>̭nP:&MP^1ӨYcJX/%P)s06a]Ml* et}RPeQAF>[BlJ-3VYCTsx`Uְʾ(:|vM.|zE+ IJ^v, ib f6PzȎMG ^4c Y w-O<4>/Fdgr+H8G6>yֲ6{Z3ZEourBkN'?.1` akqAG)U(tۢ5$Qi_\i9D9_u..^\+C 7!kr^dF.k8#KՌ}~}e;YMȄX9(Nʥ;"zVYsx#$NP8f}$-]YkbK&GZBjY&Ż]{bAdV7ͻU}xW{KBԈHm4`}L)㚲j:ռ"Bg;pB;gheؑb Ӑ걪Vv׹E{6׋cel5D`\[}4 ɕ!BB9Đ1,w8Er/Gym; cfo-< iQtp*K&|%/IL%70Hj2:Iv:D3 *IkaśD<Ήo]W*MVSVXstguE:hb׹%H+צL$cIWyv,gꩻ.ISǪBSN(ȚZUg#;Ǿ޾Y7ַ~4ܿ#XE=U[_wj؝<$r5/I*zQS c;{%UgK#Vӹ>*ڸf.gҪ6aYtNSmBF>^&IOUרW_*|-r*Sf,Y+**nF(m#F;˸Lf$N~<.!#Cc@;)TC$5mۦ #%Uxj:JEgMJ.SJF>w+C2Vސdkl4A!?]5@I+ $Ī[['o*^QjSM JM3!k[DPuU @zJQbe=6q2\ΨJJzQ\{] 3k߻7Ho:ЯL7xW%aE=$ OqQ*jȄChټ >5 ɭ-Xk/u]߸iO!X]P&MpO*S]5gz2 :-*/j6~@Rn6'_k@z-oX_{@%!ky*WS( ?yuZV%ȭS-_fz v8Z_@&t*_ME v1ϵ{kHA/]fwoIl:h fUgZ|dg}7 k^ϵ&;U4 :YiW%{flSe6餾ٽ>2B)ur(S%߅qc՟]&4Eūwh*Tkq#$LWQJDE9X%ɡj 9spTY''fxIMUǤ8E;Lp[5Du;.I%  G"t: ZIQCZCDSimBNh%\b@%u'}16=qEP3p%vCo\0 Tmsr3tp( 8iVBOiaԧ ˗mQBp|bK2ˑ,E0_F!@3o9}aPз&jT8ɆJUMCq!B&7 G%Mӭ**u 6^ A6* 1?q Z o~N'"@3;33 :;Dfٹ#${bi9i]kk崋 :J2Oﴏ/t "VxY( zc6q޼Bߏ*˂+%h8NvM{;l/b:P1.EzZ3K=Ld 'Gw4k~}]">3KJ @puONCmKy'xU9/8a^RA~u^[D#V]7jw80}{u= 9e URtRӸs$%࠽D\WI *.5TCtJ&Z9gx{.VҮSE+^VQi QOjR%USlݗrKY5(/=OD-jbp@+RfA$1i`Mb/8)sn K c UT84M נgCzWJ 7k-McpբƳ1tV.̠VSAjlbUA@٭*.Cpߵθ?q+ ERVL"rԫ^G;3QDE%l*5R 9sV! ԫzdN9oWU]Em-!,*R*Kex;oylC.r%OV^eNNiwfhmvAK BM_Q?W-P(<A'>[QV$:{ sPcfӟO) !;VSYCi4٦7ED|QZЗR$UGķXaO|^+|C/ m -{kޞ9SXeJikQXAc F;D*k{p]}eF j4m?[!(K'ֆ4e9k|WNi88sڊ*ѭS0;‘~bzؠP,#n 4:lj_TTNuѸt.Ń.u"R~s7j:XW_@p juY߳Vބ5i_2b:0k'O{gBjv糆oQn{Xt%R N0m°/0fԳ簛.Gԙ2cgK i!r|$iJtK3)W65BgAqP7Kf9^qosh>6 rܳ945@nQrpօəGN'v=F _z|Øߋʒ](tFlK9C?)-3ڿvXԳ&3V/gO陿dYPCΜeL^2i;ׂ4_X> pr}"v$4.+ц οT,tAX-LbUDmX 5Ɵ {7e 5jT%GgQ, PFL:|=}\Afb$8 n|M c>X<\k!9v֙tIzta kkq!\ܖL r%A8/kȩfIBH? *,crI؎g- <1O/nup,Vqީנ>O͕lre9"\kw k/#4ul0eQ0coJ:l_7 cĽsgF2"دrXK( s0A!raq$?wA!m%LK\8%QRM B[ #JK'YNX+HJ E ̨*UsJ2F1:=UG<2RƟK"`$c/Ag+MLd[FST@)e1hlzOX\\TM縛Wmy,z^zMb8?@ wJIrht2~(pLf (ZbЏ7e鬍5 V.rZ%˫H@HP 30nXY؟X&;hG4!9:t<6 ^z2f'1Ǿ9J79q;SvD!޲&~BM8IոnӼ,‰!BӣF82%G4ВI27q$IyA|-cMjχ)2~c37D|v|Shh>q:E|G”ǜEdVg(5kˆbZdmmVf92ʔ:o,Hfo97U%sUG6Z}(ݟ3t]{-AI]nʶƱ}5Rc|xMrʡUٵu9/.cVšR/Y藱2{)46@3=G̦)΅Ā/jTa7k;e#fPm-Pt\WZ CPxz⟡ <&YM~BWUyz`KyWyjJ9t*7, -o x.q+и̃"/kٶKjʞ젖T=bA%5 |_yL2j%̤Μ@鬊?}8^k~~ T,e4 :l~%m@ LZ4QWS*[.IX1S6Rޠ="^WiXv8UD˜VlLIbYcυEyj̾{;cU8kJÄges^[Ie˶Kcx`T& ;Ek`ULce=q,ٶZteTSl@bP% B BIB˪*cR)Rk>ǚ7|k: kVL0I>"5~uU.S\iI&CH%rKUp~/_t5iAnTG ʉ{ȔD'N%oCWwCW=2 R֬Ҕ)NenNSbfoڏy- .X+Ltޭ?*yINب)F`X!ij"a YN-)H5k=&PR4BI6iQQ*tNJ| *qqxϱbrI`V#<1NB?oE2ɐoQ=m;ŸnXtDu2~ kOP.+:ֶT@2 ~'z:{y*_]÷6m`XF$e`ڷ1i~w[rj#Mo8 %M8/!آdM!cQjUu=,?EMHL3 ~ؿ~_s4ۓ+Y{z&0MǏ'(VY#5 %݉qlw!53/\O3?uճ~-A ~,>x9LRs-a3 |\OqSx4f~ m+^ Jm LF)|y?nQ%yьcMŰfō *G9UuG&Q H+MDBe-abqBV2P[늉/$`W/P~㷣ezjx<˕tFH+U R`GGYlvs%W_Vb%At7*av@f À4Z+*1R*AQWKO{mlY{u@禌 ]I,qf*{\Í6Wp (P#f> N7e^8,utcYVV&ɦ]i>T*ٟ~(Pʖlww |ϡLkWWWf/Yf/`@ku< Pϯq[?W(Pj7q70: BlX 3Wr4i;"Yޡ=e: 'p]wP ͎|ҏ5Ya<"%'썌FѝFO<z5[6$`9ȳ>m"h+:?aOo{@r[lгP}R71:ZE|L)kf֠>kN_[PE;NWءO%{臍ɱ_:ػ>ʓ\մ+eA_Ҏ:d«VMlIWWx=6Bc)ƭ~ހ+h^;0澀n%]s:Qlו5{23_DK%K/]nqL# yv՝}L:4A`,QDͲ , ;SY}>:{us=9UL0E(|2`v98gP"0! hڻxFcШsT9 jT˻\ 3nio y'Yjk{rz/GOl7Q'&&4A˙9:v競Og߈lt˭(?S|sz^Wkk. jޝۇ,aLCoa&E#M~MEǂu ֙#1nJ _G,w(- z -o6HtG!C7_ 3.ˍفؓKPs[1&Zg`xNIP@蹤QS[ϝ)U?Ϳ5ENτ$ˆ:WB!z&CxYD: ED!5"TXzm&;bf>$) óPp`—Z#@ij"Xgٰ>!-~$(EZipe7{ҖS PK&Vpz&50LOIu"ȍ,z_SFj{%dhp^438ʊI֚k|be 0kF طM RT /TTsl:PN"4^B-CQ#eڦZ*#ҌYM C.]vXޘKJEԭ ' B8;q9>+AeUڳֽVA2W_1 2NDޜʼVhjKlR_ O \x)A"lA4@wJWzS2<'/k<9ZVYia/1as#FEj3`ܨ(")H)B3S%5cl9IWU()d7aҞE/ SL^f%:c#ބqr K+l^4_g@{$Ggg֫*l=^(c4"C6Z_b3,0 G‘@8`0&tsFğ\ фj"gp;5ɞ$6Pz/lzUVKպ:UIcPzh|0JG@S+*JG;o"Vr`S\m&C%,p7fEEd(EX%^THzI7DC2g0Aé ѿvqH(Sɧ^Q\qxJ >MJ6+ֳͅ'o N:jg6ԁi-(k`][fbT c6Ux$1<Xݽ>ӎ -z#B33rf%ݶ4maAvHq:sġƶŎҜ\Yi@*6P1*"P@xN 3^Z%aQq!M̲"ܣgJvT*O=9UҨѮ:#v3ȜD2ÅajkY ekQn3$BW3E}uf Ir:Wuwxi nU4(cqWPu]Kek?mUp  C{q|޲Tk5%F%Ч/:7%a&gJBҮ{NL؞quOQZvPLݵV˺hPRtSzP/1mAAe*jӥ{m9keRδ9Uq# 5tw=efVZyS[LYej+SiMl70z KH5R4Cm;;w.E5Aa3# ㏑6ud)i=rd㗅5vc[-/QQ;UWm-ڂEFu"r=߳3#]ewiNPaRJBk}qδ ΆTNGFV+*ֲYv^Syi]mV\q5ܮC E-VOl{]AҾ.PkuDy =(Ԯƴn,9H6i@밲J);I]n&βp=n~Ӆ5/;nsQFfsPeaD[M|mZ ntXYoҲ̑&u֕5՜ V/OqԉK]2vu;sR Wk`aיYo.NS|~j &R꺲j |^{ eg*j%n60ȼUW0ST Tq=- _ºeɝUZzh*{Ob *K+i-.iqUĬWf0No:)BM<ؙ[{kI֛*;ÅAؾ¢JnpaHΉGԫ5Ջ'Ή==Fر2òWHOoy=yv4/Uʍm--ag;iU}VeYU]EΜ 7S{q$J1,;+l,No-mTp+7tÒ$ R;Y_vm&!A]r27[dJAΨoˬcTRM%fNz%i՛=Wzc}y&{0&|пN!_,)Nyk~k $h8f5hjE"k5JPJR% UT(IdRJYŶ-RE[L) JUB!ZX6ۯ3wp6\T\Us;).TgS|*KV?yؙo2Eϋ򒿚{g,[nLbΆg?}ѨiDREnYw٬P Qs@kuMve"_e_}MgܪqJ%:)ə)15i[V歯,TsP\{mRxVU匿Y&?uMCB P\m<1i" Djhwm$bZ44/η!/2 b&UJPLT XJI(VM[eXR,M* [8V}=LIʉ5]ƑG u͘-? >J nliq*0L3asXUZNf9Բ-Et_v[h[ -װux שzٙ#@YBί]9]Arl6.kfk) caw[liXT"JZNf#RF/e_BJQj\Q@@ae56GD <4[mѩ%Նv;ڕ%V&6"Ս]c.ue=xSn4NzyP)94$k1Mr6R;Ik*#ִQUmF-K%(} Ss ZnG9œ5v0nn$N^9b=5Ҩ}Yf'VKq~ +ԖߩtYr"T) +Lem,5(.)2mj5Jn1*I;&k {iU‹ aIAqaMߩҼ䵉yjI `̠YVM~-e7;͆wuT݆'RSC[!dacsvRԉeq#2(-26T%uӛ|"'ıӉAnM[ ʐ6m]J2*]k7Q;pONp+/; s01r}Lvs<* fBE LS vheksEYlx4J B!Bե_dgJYMf@of3qf6ՆnԪnW6 eस]Or(Mθ.~uH^_ӞvЊ9(RRb  6m$}%xy=k-lF|i^FuS[ggKbH?MChWL{nMeUByjfJվ;v[ XS Fܽm)Tm=95Rڻ#&,}dkSM垓jM坕EQHWV83(:f"2Fd:RSSHcXK*$ SR[_IFұ&m 4S+6 KsD+wvҥo~6f 6?G+"R:2Q=ij n5*OԊ}_])yEOUtwS`SkWx3^ +NWdrRiMyW񽚬+\q;TW.s&bQ\OZKji+a6E)Q6n4Sga{WwssWYc+y6I3ԗUt+9TnaMCYzvX^%ENEmamAjK&HSq58[ C.Ի NwmT\mdvړ٤bs2jIq= qLQa`WUbx<7v7M]=kFeq;{VvZY񲫷oe+eiJӍO tWsq.DKA= GI(ն۫p(Xg[2$ r;*Us+(ʠ4>,W^EKLztֺ ʭT< xpO_d{j-V]Nr>Tտ!u  MYڄ| "c/ҽ}ә$LsSRUv|jgЛ?YVgcx8? ]lH e]5\+m?WPnb?e]zvize(vshqo)jsn/R¶5M]a&z ^[*jm *پlv`ꍯeĜOiƑ}m1)ݝVn; h*/21vЦ'u(@ƚ9q%Ӌ^ͮ nkU1 CoU*X|djfmpM%{БL}0UL[ZF}u} &ەN|-ueyl* mӘ۩ϕSĨyjҤbT{8k3dNST]Qx)u&Ok*3M{޾S澡:sc3:=K o}&u<R|y7)B{3kΧePTӅȬZT%Jm36X\/ δĵK̯78D.2YW!r/cUBMvvVl8\0&׮YI95^$ّs]aIE.Z*mibR.UgywwZ[K1Qamy]8 JUR6#Ob_Ft>$YٵsKgL^f}wol.  jso/R]}ċJS K2 NZl"\]] 88;Tjux>JgRo*FGB ӧi7z(<>VYV+Zʹ/U5M'v:RWSdRw8Y:j')w\S^QJ㬅%Usn)6`}g$vꍶH.OHgD~DTe6 ЅY? L6)Pn48IX:9Žu5C% ucҶ!tۚj ~ yc_X~ob$Gu2]V9WSWv]yk4TH{j)4bY@c}qc[s+}abE>ki kqvme~* ge7ʜ.-ԶrMڂyШcՕ¼yi9 xw,k}62PVzEj)f߱5e:ʰ+@m/Qމ[Zrl~OYV64<([R]fKvvY3kUqc/SA6/V5' f}1yI>aߺjD.2$^i&]9HWĶ%ȁ¢5(K©9 zY~?zE>v_}eW[wS1Mcan=UU"w*+(-Yv5wm -x%% MIScxqFlTUzZI5̑9:%&Ӊe *PкңJqל.vuE5u5A9‘skljBK3qsgC:u&D 6{f=K;im*NʟҜ-*ebm4WkNF,w 7»m'07_r{+ (YKHU~ʓVf {W+0U]}UNU^OD‚o 60,R@Ap#Ul)):[Ӑ$AՇ| '"y/;KJ53vڛleOzy`RU@9 ~Ӊ5=U}q+CƉq*8yz4$n75wU`xٞ'nm=mY+45۫в|]5v sbgĝ5T԰km6dɼmrsoRցm XRԵ}EA*]֤F?qrWEiUP&q=;c]r68FX_m< hB!w_jch\E5IuTtr Mr=VUզJ[ ͯ+?a3XZYn=/oʠRnwWYMk-eF$إy}Vp''+7 %Pe];uzFf(EF6YUsYqImRmr9+: P=yby8Y}gT=֞#. c7VTl&u`TXIJAQY ̕fO&S!Ԝ+Tw܍MV=[|ה4[trFTdSeIJZ|uXڍ;_y:aɿA #<:mj,GeC}anSf B́avq:_ (\fڪs}Ϟb5۫+%&E[TwPp-+!bXD\fiˑœСȾc@77#*\J\S$=mˡ BZ[M͇RZ6`Vym,/KL!pZ.=9wtUS=E nƩz y\^ W>'Jqyy }h&goJeirF#+Esr^| SgU+5[n+N^4أZ]t[kK_Qn36TZY2ZFK Lk Vm+nr5^3OPUK}in(&m?1e}ZŖeVeͅ44JŬq(2pk&2ܹ>v3.+7TpdjĪxj-.6-ŴSiBޅȸMAMm48S* +EWZ }=FfIM.f3+o3Yi]ZYm.e)BXXļ>4'/eAF!,E2Z>?ci__ckN2dnӺm; mKt[98'O](g{H{?֚s}vSG:Jnj,I.c~sn$vX.ٸ.Kݚy7񠧙}?^ϑM_*sY 7IYUPwƅik7T)3,"UCUz)Ѹrf{Хi9~ϨMru&(]mym[.&'Db/+a|\lUR)b{耳2y, Tv|l?8ӢgW}k$/1~jT5&)LN9ٶ_CHnk}`ROy"6 ^ӝ;p8{ND(}+ 1/'^ZyeoQFs^ӾԇYXBU‚㥂"_m˭I#Uԅ&U+H_k+*r7ҩmy7 '*«۰cz΅ۛZFhUZV_Oȥ3#]5ȉA=C:I E+J\rU/#`M0$JV}6 -}8U;jwVmхi]ϧg$vzzꪚ%v,+3mƶ*vv|;irhJN9d`DEʸÝ5ʽF$ Ee%k]v<Ԫ(L@2e3nޱAwZ L}Qi9zD6vuvg=}+s9uU q8,eD]-G" HR[ξVTTUT\ :*r! fUv$mt /\U\^ki0ZpjUӶejkh*'e694ʬL 6nV9ى}Ņ=Iʻ jq{|m5tZ((ZTetؠ܅=nežNDΤԢwוּ8(_Yƕ~lZDIV`m[ 3Geʴk?l.ug?dNBw4\vj44;j *IcDnfҭKv u+Oa;elK;S0*Uۭ=;)]&IKYZfxtPq+*\+TN_O/.-:)jH鬩aik2D!Rn'!ѩe]^F¼Υ7Zb-Y'"JlH$_gVXX7?Ҽ-dzUfAC7}/{kea#Ge榍G6,nZoڲgem]WoSKR4]Wy۪ۜ)Ԝ+)7k*j&a[e-NS߰-;3VMNcЬōyA rkS&p1cj[Pm0iGV;ʖSZ[Lj* 6ԜqSȨ\O=yfj)T^UPn SZ)uai2ms0][ O29ïl ({D0Ӕk2qJ.pʚFM]2+UPEYPw*LY7xG7,ujUɋfFs"b"DL|Whl4@lЈ\ 5p++uVGis&˧<5̧g8Q1$96-ܹϡfRQ @PRjRngQ(}+^qD5x$1AZjI<z56v览ϙǂ50 ʙ$8ݨpfJnN U:>{3? XQ(8WB7%tc(]Aqε4b8V&P)Qr-g/p]5jO&V pK3jT- v,_@K n@=HaEh'r0nγf ho|F[b D.Grz'EB,v<ξqWU7MsL?yhf*iJ%rMR 15!OLǦR_&r)F/ '>AP OtYTc #tM~l6 ݀D@K%H?YI9cdRYM˭0+Q96Rd!_$xҨ t-G``BbRAd-'ڳR'\h~xn< Hđnd^HK!7`!``@,LjR] anD8#!, +C2nQٱr Í&JB$Rch4PJv'_VUF8NRP<(,5EBOk ˳*'C1 y{YhEb}voq 9١6I}47>_ i1T0 n.2@t)SSV&pE714Qtfdr Ba蜝R4Wձ~/:CUv&ux GOz4?}>s5(?fJOAS]%k1j*) bhc,d`?} <'=t6gAa߰6ۏG7Ss7 a8zgFt4#!(c)!7"ϪWR'7S(?YdّVoŀRd]^suFbZHPkDA\3r_DfV&Y?"7eBݤH't$JKqB#G0t#Ҡ3*Qk]IcS=Ģ|UҠ9z^:T 뜄M6e!1 0A ~ק?zo\MJ2wo1 ,6|T艼mަ-/]!pCX"D eBhmEҋkEe7I&4!\V<++k}wvH7_mnwcj%H ژ_UU }SgZwgeFEK_zWsF}\/f,_k U0.j-K( ]ɥLÉK/#lnntA7?߸Ht)vPNeLQ[*ТcB'aufĕ&oV?ԜąQv]w6eWxթՑijaal16QS].G+Jݚ{hUwib} K]*p(,d ^_4֜`Mei&Zp(ѧE=S]T.4ڱ;")Ұ]n CWNܸ:]ܨfpΞFDk~%{Q;p.Wt;L;'u3F"/iKP鰲UL4~k-젺ڷ-4$.4ݗwJҺq7vgATu]`QjAxyTYN΁ :.1.FTOk,Od5US/;J5'6RNƪ®ʺ;q6S7kN}-Jtϣw~o}IB{DȧՕ _'!lZQ짽 UfC["}ŲąPv$Mn_u8ՅW].5E'LrsO{Zh^}E ZVTj͵ [[i̮{aW_4F,Q1zTkw.'ᚗڒkk*ͯӁam1E[]]9˥6XTVC㲶)M]o0WX9ajs:Q*7B{SrǙVsqM#aSYak4+|y5XpθF\Iiݶ/ƬҬTL< 6~+9uu/\̂b WM'%nfmCWJۙԚœERM6QB:g*8JTQ;el iaQ"}57^ٜڞةNԊNa$kezˬ.&K u5^XGƻ /PU6deawywKN9j&)bu[5hϝkpkvr.//K>Fm,i_!l}`2EvL(]~Os+:S]uScZ1u󏓥JaeXQQ9JETaKs~Iچ ;vx<-.h) N\B29W$ :hQ?k'Um&=uA J/B6gH Lli+is<,T갳Tg[]k%զhOEKRjW-seeަ=fe7 z7i\s/)o\Z{>N>k+^˸cWq1{n"Lާև+u'A2hSM @[j)T d&iDZ[r*MrInԫ}>.+~&ϴY~GM>ӹNΪW^ 5&[˩ϝ԰dyYUԛ-\+mYM[XXqGeOz j]^G#k36 yථ4ƥ4+d-~[o/谤*>q3D͟mfGK6GZoSkul`1a> Xǽ9 b :|WĽ^vGbPgT]b5>9uM.E99'"v+4,*; 7+ S^ir[ĉW6 F$u,[]OpsnyWKiQfNFZ/m?M@nWS؅s=UqJx9Wz˝n^*Kzv K(YM"SAbt=+*FUizEi+)0._fcϲXmI?6Z9\B]vیNTUNWkZ_RWgTZ]ZOjQ{J YZVq)=t=+(TbPOD&Qyy5*D$-veFX0MU^۸SߍĤZF'ImTO|-JU̗6R-=J0ʽ&E= Ll[fEɨ)kn**ŦG"jsr-uɫ')25ZN+N͘2']n&^.aQʪƺ9uiǕU*~LڞiOPGĢQ(9[U n9[iTV3?w Utۊw $s$fzminS ҳmS; aV:v{y%8 U2;u_RE&պ勤_bB OlÝIZ#XZ$zG#-*˪I4_ JU g:˝3k.Ff4sFIB]xhZy"/іzmatDP;1sęOa\Åfnvw{rb_N-ѾPoYd:,;]}QTʛaQ 6ĶPJЁУ3,g[*+-0B(!Qkձ^=]t/L9MæfD$ 1iW2FzYim$km[ '^lӫ'?u]a+&7SNn*$XQo6\;nQ훭feȢw9'WMwXꓗ')?s)^ʫ:9[Tr'rքPl@VձtmJ7ʋI[LMKI VϵЧ Ok5m[SΟee wWYAuAAjJ{Zg#̷5-7_uYy1(U#EN)}&uUXeT_PD6v-YӒ6TS ɪU&Ҕ[Չ]DδQuRڍ^dw2xefjD|JLYq-$ K %='GwfҺE=#;Kgk3C]Y]Y|k <)[hrr;UnX,E+^UZŅ' xrcwY>6}UUO M[C{u=-K**?֞é'UFw#vYQ͉;i9}oŝ^eAA҆sfM=IY= Ӱԍ u[kmVvTO#^a%Fy@YS̤aO6V^VzT5ԃLX3yj+y𲨮-_{.C}SeeNYF3㑲z[IY&LJ X*m\=u~i눑X{ Ugc2a%$3ŧ.v{WL.?ruK莊 "k0-./pzҺk&ͬ얜jK?`r{Ep7&ZW=K0ANYUSv4at9u$̎Cie kyF37YIn :.inH֯M:mЉqeV}yR]қ9yk5E6-j\WAlZ?#++2((E7q@҉eT^B`ĤMTr$ _eezmu2<.nV`MNȁF Z}%y:TP'8uºAyx\ۯT䢲ҕVvl%]:讹kV]QVJ%Ay̍ܡ#eZzLL [UTEF .o}T; ζ7‰ ҁ  ɨ&Ǭ+uέaB%- \M--lU-27-8K(,;%eEKզVhkTDll-KaYmR~MļX~YjMiRҦkm]DYiuiY}"mPUuV+BHҹ^U9 kn67eA ZH5sfQl,)9gC[[Ji^N nC=¾\GAT5(- iL(-}lJAJSidK_4‚5R5.7"DEEԬ@oBFĪ_q"Ӻ8Tʔ6*VyP>5U%r&Db\-z?Ɗ); -L /Brq~mT}y*&o*KksPA*6wjhu7Gp1-k-s#u`_b^k&:sup*Lcoҭ6F4/eiEݴ,/evgQ^:3Y=:]\jUUؖ{mUf%riAv״4 X) i_GjE)Vݶˁu9y\YáXA!" C 9\>ld_砾/qi\ZoӣXhϵ/ ٮÁ֑ rw4WQv4SwZn%7Kҕ,ZlY[Mo"yKˉw~: zrV ц}ZޏGc*CV̝(4Ĭ>յv *rXth\Uk絴.B&l -:"qܞ’mGXdefJ[J/GiA]}{W6!iͥ*Z/^kUVUd<>)c:ә%Ytˏε2T:ʄ2t s­ n,qRw^Vϙ;ն^ZFTYu+`yԝO*;wه,+.,Փ`zn3F/щDΦSmB鉞pVQ_[Rm0? E%ۋ-̍=ŘE{BGNM bvq0@up.,68 ZRUVxgTY.kBjsI)7k>nhpqlqXeх<f+:5&{KaEԽ_St&Q"ݽY)!^,"e`oaWEm}YQQ9?"^t-d>t[/۫uyǁq]{]*7yR4(P؅E).6ymF歠@qUyy?% ,Wn')/J>t{W"Ii%a9=Zds7.p /V:n}xR9xecMYok2~g׻:mfm-;)F㰵ef:ekcOƗ$.Sf{ ڜ"*Nw&T^jxh;nW -d6"pWf$8 $,-fDW+E;}z]<[(Pm9~* U53Xvun)#SRƞd"ei#i-ElTmK,_jK5$W㺅E( T-2(ĚΨ26YmxΓKooϛ+Hl=&Ye(Ĵ7uYSMuF^r{Z˴^&ÉJ)LW(Rnp&Hrx+2K7L䠬k8U4cJSѱ [FQ/ߧAg, .ti͖u),Gi{)( ak,'gJcY~$]ΝQ!bZнt9Wq,<7_¹15mp+k..;ԔfS_OxV2uMve]lĺQPÚ#-mdN5N#ia,L]b-z٦paƿШ}];f=dl;l;~E&&˭*g#{~eUfetmW_c5tF4Jn&r=V)A^}g(eDZձ2[ x vY:>i\r7jJˋ_K9`hibl.8K+W;]+Sr*KmBWEt-(y|3SP(뭕556p'gu6ynʩ{I:;g;FsJK-Cne5Pq:ñ/),Nr} 58ZBX;嫊BJPp #lk뚣S`ֵ ʾ{ ǝ;,ՠ*l-sy47ȿzl+JWl6X]QM0yYh 9ֹ.!a<٫4QI#-)1,e=VaKV,)*m_j4x"aQ2.&iU=3BܥAt"Onb|OYqrFkp7og]s0ȭ8**]nTM)KȖ#kgC$P?IzD~t$(U \^Yk:K)=WʆuȂ$W\i\<>UsƧYK2um%?{W]HЧw+;2k-OkY= mTue_TOm&[Y OQۙmR] ijm܎6M}7b٣̕e֮iӲ/CSaiEi8N VqȬo 6= oaU#e4OXH֣JE٫/V\䚰ʕRs8fM+u'iiΫmk\O=?>c]?"}ĕm.ܙE ƪ_H#ebڕ\)Qc^l%Z݉cԢPkT\TY]RJNfv-6f^IkY]#UwّQr]UBe.Һ}*k.eE:s!@Esԉ{n|q¾mS7ej0AqПmʜ^YcgXЬҚaQ"ʃ,J6^tK ʨXeJS ^]+ &28/ř:򓋥ȕ1kZV7y{aG<N5(,5s/<6+*To)_*D֢]MOyUUwa^x'7mNJmOхer1jiYd^HM"RL2A]+Ij4z7uk+m*q?Eͺ[akcm4/'V$+()TP6̌2' H)7*)";Q(ԤCmF YĤo,YVXs$qs%tI 9+17u P{ᲟqR$Cr8[-,"u\9mI뻫3>W' a [@@eg nV`EWhp3],PNPCc! jVFY$\4a5_5M&#aA¼uIyI¦G5EN/#V:Im6؜U[+oi96t併FeRe u4WQ.<-;vjɏ^}MņKTCVlp)1EldhUq6V;{/*j%=4OZr9z[+LmpA"lS7^v5N 4* (U5NZkTMPQT^@.Vl6Tg.-^/0-\b櫝(f=rrRNc@C@BS@?DDꝚ~>hĤ *TXڵh鮣Broj[˫~J1:(ߣ]5ususNc@Քj:%+ҫt^ZSmI+TeZ[?U%: elGyuJꃉIY{&^‚WMk~yZf\ҧmEKr2aiopUibu^*C>W`rRzQ(9K:-L@!9g$ 9m\i27k<}ֹo&9#ȍ?iiFqgGt 54-iAjEHq?w㼓 eG][mH⻙ ʥvuۍQ6XS*CʩԿ}|ߗ]qZ-ZQؚѶMUB8 ѽ] ņe2W z%į5euqj$MņqmAAAQe*$n)>OYUIXS'*z#{V̨߬9xV}5}#mkBwU޲MeYHc0pr"^hMTHЁ"R*rrGINo'فfj lMu'U|oFv.##6[ )l¬FzFKh/+WMg!x*U΢: KIQH:b2GwLljОYJo#Ue5].BZ=5K}a ִ#gU mզ"JSMSTSu.g̉yYB&T% :k~j[+f ՒbJ;5Цqib uJV"ntSOrlY_UseU5IP`zj5&Y\XY3(C=54''3'MM ՔIꊮK )<(UIc~fln7^UХ}*x6,+!p'+8[+ w/#]ym6ҭ2!BNbA3M>+5=̠VyT;snkZ0.xS'-yk>; &H̼Cl(_B> nEe[5u-DƻcEOՃȕU]MTgjN}EQA} JEUVa jƲ&$;j͕g:mf*R{,q0Eŕibl0ZQJx_JՆ{+5=f*8/"q0 yRANTH7[ʺiyi p@j9(L(46>jǮ>Gi:]jlP, gʊ{T˟y-Y `iq2̡>GI^h]NڥK_ٕ͵vk|uzf.N3DI?;g}7,]6O2sb: IUB…E#YYd%{+HXb T&WQfp>~eB)e-,lgcUyRj:ӑQ~4&'=ּ*2]^j4.5nJ'ObԨܗ$ݦ/ʰ쩺3(6XjyZSw؜eab n4S7Y\i^ LHR6Y+03JN]iAy۶AƦ9UݷRWm^\12X\y܏箼r}[L+,\7Y/l%pn6nPm(3[(QnѕHE3\v4NG,+9\*b&&A]ކeuG#u}7n㩒f4+zLʫ*s($S@Җ"|8rMD]5R?Qaa+WT 94tҕmЂYW[Fl1p1"^h]aF趫ȶ(2*ˋif-t3TU/8Lv-*znhԆ kh԰°R#Se 5!T1 &+ux~ҏzRgpɆc%F) T&ϥ> TiO>rlV $L NYyqbUt''//5P[ے-* %xgřIu*^[WU /\F̠b4Yٮ/1yu5k^VMoMWk^﬩;W*,P6x43 Sw۷Wnz-*vsЁ\UbfK}%U̬gסOeŹfaE yp4S'xlkާ6~ϵ֟Yv3( 4" 8Im6fkl .NoC]{C;{Wn:`;L4#@a*TӓY|ľ'#yYma=Lqv1< )l'bkeQԸ^PjOms;l:֞G}5“i;aCkxJϗqR?H9`D+f] ˬt"߰RNc[J}q=3)q;P#DĜ_oOv veVCi7v2Eh;I)S՝/SL g寬ͰšYkv\S[JHI;mT?y=xT%SQSeN3^VGaߙ cgNЬ'}ԉWOƶEc$=lSPYJt x]hZN>DkUso59Lid`aqEEAM.y9Yݫ=kKL )Jg7XӷXq8/K݌Jg}9AWxut*z]_\qyi'i{? }֠39GSN2s;yR`ʞTTFPNnmSW&NhxUShW=M+M"SB2_E)זyF߿TXR{ak#?Bm+3찛aĮGס$kLi,;MQ:ҵ9Wnh4:s=,g}q2eUevmFWqWTn;JL7hgNefr¸Ņffl?UVҤ2VVӝ:-v9r"V# Ul OyaA YwZ*O]qu7>6|)^Fi!HYjy;h :46>kj-յU26li*ܘi])/{eD e +S]̨N~e5=gCBqۉ͆qa]sNHk7.nxU&Gbx1< i>k2Do:~n3[Xf"k$n*E2jv'Sq+ F۹;iz+|SliktZnvD󍼰԰7Y0L v>FԉⶱʍIA&lj,\7_DXiH:XC9AmYnepPWv25i`ii}ձ|6ے6^.5.⎳a$h^҉]Ucř7*vU 3o,UxOfƴ _IY1&O}F7ewVr*_v+1V+i-251tVq{-h+Ղ+,-7ZOMp'%K"ϲʍГCݚQr(MF]ĝ m9,[J&0ޭ+Vr:VᛩJEe.MWH)# *.-3UDs-y9 ?}/ȾQ^GдÞӇm_vCu V\ҸR3E; 7#; _C*z|6J [,j+8[vmkZnpT/Z:jJhK|Vq$:m1z]24搱'}q Зfh8\M˖t֗( o=|x-Yp,2gȴ7v#J=#t߯2ںz-V9T$ܦ%MYnX٢TͿGIz+?ieqԉO]6a|]&U%5扎Y{e9,:W3ٙmŰ&2Uj‰ Jsfl(8*rj| t 4訣Oo{LK[ Oem^P; @ +R 9|s 8j>390O+Yw%^yl)'@߻8RtRSXqT7^56n-ԭ*덧{ l h])z n&0n޺7ֶ'KhMIh'a^ ^r28_zp-ǴUIi5u ^mNRwrnMT_2tIvAmkK 8ޅtv֕/  :fn0:wcd`$U.,(V-+i&T#Yoߞ箚C5 N}vڪ^kXߑY%t֩f ǁY<)p|:QGzg 3 -nmxZ65Wv"x+i+O:Qb_xn;/]mqw*[sk"Zs-++[i5F^B<$seKLtiCSÏB'eaG5U$>DgyS~ \R*Ϲ.wqS ֥u%} 太c.:n$.J um*glVhX<4n#ZjɩlrSƴN%)-7m19\kZp,^O^CgR|VfSCC5̈́ܽմɩ4$RĠ/o%̉Y, αMwԍ8L R),RCƅ)4,0è;ϑ-g+e5- KvN%nA2 )$MDzVL I258Νm"rWl?mubE/aUMw$k+8 ?2Nglu*+72'TQШRYaʨ/5aB~{LSEGKIAv>w&vYh{.W`FJ"O@1!YN\I_R^)T#znkhE֧7u=DQ6UkOoN Н(ǟ3Z3CPPKB[*GԇRDOq%7w2= v9z9&mN5+Y5 Z 0Ae=9$_[RO@O 5 ݖZ ./Q̴7xn27ޅ;))?U%tIjV6й]WUi]Oq/MYi*=n )ðûZp=Ueɺ^t5*F|>8w D>wau; 魸BIz/pV|N~qb]yAN +KyE#3vuzYv[:ç=yy] @=W(?RL&˜YR azTY&Œ[ެ k f$/zK m;qN"UnU:;a,I\+Ypz5ݥWkJrA=~q+fw}ZaYTbq-^"'{ nYJfj6¢WYqqQ.UWަO}lv%-&7_6&5ua<;z>msh^[p8m m/}^7n6XI)Bܲ;00*&mߩ|?wԙy}eb~<6'#[|TِPY ˩P:_yͳ[(.y2980KUdMUȃ=wq5Yar~jhX'ڼ--HniҶ[M{\i_d+j\Ưfg U&&D;R⺳C;•ooQer{OޥH3(m9'fw&Q%f'"6G~fe o+p> +qZc`[EJ@p&agHZA}UƶZ}ǙmO1>%3U*VdaF)oCliʩS]Bs'8>?aEiIoxKĭwҊf~4W j#}?mhi"e wy'[d=I]}IQaN^{QZ~/v=JM;Gmagp=Dn#C3*4܏sWߢ e*KN4iX|W=dkH{[y*Ȕk5`Fsѱ WԫAT'^HQ\!@\_߷j0l"oqPu׮s*/vj;X~=imhW?R6լQO &TCcaJ&=wW>e婓-ilV{ƲjFDeBᅪw~yda_S`/IZQKœ+_6ګڢf\WeԞUEE.+4ӆ;VuSSQ?o'5TvZQ&FZjhk:JNkdX~_M\V?@h~_ŕJq |1U! 0rWaۤMeNGqe~ڒ m9UZϙQwkY^X,3}ok?#\Q;]ƙׅܹThm~Vf%7N2T^&og9n_|kF UX"akB¹=y3eWQ{,|WWl 'yV/8q6TfqJ<鯂mMUaE#8ĝЉ"ն :4jmI*0Sv>Phڱ6"-mW\~(+Vk\dF!:j4ĂdϪARtWgu nTQ,_pN;*jThռ ңqwuYbUU$5hS[+ ֲTmN:A+I'-6#SQYPgrUF?6nMt KZnu(&jʃbHH۩UJ*LТ6n'vwj) R>fD7-)\mƺQT+ҩBk^cz8t1)PSRiT_;r+}5u^e !a^}{Y]64tujWbq2; ;j*/ 2Oy<;= ee:q%&4M[58[8< l I5כ(Mv?Z-Ris0*VR[r(0lGvR'aSSXOhX\Y٨feԊ%&-VKGɅ]ζno.L]'iszJ5X˹suVs7\ԮugUwehΈ,%^as"F¦[`n(TYkyןV' 5uR:)&&٘q"Qi]yVQ^m; W}) (kq+ ۨohFI.UV&E)9/"{zveyjXlYJAuqe5cy &ɕMӃuIW/akLyd)ʮVfv$Յ-̳`k>uNTٞ{+*FN]YOq±0V,X׳NuYWKPq%Xzv jrOAyCii̬Ew#5ޅLTlIͥ~W:Uȷ۩+L+6xSug]td܋<ڐ,<[B4j8`y]6<} ]JqԸp^[b]ncF{כ5*6uf/q+ltH\ʣYIQuI4 -=MYb_0&n*HUa-7HV0J?k纙e3yV+knoWұ9,XiCi~{fCdȅ9+S>%6_IYC-0BF a[QeT ۯ=$7a]8fBIf^Å4PS.}Ku&9i'jE-g;3NL5weTw=~,S/G FKNxZj4_uStbdM<,3MPE-EE$愯Q'.B/sً*RqRjRо9}8S0qh C̼gmw\Y't'=z{N4(VIxХڸ*_}^๢e='i؅iWuMTjGk0gOj+46l-giCqff7J/0/!ǻ†a&ufe^mUaem[JÊuMQyʲlFe-CieuI*`lݾjq;Uy$ϰΚ֎Dmg'?&#CiB,xZݠjʛh5,gYZ\Un(cBeCAk%JŪSYYs[+-,K L7(Zy?SD+,Mkm 5wi{=JʍwJqq5U7 HTU`gcHꞕΪ^ԤOt|Z2٢۫5( M"oOY@m6_Ɖ.dn֨9q*_B+uEi]qcWs-+Q]:mTHzfV5_v65, 2ժg"ƓKSRڤݧ r*~syB ggÃm]V\:{0mxכ,jmjNXx2JNﻝN)X2[bŲG \]q.BamZօ.#Wi-ɗ6XcuRr`\q#Z~o ϻw6.-:-(2j='wt:ڻ2&e-3ڏj~2*u'yJ5846斅tkؓUP]NЮ_=6Ln[KL m2Uiy'XhSn-?MQXv1Ѽƒ5qvVkfkVwM(WXp+J[TMr ܠr "eFD.,B">?jNǯVڛNرv'qO9|Mz JZQD٬4 PX;Mu'헁Gm[{xXh.Rx#/e&\u5xUV rX@ǮWk4(|^4s0*27& ,OYQĺqEub*{m+ҭ\P5a]{8^Ņo>Ki~OS[nF4 G ~{IQ'F3VaIV_mA#G7 JJ J oՑO" 3~K3*$-sDoht& ZgƯimQXQaMMN~oam*ηQ쬺?/aB_.eGq4l{} {{ N^ 4ՑQҫvp27[ӗʼH&IR.I-7I d}L ]3*U ]Qk+9(FҶs;񱰮neeuT6Bj:U46UV{KaO{6nu]Ȝ,S=uia[\2~mI~D.!E3my mBMgUУMF`SWTZʮ}m `]uAmn60gL,蓭52!]dN U7~Ywn͇}fŽoUpԅ]ua¥+!TcBReڤGYʦ;i:FҙU1B""B"" !Vp2 bYFjL˲|mo ]X+,X=5 Ƶ7CBjCR{)(6"F2E6Hyێ5NvwFnq.c@ޙss6)%iCZm=5e9jJhT9ݜ޷Ed%-Gzk&Te4-bJ].湶aW:;Q5]+e,ueS5͵e=6us&?U9Mt,+>T¥Ҿ\qXh:[t.Rӕ}__37SqMq U#_땕gԙGF[CQz#d[kt$gh/нdS-mȤz\N4ksnr{YM{uU5(TכﴧAY|3{933߹"V(Ӡ~Һ3*r2'¢N£qLJAuzg5meǙk_mH뽤NWKk͘DlQʉ M]k(%[/eScESRM*KcS\Y༼I/溵u*_uqcjof.+=Ǹʯ]S:œB4l -YZ/Ī{E5-ȦJW{Vu9~FİU/Vb43E""ybPD6V36aƿȧBS)&xͿQj#^\KܳoN;joVbB޹*n-5.&@~'b=jV cnK™22Cah"'+iB)K+>j ,*OevWOMM@wܢ?·| IXww]l̪3.ESGaqs C3:2{=q]x;3>tL UqI*&入 -E"}RWr%_Bn[K[tkVo&zr8^tՖ5dk*7%JT*r"zOY܈tk:[[ YY"f}\:1UQi ʯ;}s#;qBl&\s23M@ mjhۮy#2UZ%+V첸VdJ}GZ!Ejsք9ښZy6t gRg]u7rj]t015*s554ObkWrT(Zv:kJ}FVXYUp~oSK*̉;TY81bUm՘qq"s0V G+8K55% U; ggtE4.?M*M[q)ʺVi]^Wи)7-kčJ턮|dZK.NAy']ZSXIJUiMVFܝykL`UhՐPiRYBN{(R ;]WV ZW\9přיrF¯ eU56rzznzaKM,VVmV[@m76\H>;jSj2Kuqspjbf{Y wge{V{T[4՜^²-ʝJMF}fͭf7];Ыv=\7Uc@R"$I[-fm iMnkNjv[G*06ݴTR ~w/rD~VsNT\̃yjg"d h i6ùB\֭"R$RbMIh-^/26U5eeiQu,~:mLS=wФبryܬ%Xfт+:e,P*%*KBm2$K6{q6q-fgР5*Ggı|p;\ᲁkY}]M6NmHt*mgu"ssW-:&Efתr䉑iώ\ZPRXDDNKH-zKOf?޷ wVK𦭱9GeKXjؔJOPmNfn,yˣQX4cf#c%[ MڠŸ% /Z~#SYBlC3LfQUzכ6õM ׸ڂV},FGvk:ә2^ fpBUambDpOy[O FT*Q[J3•ռ%_\U ~=>*6{뫱ik˺PE *κ:}td~eֲs®1`[vR3)X]Iyl>WѕkS,VN'žOmƁBDJ5uUORRk+XUFþDb O#UK9{($Php;k -del;o(`OaDFFEkw^bm4H[ZFƚYgRP" *LJ([aQa#kn3[NZithi{Ƭ`zNԧmWK]RTAIr9(ؑ&D}Uݶ#Kf\l'-_Fjf.i[E\*5UT N1#f̉?QNdʃf`鎝/+\E3: -"~*)|@6:*z:U>j&D\m6/+b_c*H%wiZ±}fv[KY_gޯZFW]J*索Ԝ(+1*қ'{tKH!gL1k HC wx2VcB k3$ I}f₪sBȅI$ȳDY=YqqʁF)š4U ]ʉ8kk#^i&a}Ωa$&KnmKSlh&\J*.aam@߆w͑5́īКOlZ"[XE>$wWS\s֥YayyV+]>ȅ\ blVŚu1ɃېQ8hrp2Q6J,1OP<|8sL ZɼqCX˹0y+& tW8&C2e6w2JvЁP *;>`.2/Izwx\|+xz's2J/Ⱥ9v6tYDRm9 7!Auу3q]Yf,y-0*e9gV3AqJG˾LfUD|*djB(f ,'Ngdw#-ABEa`~zWG9l}GP7#Gd]᫐%J+g8a.?`|p(G[@ks$Q9Øvq!/CoDyAlrGNٯT!4>]܂t2FJ'j3 kЍb8z_׷8wW ..&a|YK]*ׁFAWx/>OPrsjv%\_J3nOs0jMK|^o]K]G]b>,(Q_n͛P֝WYXR'ϭ3 M~.z *krpE'p]ٞNvXg Sc +OuydʛR]qp$.50=o6rSTzKbfVԖfR;xw‰VW4j;D&5h|i*HF=Nv5jUN~R`[}IEx9x˴,%(1>4Sv=0-͈g_9tKSz& 趩<5G~*rݱ څ=Shz֗Z5Q]|b9KR\Ui\T2/1N?EOvt7*-MuӅνőJ)㎭2jȄ۟"ܹlsܱ;K5d-Nԉ#jG|3Q?_iTk?J=WUӕ(D\g2a3|cb5[ܺ/|*_X:Z !#C6M;o[š яf؍ַ=KCA;/, hy㞜KŸyWW*,KHGBA>yHSG?ɖx^d;qX4i8tVQvm]`{ԙ<^^A5Vvocݨ7OB)X_2g  0xiSrcކ+nm1gJm& nDQXQp/-eo;fa.ҰĬʼ5nW4sx):h~(ͨ{@b KIf?9  1r WT):k 蛥)j#/^Jڑ{/Ѹz*h!츮r,̴jy^bgȶI7 ;Իw)?eRwҞЊR_P9hKV9* ?rSSl৫ZoS/I5[ZjLc7wY{j5zLؽKw]y џ.lhuR__n|)Fef>Iœ8*CF= vH7%">mm4j_q*sr&;YsOp%_ 1.ua}){T 崦6Kg4 ;B+, zCo1#@DC0卮tb0EEF6c}c"|ĦӧOtRQ $VEh R@$آ`1jF u"Y"e qƞҰAfyotdi SqC'\\gs߮UQCkq]D&/<̎*Fx~rZ7G7v8Gh%%nZ@.%jUH f "H{|_j"Jniiu0MgEz=ăγ .دrw %J3NKz 1fGK'jC}L7~o;Zq=wa)ooWNaY3;J,Ԍ'R/`KLngtv]/*5پ!9̖=z,AC)I'@h1F,Hg#u3{'ÎPp7 - xd+g`ZSx|FAxf -g:Ʌ8F$uUbRGjPOՙjK|6ꭼulRˈbQQ; Aaw\6XœU,ez<=5埰Z[.55&%󒗂 q`)|mַ}R4:G1W:EeՂ=7VDپG8<X3aN]V䤏fJRqS(DOs $]Ph*Vޖ(<=,o)E\]CsA g$Ǡ?!:#x 4$Z`YY29zC!.NG5~(sg_mq`;B]ȠYN(ްfpL;:eF7n0{K\ڰA9՚fqkOd2_( oyv;l#Ӈo]2Ԍ%Mk!HB"K% ҍRu/:xd( %5R¹t31RCc`I(!9H?V ~pC#t]V'EAKrs6aP"*-y5¸S?A3_k$?7&! C4qn:ђ9xk:?]{ /m㱏K@D; x[7WUIYvRtt~ˢ%}(:&)Kh? e]][ ׋5SjX]M;YE $\\*-bIyG+>%|~KH"K**t(RzƧjz#Msz~q-ݩ0Jj2Ho) FB ?Rg BR<"-B lҳh7Eȵ6?hq|I\ƁDATRp~ȵ(X[vB1~dny+d7_-iЫŮE,?*0(;`}S,ϜD,5cyqZִWXt +x/!*E`>1>vtwlya2clmiTTP4$Flѽ% Jb.Ú7!h)1F<ǒ1!շH ? B`w8n9GƶW ϋDb;'(0ٖ]] LؔgyQ ).qGh $~_H`|(q ѢZaeFzpL;݇ r/Hv9j ΛUTpyh/;[wi?i_9 "Jq޵5z ,"!4tӎ!jz9%۪SXocqC[إ{B͍înLX¦m:9,F>d /w)JYWb,0n)= $pNX.J8*!,) 7@mI޻}`N]Pzx:Pj@+ͣ.rW12N!"U>)Qgq_qwP INȹnHj%![ql.׈?OIgs ɟ`w0#S37='"lp_dEϖ%ZbNhD%_= "~-?ѭ4  I'¾+0M1hNX)AE۠PKފ%jhlhy`be-GPmO~6խ8G*'Ĩ haOkkA -S/hfSs/0eދVuQcى{ L $iPS'џr_d;TeNmrhhp[g>!8 R^aAE #vMrs ds':$*PTq:}kk`~OHxF§µg 0yZ!/kBW'Nd`{-(P 2Lxt}НqF?mL.(;N=(GsWT-:Jo\9ϑc-SkZIO28 L®LA[3BcRHTTgYn.6 꼊~U0/tq|l{8!-n:&wZ.Ey2p} J7*Q.rEiL!g9*-g#RϝNfZM;OU$;Kk;i=je&EFlj!ebX%W[2لnQ54&17M.".͝@X^Z9|`Y}.jUX9꿷1+cXeka;\}~/u\7 7)n Nڵi|D|,TK8<*>0(ŏ,ζ4 tș-y?cf3~g{B_f Z޵˹SDnX/GdD)Q=|oʉG;%?{⮣ZAqusf%:ue+JhVtD(ѨQFc06zRsuU#!`:V\WJZ%evtD}l~w&4nl0%-ύX4n&‚EJ6jt>2*"4z |5.r5~7 afc5_PY/96P_ _tIWRvIƛM@ZjrvV$D-2wYHg{^a3{ y-q_ jUN_ EWBR$2>w Vhlt,S2xr3#6#>Ԩ/`t]T@wD SǸ0EApPt3B&&%.$yn}!Σ%KUf1BL\8EY`l "9 Ǜ% DHJJ~05FihG‰u2Ǔ>=PG9:ikP/"Z%/Q x~VZ"538B=na U8qx䘉Gf=Aqk\+FJ'?W!v=X$ViģSl^,_rc]p)[~>Y<۶5yx'CXj"-fxiGf/ .A~+Kou,3 %&EGNv#vY{>m'B#+dB$')ƜpT (kΎejÚOqކTFd1=/qE$x^`zAw/IeS^;[I}PiHKEH[ -,K_)P=XkThs. Z΄>֒v4']ͮTxv7_OL Ty+.Dj4]t>y$'t't B$W0}*J''D] |94T M3jCđBd=/06s~/Y~ S|J7C. 3#AzkB!ֶuGѐ5qrPf!YcdBeJv=I.Fþ)C[ҘϦom)^h.wvp%Jឪ&t&+o;!wdU$Z=Ae %OUvth->4u.o@ RARM/'hiqR :!9+% sS0'kRם?)gj-N]仵Ɠ֜\p+mM1 ̂]+kJqM?v;(}Y",A%b ئ3~'e-lBOTH=<{:i4DpDWa0uh@5 9ȘZhb-ci{<--UYssah%AOàL$YRa=kfWԒr}cp*6\T,7`zXX3. kDmX(zaH<#E.i@xS>g$#,[ tJO`r,ۄR#>ҠُU$Ւui*Hڛ<8c~Wd{$tOE̵a!&}:3.@>=g{qMǰKhiϷyӚh'L%:M Y=rӚ?]h&M:g*iĻD(i eo_3$!H(K=xkZ%3I1 a$|]ȷ*Dz&/Kr[-EU:tJ6wVSds\T9hYqj R*Z;:*KY:.׵W`>^mݱi\h%]UO%Ep*̷:ZQg/BRRZyk˜VŖfYg>JBUa;EUgr^v穏Ҷ++.İnwgW%p_S/qSSj'm0}e8cOMИE}A[ZՓHY6s_S*ok.ywu3 AA_'o}ZdΫb@_饵YkL6 ;iՖ&+]ZKclsKQ/"c74GCp<[ba"nhךN`OLJLU oW+A$_G9`CKԂ6Nl΋+ 1֓ܯND̼>SOW . ETUwe3 ~.s$N)#gv duà ]+2_.eK&(]]jGB䜽—Xt#q0GX""-*GOw{E")/i[w˼sf+g6 rZIu3KWO<#)z29D}3H=Xq(sBbS(kŎŜ<_ZoC\.)L)Ԇ( ,9ZT jљyґW“~< H2}ILYg@&EbV’#5/AiQS1⿳⯌PQ=/Hɱ]8s^F:)k/lJjKE> !-o+^i" YwgxnQurzN9:>f5/.!styuw3ix |j)vϻYWЄ޶Z ! N}Bzru~ծ~lZHH*Qv&&w/eKi]-FR֨iZR~l u]S,F1mڬc ^d+3T\ǑiD,zFsɢ)=_׈峉S ^qިmSD'Ns׆-L8x/.**[XviH[;k楉x3 :ƻ:QR ¢V\ۢ{SmKh^]Gm ,C3b4fo+A48 A-aު5?wE6&=orpr?{u'0{ߘ0AL_ɀM7}<P&N LHKcH JGrbtczˑSV_zkTħ wnw;}D_ItUq a5Ol6>14ד'\QztF~S3A/ AJ}vȿR"UF1x|hxJVVDY{447ltSi|%2ҋoO7ӊ`SO nʉ}BXmk}J ':53k[ޤ@Fã]Ihjm,N< u{UBP 9Dy/X6Mgs]&r>l*7A!1M7+o ݹzle~HI.ѫ+JC[ !]\Ik0w< y!SKȁӴ+:[&y|aTA5|0"%_[rt:جؖ޵dI5z\F'9-sM-U#&GMx9BuD4|?Iq!%t~@̈́MW1ٱ;gw4QGFPa:? <=ՃN(q)&XyKyB_RWE$=/Y5I*lsXPKx'ϝ缈}#gqy ЉR(]Tڸ%uɈiAz6qQUf vX]̘Vc1*߼ V3{E933_эo֭l{NLlϩ/*z ͕~/%W⨌`Z$ߡ3U!ػJ#O45 OJpixYfV-&Ӎvs M"37+s2P&I?!)ۀ(EE2ɴ-&Ltz(H$PVE$Ӧ@~BӾ Z9R۩TݵL;6=aՙRZdc24hBj̵/l.%.%kRo[:y^[vFH=eBzkH0Tr?E2٩WS*Z1uP6À`gTC%5I&}>]˙yN#ض;V誸gs*Q|@J5G7]kn5i!hlÚtXl(-&E-)_ mTN.fiC3D%Cv6Rlr$,:"h>pЏGTi+Q-Y}t#/o=فU ~E/* x޼d"jK x:sM%Žϸh8iZޓS__vKȱ0*ONڀ) VqPoHJQd<:Q*rĒzRF=#R? .ם5^ךԪ(@M:-ݽQ-AF! VujYS.,%"ܓ/R`4-O*iXD"%^UVvm}/u.< ssmZZ `>ےY(;Ν:ʢ!+*Ϊ,%~Tg0!0H<={7-.J S?#O' d\D =Tq64$ϫ! $Ų>H"fA辘 ]V꽢r>w \w)QiPʯ^c3”ܐTefi&[j/N7!*)Phf=# c+:=ŭg:N< 8q׶>դ;D)$^ ,=ąKR-'5Q4lQ@+-dR&? tnlHF4*?h?_>8k=Щ(6ߩ#s.وPuRʛk1&RGR׺"}?ÎbVMERLm#!x )}W[˪beo&-V%bOOmGuשQ'F9/'tF!UCB⴯=}߿ʵBGxp$TX:DD ԋ:aPc}" 3h>w:MT+ _-]MQ ~(MK?dWjjqՌ$ 4=y@;<BQ1ޜ`"AK㢦w3Xᚘ^=>a7(9cN,)NZ|64=Lڕ!䃞<eBExl>#8Ξ7e541?C=4ɑr܆6|}YY=-S#M m|'ܮ>?هcZ]O Ҙ n&Ǯ̼dyUJ{;y\߇qmvD)e Sİ;$HB1"HR6`lqzqix5i`Fs,]tظ3;OBy*V6),Np0ɯ lQH,]-2Q.[M$`Vgkc݂J4ufs K!HFDԒ/ POBȰ)bKg'_bɨC'iҵu-SKX9d%da9S ̺_0~v4$.=# ;:YR9VW9DWdlqÅ62Sbt}{|Z_%.ec!>:s.V&Bs^/ϐjCDl(d5Q|ej3+Xg7sRa6QC zyXOA{+*u9/ٸQ 0k{F%B~#)dE2ja|b#7>ݝ>Ť%+J sm &(y4mQ+ѩ3T# ",zPE~L*(HͧgP$e4{Cd1L+:wrLB-;' 2%6Vx\(rM#"#-#@s b<<~13RUr6I$cOZ;bDyIsԊΩ=3%:x)Gjqq,ND k YfհoObbdaPԨ /7{)!5y8ſSn-BL'L'Ī/ B]q!nP[Ndp覎`H/D:nmMZ:SUU5qD!%ꪀ}( X'jҌFREuckʠ;ֶ$KTLب9P6ou*,vՖY(Sok qpbV׭Q+ɛo]:IcH6诩 V2+NWn讫Q2أT.roaQ,-ZX^p$Mz:ڪ;ؠ49 Sj|lNvZT澯L>~kMYyn,`fw]=\Hs2H=x5a۞+z YN:JL]?2FN XFB;AN= +5X- .tp6{ #г ̔UŒ֭c|YpTD Ojñ7i}5H0FY{%6ʠ`4[gn7aN5Zоp?Q_%[ D<;69D9lOm+4Zi-JriNꮏG;ωZbF3x̳ΣDWs#mtVՕmԊP ,v=:VV(FUKB],+]V.=4,a\,͡B7T1h,f9] rM>ᢡzk-j]g ]x˯ߵ,7ա,V>~k 3M)/B"&܊M4Fұ˯%jc{m~$.9GKKB->wm?cC]m/ &~ c0GYIMeH>屌dV^k[.cr!FnzR IcBe =pxXBL"UUú[꿥qaaCzF5M&)8KդM=b[ ,72+vO1W7!5e5Dz͠ŨiUk느A^7Cm[LÔsu4p:pLKM\u}c~fU7Cyt- S8#eY ݋&chqP=$/Rk-K^SR$y_i`1p`HQ+ܮ^כ 5'd&jB5h-4-gz< ~tM;z'M% [2կ_J ށW׿)לɚmdOA(F[j<1eZ}E^DZ|v$لLr3U°e&\,->|sLPG5'a-or-v/nFOd|#ϥGCr^qܛG6K SRJOb5m$١¯fMdV,'^:[=]`SeqEIFU` &H]v/!s ʙkfXp׮Xu. )^8lgKQ:Ql¯璍?^[-5 |4ݞwHX%VL׽HY0O퉔G1@&Wprnf\u[wYȦ9jI7%tAs`Zr{㉓W*WJaC+l+{  W—W$7uff9E\ !ul- e֞ ,#eS{Y ;_/?v2,hbZjuɓdrio|ak'y`CM61L5%ƖU |[O kdej,Xj,]~\Bħ\8柉|Z4Ex*8Ν^媭xˆMU-ft^k Գhsq?/҆@6^."+J=^@5OQ~W!/-4̤\i%N)XRl2׷>hJ#ݚIV)ʺM歔_-jÛSt]qh7q2IƸRv~4ĪJZp)E'NJPҔ5ږ43WW]bNO&?)_-jZ8WBlʭeץCCNs XwӞM$WkyG"M?ܹ5CI!8K<ȭ㱗cEqiLk+W(:yS!Z*]'LC>2*4 ýNp]\>b{*+ wiDVlJ~.SzriE,E4*9H:N7K^ͥVaVxҤFmU߂" 1[@:*ǶQJeK1 `Pƽ6^tZ_ _ߪX#VᚊK^MdRh4RSDb7huw9#c;3BojSĤU:I#PRjq \xݸGK8BF咘0gn!ƺz yb?r캽xsYSNhm8lii8կHp.& /Y`Ysw=njW,Ϊ1>}JqU3ip݅5W]foDpnlkGkf'PjvS )kRN7-7 +__:\ˇ"MVOmh7߃f{װw69w]& A1 (!Δظ%ˍնۣs5bRjRKj7K>t8O5WPO]Bش2M?ky*H[KXJZX˴ mџ+hhJ5ܪۗGF|Z4%y NUS?b&J]EQbS!ƭ[X4)1G$٦ƭTU]bb~&t`P`)oMIk`q՜`-þGR!GJ>7B0 !B!rUADzoꖭB)LmcZ T5IoqUK gn%\kvZ]\ZmqLmi|&l`!"c9 9\Oh*\Lqx?WC*Kkm recOA R޴+yh+ SaF{J;-G<Spԧȷ뢒4ݞcT(/1bWg(4 ʉgۣvc:]xvH i?7!sABl}U4`LP.d!3<4CAul3#h2,W4l-j:CTGz eJ_fWeZYE/$VYנwvy3[q9exIiК"RMVxːFEjW4ƐuC]ٶ#/'G-VX{ȧ\E;9e3Ia3GNsDYujb*p?npuKkoa47kGWiqG|z.2FA)K%[kޭ;nLxFH`Z(/ˊXb(A9 EK 2OG>z$L3cj6]Kild;Ž2/|և&<[i alaM1 |KqoF]ϜRW'_T_cNׅ2 {':J4HSK˭q ww2ٿS/%MV"5J?>)u&mHԉ?GQ}\{ 5jˣsk;"Ky,oB-Ei]8`Ը&([N"L O9jcD;ww^7׌ɭj_p dg5!"qt)..^v~/yWg]ީ(S;vnRwU<&ϧbJ)"lUNBq~VإAY}ZX E{qH$;NV7I6Wx&Mlq 0#3.""DlJOz'-hcz1('du{Qd合`k]k!Ӵ{t?.B^/*\1W/riO"XX8}mϲ{8i_c~FZ x'z?$mzAF/r,qNx/҅0{X,9T Ǎ9]\Ib>9GòQQ4RV?s@,C{9up9e_iU|"4v.{|*G@nv'P4F@oFW]WlTVү0ehuVIݍ4'N}^{BL©ud+ר+ibt#aߵ- +tnBs./mCay׍!J}'5OGIM;mnoNgXްuѨ*kk^A PZo/TڏD70FՋU݅dBƞ }_:R93dKzI&7] bcbdYI{+ҞgN \K CGX@Ѯ@}DP@eǡ+zgN܍7RoFz'*.E{5~$z?nLiNK;p8^%"ˤ>tZ*td.X: ^! `ne۱߾* r]h(iTVK+|:ϵ(+$jL9Je ?rIR#mz-f4'C4NzJhpKk `BaNN-LY^UYxVݷ%n@ʀ48n} Ҹ(zׅԺײY^S;bxS *$N2$=ү]}+Dc+,+Fs-aT4_棐5hXU(lBLplJ+B (%gy/)wBJUU⭫X ,ե+57[^&=6#Z)цS2a>w`$eׁ{UHtEXo>2Yتuqb_~W ®{.RYfg} ibmqc!I^42{YںI[򦾃wKW{ĭXZc-Ǥe!0b*^'߂ p: !"cI2h0vQ"Uh.}mDbBPMDV_rO|HhKxݺMwp|oE™A8׫}̯h,iߗkhuNrDV`ÏnLaCtZNvU}s%g٧ED:m,!/ ,5k,VC8kͻKti{H7tLUf $2M!Gpe!aiyjܒuT-5NkB} tΧ5ҞS4 ,QJ͗Yk5D`;{ܥSfEOAiyv9Ij_SW ,*hk~wMʀ>պIJ#яQWO]!l#qH״O`Cg"܎gȐ0ƨR|_S!+J?u6Q$tVR/eZѩ+TȆمhWnGE+JC׏/JM(̊$ j;hfQU6i'%l#g(NS]LZl?&V$}PU玙4É>;bG*F Gt_Dq+ ɣOaf(;~B3`XodUm[6hb Ύn r;jz#LU,TU*E4F3o=w&{g,ވ(5jLb61UFD.կ%S+Qg5,!oGcq҂qN]:WG|~2ܿ޳!ʆ1{P`>jٿ?*;iڶ7{&V>5ױ2/Aaރ;epؘ>EM@}=b,80gtâT{mw}{Tu_pY[ S/e"߀w/z`MNGMA<8%Z"n~2Kv=%S?,:9:"qFmJSP -)R?}1U耹st 8| A=6,~gGoᨲ52\Z f n;lb\LOj8Fxʟ~,;%$Rړ頡2u_jRV|UȚ3KcY/Ȼ֨7V_IX щ'Ƨ2KvtQ3##o^~L wq1NJʖ*{gu?{+! )-dpV2{2lh0Z-^85wbsU]R?1Gk愮XSڊFq4:zWBP}Dʢڏˮ m=<}1BBNUX2aE:p.1:eD3JnTHVkT~ qljlpń0Clfcyg$8wʯ.*^Xiipi mM1h׋oW0* %OGXe> ߴy@d4"LqeN"]W4Pg׾1\4gs n"{fSQIlYyKÅ[~h?|+@=婏O*p$6لP~wtqE~_k;.izW/%Lq7Ow7K_~<.CAJ[dєkĴ7u L&b9o&MU46_XEj3PF:B S/Nd-RBx`wF%ߔ\8 G4ݟ9assId`*EhYz .DŤ:E֌%P K%xXt6ѝ#dxl]qv 疭\:jD'@CV?(%Bw9)H1߭q߹L)ϽV!Ir g,hRyS08 ƚyg=ڀģ>3afN;`QhlK fg1iUyҡOySY6dyē?>]ȇ)=߮^U zAH<ѯKl~m .zC{D[h3-j9Վ'UcF=aY!q5|~GɻLOܣcc=W,E4{<Mn,'es 5X-_y!plUP1~V EV\), pKfQ92*gAOiNm]~wX N%a3I8BRP"6I7vvm%] E|-M/|4_qԸ( x)7>Ek}As47}TD$Qg^,ꚳSG]5|:֙MUbUtXaQUՒY' kSc4҅^ȶ-;A5-5 R)[L*ZIMiKX mCCx7^>KiZo_8LE)>^v ꭎ!0dʾάuyRVjuvjYUۥ!˦U+Q43dь3_w62Yߒo"bޫC"$U.I:DJj!Xq[5l`YtLqgt٤H&llReY[T)/9CTǴYNcL E?~6yw~\>\z.&ٖ=#Tڰw^{-v[el eA(ST||)f4?:qPkhRItY7npQh#R(8g`1"Zt&\l1$7L^}|ĎA1 'njdHGAS%5sEfG+ 0zTYc7ijaHUjܗk[5&-zS㣖5v^SM [{O=e SKe"TА{geZNwԛ>q`R*{ ^qk;'ra`M?LR.v4 H$ޡCy2̪9By.l V}醅D=4oJλhRݢ_OX$QZ2oIM՞"X!UETNI]!dc~&`ܶ*U멫@?_k" p2[,:\jfmZǁWqT1u)MʴdT):Roh:9ъs\w9%.SO4  "QIℜUE岉fUtQ, -3`T% nU$:3K Pg*`;H7)AKVcYMUn]EP5e{kl^ЃCY*:گaCuR'I|2_-a&mVD{ c~$sooDb/AjAL. RU{Jƍo2* {7k>7.q*y/.7!]Jcmy1btan1,ڝ7o]:&\ MɈ).s}OR36{L"#@ ՓEb4o7gɁ,+y^`y/d۪$&W>s؟8ӆ#-ݳL+wz"fQ`7Mq8ն3brq!-.OҝO^0s<0megDanTVvgw'H7A/yT@>unǠᘺXfx;'#K9!;Y ΅n5i")eY~l7_|^x"'HJE>X6s>LeSh2SRqA$iN{wx/T `hyHOϼ{ښ\¸ʼX/ KGe(VѓI YIR#7(6:2JJZ ~bR`gJJ tMƲՏ];Ơ*Lh4XZ5kso 1 /FC累ak-i|dAn|ks[T)eW>)a)3/'UId0qD*.҉FԐ5EW!3ۢ]iDB``ӞkvbENXgۤȞeT08sjK6PnSlDNS̺AݲcX8q;$ ,JPE~m sK$R%foNP!,bcsIΫ:D__ _K,^쵙d7 [ބF3Kqxop;㽄vSeW7-ۊ62~0`:/:&Jǣ,FQ /(WOkt#]:Ϟڑ^,fKBB fѽɱ@JOPy|,O`U6ߌAlfB%l[-4)hq|owuT ^MY*ѯrHo3yFe'B*)E˶tb(Q,V> 2AQG|4A8V$j|%.jzFJn ̊Dۄ 6; pSԞ~$xeqH%TE& PJ#žPwc~l?)KKӸp^{9+r7>pLh|Gڠ:8"R|wM49jE@M0-m%eI|tH(Io@ZbK֊'NUSk$mIeݳ=g~AM.[1('Pd47{`(<=.2Z6,toZpl+m%?HI o`C|q\𳂲b!&ܨA}F79! z7YṙXv?|43ȱ|Ie?qR̅%k>ngv{s 8Cω(q=%э:(!IfMw5廢ad#[2ƃ4/'_kvGP[)f(%f fbӽV8~NkG|?XHSpߢʣܽTqm@jiO ƕ%v0zʸ7} F=gNWH?R].I:%P[ (Uu\C2RO yrQ=(V68*3zGqU8?b L(:#L%mF/}i_Bjh=FOOjtlƍt ; y>e-25aUTyY֩DZ#OlN0.Bo%Ӻ/V]VRn^'UyjU4Ej+$PFx:Up1H+>wdzYjԆRH$`Ju9 #1a5%CƵIG!٧9* ˊȱ > S祰9[+ahO$k eSOPzjcǽ6/l9$sԢ}y}z1غs9/PePmT2Q>9vd?11`O 0'Ԕ%2oicb˃e(B\w;rr*;ٱmv My-ƨ[+c:㡣68F֭<]"!Xl>Da$s0 z+ O@;R&ծfqj~J8|rMu7vƍ$ܥFYd>wYD\fxD?Pz57$!o֗aLZ6Dr~#! C1 w&}9D* ؕNUٳ2'β&Z!$F"I%jSuY[#W$#mB4O4)i<9- 5,=AD@*KSGLz3 &*ҕ}zIWulDG!Rmeݚy#nqdNn'T}]o۟;~f?-kU~t ʡ!A?nP} rԄ|ܘ\q:Ndk5"ئYU_lbBV\sNl&^9N_ JrkҖ5fSvQKA"2+_73Nƽ$7FUy*50=OP&1ErN|d՝xP}~0/ML>-(I*~YN87ynE8(n:S WqE1k!whwʆhdFQgMCv:u[LZ3^Yz\Axo-<½<`kO0mHATtZ^kIJ"=SDғa(J>ZlO㰙Tٴ_D%d{հΑ1 mkҗJWF!o*3,r9(truW%7wr:踻:\Ν{0Z! ~*d"OYJ Cr\P+(u *HP|L Uz@_6;Q6uF&^]e(MKO 2bq[QQw?MNtE7ǦS.-.khb)%UVE6U;]EU`ޚѲ̻&?2ãJ+viU'vjYYyv3 p$_ar2=0Kh/JH IKϪhf0=)˷}רi6~ZVg`譞H 簉 !nioTWfHglCI;;wUԗN'9ry^tTwߨ{\l@DG-Vq=P<ϣd뉅Ufe[-CoOӸۄ)MEc${_dh}PؓCMP-m_^dѵ1Rfku8tDI >h5-fǰZ(iщhvw_o|&Wd86^ | tϚir x=jՔQ9`sVqu7789nÚvQ(){'T4>2y|b >d};g IR9Q-`/޸*ˡ٩`݉3+OJ#79s7v]ӣ]A\%6 {)IT"s6!ocΞ6-Q Dh~Y%AsL2 oVE"^L#D~ϱduynQ?EI/΢F(BO_na`%A7Z5:!T&МȡR1.QRS|p/ZA@,~'dR}xO`Z3Ekwe Tu xc#c$( Í !CaI!4oyrlG[lXkBFUL[fĭ. 7Kƥ~/=Aqwvv{1j=JWy՛ދʚÿ'>.iвgԄEW}I0>ě>bjC8(HI`qՄ$!bRkӔilKQ4_)US\%i*K:x+J-XV][` Bn'mZ?Z%9Sα9k2/~;KJ}m#V5N1Z-h~ljvyGQ`I y:4;F?P)V1)ҰO)CXBzڢ[{$ۜ)BM!Zj`Ub@FKj g}ϹOlx6oK7 * S3ʿO8uQ϶d3gc94f Pq .$`<4腑\Mn/[FMHˁ9X~4E lc I, % d}i=g@)l:O ,2$7$vZ XO9\Hw2¹-',]vH}biY6/ҩS_nv?Z{g7_dGْuGOxP,wjVDcR@֥%; 9E3JWԱN}RN焭yyֶw=!JI:)E_pz|5w/Ḣ߿#YZ^<U 8 0Қ5#@;sƼԝaDa6\'5!P~CsOdb+츉PJ?s jb*S|+?.AUPx[Id:É%is12ɞO'x_eRg8Z#hIN1.A}GJCuYɤƺw57Vh"ٝfs] PG |:+t{Ϋe t3V:qN>1޼)Q{[ \V׭OHqԨu^N]qf;/iL>A =B}#KXO 1n XKPڠXcy}9$M)_DЏP^?'NWWTZSz(3 =}-zz :QrV/(s_AKY+"B5."D^;}&`xSn1nYyxve"VxP F(J_8؎J2]Gq2LiΪH9&S ;9:? _1I0E G:q#*79%U.a8m.`X MWH}緣L8)ȟ~lq!:S5NlP#R*T{"c#IǦrAزTe1RT3<}M爿wQ׋ +S\iR-Vfd)}q*a"O-Rt9mj39rZ:o+I*0Dc8W G ΊBj\ [㺔ӌQdX=AriG;3Zbe~J-Ht<߳9{킣fڀ'8tC*ʯp?SՂ\Qut1pX氃h_Wz>ۚn͙^ tbD E+XgJq}L,T~,Cع`_:b@n'-˵7߬51I)B-zUɥD"RuA;N<5Ւ|&-x}QFnq]/x1i5p,Zߩsgm޷As婼/k|Τ.>vaaNcOcNZ&φԱɿ< ~L* z̮)P3^ǫ^yo1%(i2d&Ķ 4/g{x>)E^>٦W=, R|L#`&EVn4d&C䝢Jl/0| a8?s6=Whb)_{6myjKJԴMoPxsHuUo jESXN3~3'Q%QgygN{>lQ:{m.?e]>4{j$g&('CwL'a6/ɭ5jJa6GI%C [$Ҭ':nIl.dPp"Y ({`\[|x1cT4I9dG* 3KjiXD6M-}w(:V&%BzmcjM"Ryj{JΦ¨TX4% >vGBV <9M먽 ̸_rl'e<ʫP)=}#9,c0L# 9)}':P置"̳]=n}uyV .6yf'k[-W!s'E}aBna y߭>;&ҏѸŇ(-&PK7/MHK$pp}\7*.)Qzzƥzdzd75C)jZ/e;;dzxb.63Bt_90X|;-52m.7M0)3͔( Fb_#?HzӠMw˨ty% #фmEj)*~ 3zY!Cyv3|A]fr£% e-Ǜ|*4s-_Awr>a^'J}O dbnVQ%э5^XiGs= sb~g9Z)$D괴 q<,Pc˴&K:Eb+$d%Tks8@-+ +v47Ow"%뢗Ix`n+ xݽeu?2髬רyA0nV{0,Szi0-P &*tzu1M̹J2_j &w/DzVk)=cߢ=_ OIޏ+`/bk>B{|zA+n9~<(4fs?@=,3ZRݵrġ?M,3-Q*C(%@;\!0ಖ"g)I>{'ef>g'-Y2뿷UeZʷ.%.DYVNWz;F89E͔oWV^6]'[_B"UD^aL,(I 8}޿"猴QhիyJ_:= 4TCQڴ^5AXϡ4HS'"qkqPŪD!¯SBIktZҗJTu!Z%oRA ;*5D4VÐk,6eK؊{Qq֯ k !OA jjRiz}+YV)|_gy4^"v(Uexl㫚fv~%6NWZ~kpUJ*Hp[T:qsZN_FZR_ UCtX=ԝRԵKVB|S O܅_V<45Qc!:Z)KRx>dSICtIj(0VaW;&vo_k3;ՅtםLV6u8+Ո)ع[[\n q*3KW 2o X֙4 e|l;.XS^[#X=}r0[:psE6NBma+F[11p-ڢ19Ǚ4=S"E5jY7Rxn`r{gZE:Oo֮?m _խW>M\KcT>&5}c'2_{f;,:7ܸN?QT09l=S߹\?#r%4%y V,ܤtm1YPڻ~mq"4&I$(,'\|3ּ{yyMwYDW[?{v#k=1W)*ey[g63u77buOM3ja"o>&F"M<':C׮n׺0}ߙ+U%aƹF B2 yVޚ?o W}ւ}Qō T9~9;HGҳquYܪ2KN7gZ+1%AUP]Mi+D{Ì9|zv\2ZɷfLOr FBdРFMwauuR-}zka1ip7"LhA&H9o$DG(~Qvn08D/[?`,c_7sԓrr>iPcٿ*Ԝmk~ѳv{9(L1 U{MYVȻ=xu8#E͝*cW]06EטZ~0'xW %i/};#4CcA!B ƅ9HHwT.vウ>!mcZ Ba& ޜ}`?.>!EfGQiZ6oY"=*Su4WCܽ@Gk8 [)a`LQҪTR9&wLBۄS]qi4W1T6YhG9^/(&m U0< ^o>"눎f#>~R.%cikoÂkj=.vd,ha9  3DXQ긞{~ScH;fn;}ZRUaKW[ʳ!V&?6+D)5"+n갳nqNq̼B >2QᜥwI{Gލ뷤qm=Vt0>Š > )$ ynCB|Y^]=KUs+u/*E{pJ찔O:6q[)@1l [X/{B1_z9CB\vNZP`oTj"r!ի;b0A~̐efFEH$/\ks>׬QEiu%Jd9HpܕE evOz( ,?ۇ043wqc|' 7 !r똃NnB=aem^hueHWtCϾT ԟ4G/jYd;䠲d0\3XPJǪ#nrN1A?lgM*6F4?…SG|7ǎ$\]|-lewgҽ7ydd5Vs:xBd􊚃'!,e:9/+WCs.UnmiUp]IVdUvhQ]d&e'UxyY>-I2Юƴۡ~tLF')(ߗ(, d.㐞29>M_J/K5lxOyEo:QߘZt_"`_v3 GO=jl$a~I+=+9Hc<أWRSC/t7ץƓ!빬Eߺ1X.64[ɾO7߀?ܴlΙכ-[tJA}+5 ئ쬏p9Ty4$Mb%M'3ߐbwȚ~]f/A<"5rB ;xo9vcB{ث26.2Y?[5gϿf,ʙ ,~c"ͮ5x b;!瞋&~S0OQ2`UߕU|oer\ЗUN_|J&*7F`ҷ!M%6EgOT^sCڴMQ&ub^NحkkZ`YeW3o.Evz`X$0IO~_ws5#fq*nL¯ޱMA_ql.89V?޿6W뭕}qXVWrnYof3ݚ [e"k=}"z+bS}S}U!ռiKQ|ՂB)+HqKq= ]~w[㽤shR{ cVM=UэCGϟRy(*$JȯV4anYQ:!-uMnPmu֚ Rn`$oO3L~\b?:riBʺu@XL) cfLIZ`l{j *J_iT)n,hJ[OU?)zKH^ITa=sʖc0(9r-/dYd8Q\9)EAB)=T*+{ǫn$a̡b*pϩ*tRP#gXu1C[`O E"9$(V[^! Nsm!b!a44$Qz%Kq 4M)a hm aGڄN1ۤM- &dJ%M!ICXzofϥ =Jb[NpNVҚ:]R|zB* V pʵ(g4ORB⪡eXCRvAZSD˼zlpX?#J~$oJv%e?eQHɋhV _AЮHٓ\d-㲯Ϫ-ϱo7 [LK52/j?av9J?ʉZ?ӫMI2`莊XgJ;I ^J=EYU*wxwMsᵵjUt-X#`Xɬao? : tX?׵<G%% xk|,Mt nYMt#<љlL`qK-!9?IDBw}ަ']ͫ5 7BߏO]"gۋr[S= A@ ya}o++z6NRId>q_-J[^P9i~d#?7Us]Lck}'icC`vA&bRrFQ]1rУ| p*C(#%#p}CZcѰWm_ qAQ 5ͅ,M |y8f4CK_"דo)Gs+_Y VuT8`b&x )A<;kbQׅ6iUwڐ{s04I{|)*m]LYS"Lsj!u+1h }Bو3{,׫-{ nڡ<׻rz.` #:)GbYH^ y0x:qXO9ƈ2ocL-k^U]9:`W󇳽d -`Fvڲ1 #v?.fe*,3}O#̱4Ο!,6JS#u7cm3ViץF IVc  8AX{_-1%Wyzhk /5 [1x8 >g1>"xc ؃y[\^IKo8Oy8j]ht#p.#Buϙ%.6L y1ƙӭZ, c 0$42bfm28LӗgWdrǘWZҰ͡ͼ60C ͇A{a+NJWZ\Er'oC|ڇ҉He, 7ZzV>Wy}wEtܚ5lId+/j  p:-Jc 8u1b,CPpg "'Cw0E\EqlC=45QED,tsf@+0>cR(xW_(Ω9B#zhfh c˹ i&pa4ejt7/ UsSuu0N#%DO^>v xxM}Xadnr늁[&1'Lx.WI}an4qxR\w%oSCNǏnYaZ; Bis[+o4WG@]GIzph B|?䆟 "q WAeHD$COL`%́zK{ez2TLVzRDM'P;?<#%wTlTscq>TKt[{r e(R3gCH"Z/rΨx+~tu!cc7|޵R|Uml貒$Psλhh )#D&lfwu, GySsf9f*k"in 0Z+І5Vxp틧iXg^L+G z)f#/݉ẒuMTH &m!)_2<S$@9>9؍,ձkiŌ ?$F[NQnp@SL a8iqJ ++G8iAHs1xXXnr tI1Z@lͷ鎚jAEdQIhbhy i&9as55HYQG3wG9Gs \flE۶#7D̝ pGCXk={5HtrG㙹P m!Po?+'#wh0%*.Gx;'! vMgO<6__带.uyUV8o?.)ōc-гDkd\{P1/hBRf?`QIrM^8S# zsǓɉb,9eUnwfX` Ӯ{/?A[t1fh"UWCM.?a0}sznl[r+P:)VuKlkןIAh .u4cGf|+JY iJh:&Qs=Ǿ5ZJktm7H~>ϦK$<ylsd2M=͡? -ӊr=V} 1~šI-FU "UὗRWcLIFΫغY B]zω?S%X)gM)(B(ºKڵk `,B"mK2SZI#M6|AaWΟa碔A C\jzIyt46U4Uwo`,cZ%M%uݩX)<42Ġ5,E"C"+dդKyCKܮEָ И[i>y54/5ѻE4/>&b!1}ڴKQH(br ʮ)D4(+J)ai2ȥ-AMT(6XdQl[Q!ME$GZ]jS4KD)kȮΒM. PRuŔ_eҪPM\%}Pa.ۂkU'p1|+E-wAkIeIx~=>g[esW5͑ziGγi5a]P9_Rk_UfAq62slN+r%eEOJbQy6!]4xǠ $U}kEdDBeJ.c@u[v]0%hh^nEC7'9\.0>? 8x6T F/BXܽv{1&Gh*Y/ЉhIuD" 3_߷?hy0)fj-y4b#-5Ν9KLg42]/}7)\>{#ehV,(z~}}ۣPޚ/~"OҳE|1*ҩ8wG]OL{gס7w2ar3ʔES-DAwk1w93\X<^?f"݁*8|^ R->HSjOC U"rbL"p,ɱwտA^1vZ&ݐc͎1&bP!3aV!8P3[ ]W-uH9O'K' xnqZ:LUNߩqƎ$BsJS#zb:YhD_I0bcK\1WO4uJ03UP8/,†Ť/xޱu-&̧] ̱ZNs!0DYRH6}alwP3#‘~F05ƨt1q 1Q0;T/#JXy(J* ~ I+F*ZbuϬ=t<2m_̤J<@6@P13i*,ob%ٻ\|]JR8csx8sws(Jy*,ɗtdeʜr`<`XX1H-Gět&=v= ՠs?w/5^Y d= w, Y8E:a.`X ,ArȸLuKi'dP^fw8WأD23q@ibW^pB<{!YJ޷aĻK-} \a 㲨}<sା4IHEyF{> crf0oPy>pD{V &3> !`Ì1)^$g$h\]EؖqGa qs|V,L&{NOڍ H̋Y<?U!k6C?ɧޔ -U,x%1 ;zMDDePk&Ι1 yAP}z!tip1E7w whunxgg f*l v!ld82>[L.TפJ۶:z:1ws^yNv,R'_0^#c0ٶ#W ݉'r$6t*,u/-˪Fn{b?@u?U5}ƺ']`&308cb V2ٞN3+DA.XbS3nAauD"U \deeRj$ .=V41Sr^X S.7/0\[:ċ bnb^w5OޝJV%k5>S:p/ Bd V+33)THJtr9u:!Ktf-JUu{1Jn(jy{u¶"@r$>8zjOQkK&)<|Oi;]~$-#gXw*/oQ*80ch-2BOwWY[@>"[t}R""yh/fptVp( o~e$Mn-xM}&h[Sjd[ݍɎ)/E8&4 uq i-$IɦuqN*v$ N$8~(rG?}E3\܄aӨ㨄Q7'V,++A*d؏DeFNp^uO |+|}.j;eE,xQ)+;~䜛e80>f!EW74Kn{=5^#4*A}sg;{M<;ғJ'YD#s967H+\oGcQi8~[~ܪ6 |[9p_ήPE9-+1y;7Llmڞe{.q 1qrMܷG% r= KG!Ōr[$黗Pu{qS: nU/e{ZΆS\YߛS}r+fiXV/DgP!}8&yHך)TRUjZERvIwub`C@ ~;9,JHM9yl"orxI#|TS@"A$rtҟ34bWg둂,A7dt589\R%,lM]]5ehC1GF"ReO!|bo"P#2;f]7BFl7*>ej_V0\FV+Y}@DLAq}=뉶Q[oYza]hVzZ]& Q{ h[pVWWĖ$_*9}QaVx6dt0|ȻPY޺jЏ}*?e)94z^B-k*"{lzkddJ)`zmd54<Fo(Ozp- bۨ>eJni]uؔ0qĬh(Dz)`+km]Q2?ٴOTڭhmeQ)Qc*Ҁɢux=*uܡVX69UtPMs!ElPU7aQ"'JZM!&c:))YJ,fUђ1f!pY 0v{L#F(+T^KPRm|gWt~ysLZp63A}N1Ŀf>^*:c`Y6~YeAƧ.㠬GQCa2&w{==s΋bhOQ VfI|).TskkRk$;Nn Źt9 n ?H%סMf~ͦ[TTIB= 12l2"׫-}:Cx1ƘsaJ@\&/H:b̘iL'9F/Π{R c-bd3 ǽ]4vx]lib-%!y7&(":&ʿ9 ;VPCI#Rc}n6|_sFp U+[Ÿƞ5/ǃvD]Ƃe:,s%L ۛI- ~,y#Se>I]PK#.]XT/׼mu}d8Ժ207}/sHU4bHi.x;^dt;(`r@z$57g9)h HO7b-\q^^# =a^N`,:~G>Z#& $Őxd9lm[ɝMPcH;4!Ds9Na[GKj^he:wwG/dR$ouXb3E!HK fXb][ Fs* #` փXO Dgtg]?JB=1Ȯ:뤰h&_`ܮ;@Ӈ#cږs|mmtq)Y 9=b?T94n}W)RkA n!\Q; ZavQ˜#B_(ֵb : .?=()nSR' ]DgF RI}=nvj)1fMl2kՉBiq1Q gGJ%Ŋdh{6\ 0g?AxA1O]kϫ9{S"r=9?l>}E>l\F׆֭rB<(/oF=}f`4yo6bz+6خi6[>Fi= 'pYzӲY}YZ&I3%!Ngm0{RݙrTbcA׌9qhB [y!.GA3+deS.X>AΙ9}+=T>|ʤX}Af ?.!{>=,8*[*QKR5g)~RajnYG5o18P.H%0_ Z*pGPiK}tqهbA1\,0C8 m^Iuná_K#q*ALxhCݯ _y{7uP r9ִh]%MP`F؟? &H1h+L:D3c||Kύ&<^ q)58mw9>;y<${ __ǭ2c6X+Ք{k:D➭ ^I-ZA Zg0\4e19:pZ_?TZYaQ2<4`y8ޤUuŻ~obWJ8 w.y9XZ^e}Sݾښɖ.ѕ6VyhLys314eW3vY wd]dv.Ε ^E4Y~"EyɁkzA`xJdnw̲qD^w[cRBg a.{}W̦|1־Hs)g4,jZa?D˕'wnƮ;s,:Nk͙?u~o 션?Y5_ALY1?ȼHP8]N?>vyD>O]ÍK-ݻd_mm,^BFpl[ו׳b ׫M̰*CglK1h4k S@IZi EQ5w4/x<η'TV{Yz-ǠbpzJL~Br`KȠF3):~` @r hM^not4 jvUqkQ㒄'z6^u{c5 9Ee|7Nl3;L_YZW_v 3x_NE'QpbάoDz]ؑ9?2d i1^ m5DA6P@YFQz7 ,pBJgAow%.wZPymj^N;[ly"2P;a1J}79ŒRoFkK8Ըۦc>'1s.k:O&]mo//8&R]_ɝ,Z鵂߂,ƂӺ; ;I.ުkYX? E nyޥk>}Y=dJ}c($ 3ax>f0 EU>e97L^3VM;sq iowVb'o~}da,ef pHQV#wdP]fmYgS"ЋsHrpʕA8֢*GHFG= DwZ.=J\ĬjuOn3`i?Z\:ic8DeV7䡽Hjsgh ǰ$5ٴp +l 8 EWb\Wq:l7A$w;vç?TiR7ttQlBdyRɐPsA eZ~B)"Bx8bh'bT,dc ZmR!6~˹r8^ɏޟzVxcq7Vlr3o*W<5I)sy>t^kl@.V8>  7!G2lw 5ju-vK)-Fr;tN>'Kŝr=TF.z"f;rAIq[y#0SO uYo?W}=B+#s.Rx'hw>BQֵ])ρe sB9(j5#^ڬN[!e%_ӦZ@Xz.tOFC\e 8~Ix'Pk^:1}v ծLQxt-vUX%(_s|΂"H&̺Kl9Iʫ-/ʜ8P!X]aNR-4*pXd7#*ICH+BL,k/}ě0%fNf&LFг>>F2sPERwLqOj1X[l#6K!chbI*ld*^KF7kp 0j3>yCT;0;sao:{a- `4>pmupQ)f+~|IEɍsoQ'#re-_xgۓD<̦ת}{QrivF;8*կSɗ'p#">UM,>)jO޺pi\n1pR to+ӹ;:ʔ0¿SN^89m0\֢MI\kZ}&8us[QwތOq] Ϩ># ͌{JyW"K׏gs?.Sf쟒x3rQkmw~|73}>s7>tKCk+Ŕo#1'Otx,.ק3rhcu'PBTsR2 (e adcG ÆYL+@2)gMR }-t f4 '#\Fũ,&8' Rڗ ?Hinjf,Wi!&n$l3xàQ.8Wn*5'YV2YB>fY r[_|5v\ưZ'(o(XʓD P@01a!,^خF9f/:5=vVwqI;c/oDrlD!,u`+iGp#ǔIHm5^eλ̭լUW-̴'ՠu?%kW)P0b{ cN'I. [prPW@VCAB&4\(n*yw-d^_О?-O q-mﷻ\w=XUJ^J̴/j5)j-lD Ŭӽj:6wd}i# n\I֤Hyq؉]7ҺlAqW{݅,ȹXr4LƲe![ݿU>Y\٨̫Rrۢ0CH:$bL\44_=RzŞ#=nF9<{Sz騷]fiU(Rϗҗh0K|k/6GqG)uʥ@E;_Jn&Uzoak Oo*~´DzĈzD' |!SpG|KFwFt kBq>ȄZRJ`ƮЧls88țQ:cHNil})|'Ohc3w)gbu8QKZeA{x7U:³//EIt* {Y5;-'sp%Y3ltiԧ=V-hS(ʍCO^ ?޷ꩼG~y; X PH3 >^o.{bprΡ^>qT稹>ǟW^rɢ۰Jr7,: KZW.t9Hh"JJ 3@hӀАpg,xqfnvLeWUYL5ITXvы7#A! Rd򅏜AKih OPƣA2k2f hRÛ3O$qq7إ0qs]T\Ĵ/+,Ȭ :#uVvҮ6͢Qx'^#S$X6^o&šy{ZqsUmg8+aNKѻ=E[txhQs3(zF)lfdvI P1ϩ^Kt%_;&9^/9EJm*ggj{Cެܮ˿#b iZ}?l=7cʧj!__; 91*%aLNbN@11O?jX0BE]"KQGAQTKt| B|ʶ׵m_mөXa(2w{UF`?6$pl[YUfI7VÓqߋf)wIKAϜˌC@xkܵLͩW祮;Ra8jR2)"Y:ӢOP94E]c\d:=J/o`"g꾠+*;QZ y8$:nOI}DC. ȓ`زK)NI-6o="e*i%yhwy4&פJ]RL߉9R4L+oEJJښ:R|]Zn"3ðIdWmU.*ǶlaMZ5ITZC63JOOLd'2pv+M<%S]."$275×abbAC&,H|57a?Ziu86Vw3Kvs6aVKBQk! Ӝy-xZ0üuXvXyp<.zWG( k!4$nDdTWhі;FqE} E*ֵFp.ˍ vYV^4kVn] Ӵ4Tr-E0"4!/)q" 6dWZ[u@msZPZwJ-4_%y< P xu'Il_Ja98͡ cPX_B=zjU,5NEb*c)&5&eύ3ϱMY]ayYwًvUIka@^U>|5Hm:#_vWeüc|db::ge}dhKxKooY+ML|ϾR-sS "uw¤]_eXH^snn@o ~_xU;9ᆭW_ S2'y$ZPW;t] ܆Xž1 i(e1Dǝ.5A`aQ7Tn\KP*BQVʁszRu_jx6 γ*? VW ysz⹋o%{-TX^C.׬.S_6dƪ-L^#֗ܪ67`)/faV#y$3^?g 4|m>zLk=jzgW m~}Էj*B;-*O]{41>.UyfP6Q%]LN/3+KmcJley*_˅bÈ޸[V ZU_b-A+po}^UŤy RgmDV.dSsaoub9PYOb.ؤm3giLL55PU{rJ?η7l: Re_٧=cUu[d! ģhT١]o94'j׾מo_Gؒ$U%¿hoy~"CC]\:/@SUi:VTIirKb ".J #7-X|)VxꓠV39~< 3쳇X߃$N‹Uni ;mvIh7|y^z` ^q$9'ݵ6zlӟ0FSV6ne^<(SGcIvm0Se%"6y `?V9ˢi}6m. O!dz0HywGQ/ AaIi/kyEueX>#\WZr|:.eTfm[&'[.Vz,vKZҸ{m_]e40>ZX :l2_"7~S}1|/qm\],+gY H7$'wg>" 8Dft)Ռ#XIÂk !,iT~ʄz[tԚĦ)"c+J=/ Qe&>վcTרZZܤlGǜN~+Z碭t,R! "R)Uyy?U֕+&Upkn*YGɅ*%d"b(jS0ʪeRPt^ꆟ- k㥕tF7=tO󿠑YcrWlߝĶRZýؽԪ)m\+˳Hq؈M*?g=jZpFBZҳϽ&}DjR Fv˽v͸~Mg|(w3*#U)a5JkrR_Xò?a~&!?E̥'ȉT)(nW d}NAlL;@PG*L!r;M`Q\|NnĚ~#Jd7cnY"::u-([umE)~J\{~ IIau9oAgDel- [QXoM:5u e[b֤ʾfSY3%P0D9*BUWV%R$CLdNz,uiEuH!?7粏CAzr: 7˔KT-KcT%0,̗JLHsr<`Mh1C,1(P#RDUQrpw4#|Oq.uUg`@z4fŢi'΃5߿DR4<cko~ΞduI>=Ads[`ԋ=Uk ,J 6![,k^M]2kI5J2%%TM{ViՖҀ:,M-'2ڸkx6U57ʢ]y*S޻*J#X}kaZ90Ȩ\E?'+ 1 /±JXN8~!Sz訊4;t\-skԴ)O\j Bv,κWա_I.y&!co1A5ULKb'-\3o2uIlEw<«HTluMT%RH?[Q׼5p8M׆]@cX%su25S9pXZ8TF|ۧ%!BF^I }[4WfԦ]Zk }R]oY[1-eS c޺-a/Œ80mOv}L'Ȩ  RFN5CjvAh}dA2T%)*LDIdm]s5,[)몉~STQTX҉30O]X/Á\V٥-@iU-Z࣓ܓ()Kz*Ks궩->IeT3Ƨ/.(j\1)uILK ѳM*rBr5J$)G}):ykE+EpNksoTZycD7Դv~Miq,}m +VՏcOux_jҌVTm(v $PR<${zvi/]iWʪ V_y]{ $VwuTUdnɻm3TU51+{%X-e;<ߋ{ tUU7t/-ZJsTs,Sbni|қ->Ƭsf)b-ʧә3±g'VOӇ)yg_+eY5eQˋS9e]WuQx BӖʪ>Msc@vt5CeREi$]GP̐+.uhCq IŽ/˚l4_:g⳯ݺwdzu|v.ߓ9D03&jB h( 8u8i$6l?~\_~o9X B G;Sc;wk;.4֥f3od5vn~_uӶLR1Ӌ@>ֺC_nVɽ-fqɁH0e#!e&Ω#lխL2ݯ-v-%Q.,+{JB|G/ {_2,¹Ƞ>A,J"˘yNA5~ [Fu2c}/(dYV9OEx~7հ ϹK(oHXؤ7Ŷݽ\$?X_ӖW9q^2ɢ:k]zؘ%BۑM?jWnl˾Wϗ2xj:s1PٞmW(ϙ3b"f!YCA]apK??gvQߺ礠Et[KrDzn#Y?xs^% '9XSÒ~104zVj?WUgeVw*JJxlp VLdS;*?˽|˚˿']emݜů)el[uDfc7ٳ m']jy6R&0*F6:Ñ9eI.Ƙd<0,φ(5Q(D<(ē5szb_|7qM) 3!0ִ.+RIhWޟYNsRQ&ʢ' z-(BE_V\w1KApE:op,;}/}7an-cY8]K$n:){J"e!<,3, z4T4NUځrvkgWא񟪬ظa͒'ݥQzh1CS$v_fN݊6osuAlY'wbei^DoNqz>gg޼q{s]Ynf|¾VzLWZ{rEfl| AlwZM D3W:\fG\ɞ#DǏVuIgRF3yKQMiv2&Z}MqRw%}?Ł4+}zlR==swaTSL{TƚG(8cn{*GI8kMQ+/`C񓏥z9~;N촯>sޮb>-`XXʆ *(ɀfF|n<sOM\bR{1{c^j$'1[Wϛυ[|>u\[]鲭s-JC5oC_!hw;\WݤT0 Ct6=GAيUjs7Vmi\6 nL51=VXwʥ!//ľ}xɇX_#CW5MR&RTZ]nz˃MuA}} 6UnU>NU]}BtD]%hbf<;^#1% 1}EB5qAsRz|/FՁPry.J𤽊gW$tgyY@47X2'fS%H#&dƳ^L2mzQ -\ tgMҋ2qh;*2wtWdH[П߻C [3l.Cv}q,cpZǥ7@AIk֍y-*{(u^ӊ(n,uKV!)Jzo+NgN"e욓[TϴgQrԦj)ڕmOUn-އˡj6V;df3=cEⓕf_cBH/cjp,+XkO%bm" u] ppVўB1 hy+;UZ㔩2RA\cκ55וRE8Rh\UIfD5ɭōH7 Er ɶFiˬ.s6ibw]ŭ&vrr8̢ڽFljakk4sRm"]?og.;uXX R]_SeASgLŵݝ*$1x+î"E\dZ r}o,E( F7oV0וUji'J>"oXac`qAXVz؇R_/?,~*BvT+"5ZI>B5M/ʹkMZg`zJk\=2 F/|ֶ2g+vٷD{LIF~y2Sْ/d!,B8?| AaU~3Y]tEVSf>UG>]^YTV+fD8ll7+=THtkh {@Zr7Kʤ%Ks?ZW-,vmu˩{Nu{h_CźԂRT]BetviMvQ_ND=G Dcy.W6`h {Y&UnU]d,jnLnֿjkd'q:_3rEAz~tO lcP^#M5q\pW9b`Ex5LcYfB4]FV2E?&g,m_Mۏu,i m7uߓ[]ʲu)/ ߥXvzOPںkúf, s۽]qkB}*̫٠Z7'sKG!hVYvt'0:8ɻ4bOmאt<S>-R^S|ju\e]zI_tVƍ}n|x2ֵ>|r,1b$MA}ќ-d;Z%1 qZܳX뢼4zIṋ{6 !8[k\SlMo~ ;V9X9v%br~/7D%n,EQw/B64V3]U|ߟK~E/WǜLwE5Λz=2͓dԇ,ؼ6hJ;?JxKtYDiΠ4ˉgY- a#45 EۄY87sx(m:vn=羵ҜIc)[fɢ9 Z=pv]Di J ?t{F+304Ä󒨦 F}sMe޾nr$?̊lL˫t!y ̪'z:&@I p[&yiGq- 55nD-P7Ǡ3*&p*]ŏĬ\3nߴ|aK GXhd6~RԪJk}we5 jk>+\c6uz_b[8U2V064& #%6[TT}EZ`)ۺJ:IJB>E}c?oU-kRT4L)T!  5-ZyS@rm)ą,!>];_Miu Ed4$c8N!C8^4TSFZ8ZPWvU|x $ ݤ-3jx$e)qlK`XB0hKKUƺ zHjP2Cy}Xl.k"M4ޝKWےZOXa0USBmwRٺ-)+b~H)*TsK>s)xeq}v69Ic EA_-jG)U:Jf.-M@Cx5--{b=:l|,Dz(G`xiq+Wxqv;egIvz ʢdBEϾҬR1ΣK򒈟O jȂQ*+.QfY,;EsyCӝtsP!/uo*.Cq˔ZY!"8 5l`qhhǫP'Y$[vM\mRO2SbϸjRd%b[-*u3xBd{JK:ݾ?dIhMO}w%ךe౩–imWwE#U'Pد|a2 .u3V^r]&CWwXqugB`$FH Db;'`-+[KQ\{?F2P&壷 wzӥ .8BG{W>w [2gQ8s=^#  opa )~ʦ<' SO%6f* B^G[*j1;i>)br'}YŁ"F<2 S_kzдXw'K,&/0ȝ|jZzyh# j0roLJ츦Ĝ[WAQDjMA_S. Hϔ8{@*oQ6d gܱg긙콽O$e:n1ʄg;Z5bqȏ^YN(KE]V=3ZVDL;A餒],PiI| c(tO r2*z?'3 IYri:~I Ei+ d%,FǚG!|B<Ϋ!G8x%! {a68Tu  ͧia?ȲEaؚk8Ϲd[,wO0Q`58όy!-$cn n(0L78 #*O)UǎWWUɚboVj0 #m31h5i*;s9k5OptfrU&C)jJɼxw0 ei./MKTӪxʚ:_]ieڵ-_($߲eh\%_r0I2G"n]Kjݵ2BZWE[5L#G^J{~;J|͌v4~TY_o _Jepϭ:F ,0+Ҍ4eJo=oi|t]{MCPjtIזW*ZY*+(彇%akd'U(1LKOe=tZMUe!b7q s]X}MKa}׶;+oJ8rҏkfeI\\>D̿G I(D4nˮr={I6񙧘vժP4KB_W/"uwȯ4S_bu_hlWF=yX_VFKJ&_6 rڼӳ kZ9]{m}tBntm/]dRRG=[awOwIwg,ef`[i䟳Z wvlZAP Ļ"l L05l"J"Ga*G z]7\:'ү-PYfؕxt'ϳQTPvy!;$SUMPbjC d<^I[Y;1c3\ĎQcP7Tśӱ9UϩD7 đ1.#yyOy34bg$\7 Qv!:loVdwqxM:9"T>fWE3?j3(՞U[kc(D\v0RJ$rP $I3\ЌRTưĐe+{eFe"dM@/qCPf?Z<*M1$>v[[5KPYGay2vo3ռ$ fG%Է.":ƥʬMSVjOy j)^ϱy)jCXP)ܗD6~o0צk[7I19ċ.5ClUɨϽ(nrʃk\ھ4n]cY^ ҏy !y~>L\fA]Xpq\(knNՍ;/5 GTRצ?uzf/IuSdzjzB5A`̒A;q-Ef@;\ʒ/ZUnm}|dLHdF JoP/%aTm +i/:7>G]SYeyWp˚.v {cg#p%tDI1&֡T4?~mTjZ$enYOOz[54K4Xs/ cZc?bwhpԆ3=8|3*5iC%s]VJ7/}xcIIyK#Vt{ȅu?qUMauAk[=lx)6 3$ମPWf/OݺkՅOPAᥣ8fsՒ$ʣx˱ w8[ eNXY`9#>FeؒJ~E1`d3ո[-sqY_")X4m - ªjRO\~H_= +x(h&^E]5,+qދR)>jT֋O9]=X]6uS!ʲ )S*~(;?޶?œCz2Ot j̨oI)VUzfTj. A+*%_odؤA2V{v:bnν"-L^@}Y{2)1oi3/PʪI7*]UmBժM6UԂyr^Mߓ8^lTB@kҫY=as[z1=^Ꜫu2Q+B: ͊mRKtPSL;Ԕ$T)6 :PW&M"T(/c#@xDYahZ'5gl@d$8пG&®yLrlSg.+g;WǺvYf{lZ)muEZIDA}ӐPhQt_ ׺ll&oUʆTTQYQʣX^7RwzI%E|6ݝcךݠ(谰j1ɻba>YSk{ ̫9!=1i&(b]k'f, NV'࠳IQqHxΫȵƭ_UsEzF)}+**ƨRFOӥf$VXԴw=r\G;i)܇OAnV^3jT[?gzEnx^E(Z*Y~L1-إ뷉ͳI;ސ4shݙqܛ&ٳu4yթufNܪvEeӸKeСeMNRΛgfmc4QU>Z^Rp@Nh/É\VaY?ͥl(8YDeZ\/K ^(!ј4"Α}{Gĩ/e0}ɁpiDfX?,Af4-,|_n=? TS vG`D^_"雳$rNy E㉗l#I>h8ݝT_181uIBjq>q+j2ڞ9+9g7د sJ-muEmbCsh(֧-eqGQRRMWh uhaD!54R!.KXj{~br#4ZKSQWMauGi%lՇg| )iv1ie9.+Sv[*}^>xZ>D&'oJ. z:h46/W] A]a_[(+FX=wΡ޷&ViUyCqnc{XzݶIbzkN(̍WfQyY¼*eV\JkVVxZQ( \|xO(byy.>ICN :VgLh˺MmDiL#]e9 tK юʭn_"v|ɕMYgmfᾤ$qYVw=ĨC]!l=d>VZ bI.(%*ITi|[U'ӥa\WvR(/-Z=°DpuR& Np4Ȅ[R%k R:~\W&i}KjqYNjZ-H|J`JwğPkx尨dϚI6$)[2wj 웬n[b:Ҁu!< F>M"k΃MRwV}~a659XVɳ~|}]tKW0QSy*/MeqS[-K<- \V4Pڠ?ƾ$O%[^XyA/T-1=$p.%4*IL1T,UsmT>34HǷT Ji?Z7*C[ue*z[Z:O6%Aa*V7m 70 zli:P%^gΪ]*Ytk.7ZǬk+7ɲ1촎B $d.bEI߅QPP&ƻQ_شK_jJU;ӹkN;b &EC2nxI}4CPu_ޝ'!,Ұ-Lͣ+%Ey \֖ MJIMMOQ.ӄZ!#[KG'ZzV(ԧ*g̪9]_P_Bx&z2"i)",~@>%ZCeJz0ɆZ{r4k~MqF/JBYr^Șu.L\g.]|ōbioIN)|mmX!m#LtWHKK0UVh7;~L]\VDw~O, 25zLRe׌AlMX]7&Ȝڳ6w4x*3TWV$tF.͍Y )ꍫR!,?Cd-\<+èqr^_^\4u#}ܾheYy0, : J.3U_6=+D3ʷ̉Z_AzLݭ}۠' ]-3YNujCSbT4<逦U5m#ή唴'Z=cAմFm \UZTXM Rt$jM !E]CFe~k5-H*(6J;<˶Jk\]< nt%YTzI.ȓ@d.Ȗ,Qq\?( ZaSl"FiI>A`RUOj1ByNꚊ{ LJj'Kh-f|-<&]2!sZPxbxٸ`سMJn%YG!~/)!gm8rNJr#[Z'nMҭ l$ghK xE^ Hv 4=  !y\ZF 7Ȕ!!/HT+wtCqg]b5.Gx`':E`1}S{Lű+.+t>]BZ- $;xL:UTq ^ !1E8r[)>,ץ®x)cb] mbWhܤ3.q&)TFWh4x^BͬKr穬ۚX zhj%ġhagM8F^LϧV?O\%5uo;ֽ _QL*{俭boQO]A2>ciJ=Ev0f#>)bc-<CZWgRYEju ~~++,YT[=PV=_檌3u%~O@[lKF<6VÁc?ɸM0?$~Wx/QBs~eO%4) _DҳZ;OG--%Ie渋k+’ũY:2h~-fl9vK:1 ^R<9!N*Qif=jiVu7R!D־F=~sTm etC׏.H6nAJ}7P\P1ʮ>Gy)\Tv Wz>vLk3fgnA|eǛ >ڮa=>̔Wߞi;fUiET %"I'}43:CE4-E{8rd)2#1~kIA@ud|B+Xa﨏] X2Leô,"^ [^#\MReTiRN5G7:iNRk-o=>0T}lJzܾdUCng珣sSZڲhIz3.k)lbioeZpU_d%p-j-CyO_uZLR†\uJaQ*l }LYuv!]&=`y} B]²&XFjUKXT/9N} A]dN6ȔAi#)&zk<[`4,s*,z͠Hn:ܬnR!LkKsg*?GqR=S)?M0X~f`#kBl dLp$Kw>SRIbIx>37(K[Ky5V:SZ:CzѼ_ LTU~m`,œ08¼}Hvm:Nw=i]RUi[IDQo r۩ #9Z"5U>HڤuNuYtsܲs#'i$YG]R*R)ŦTU}:MI&Io#ἧ 9C!J'j5UGeԩ5{[x}jpTAҨg/.? O3aFOavQ%yI!"TE&`YKNӧˆM۪f `ت;)HꟷYGu#T^2VQ0D5ydf6!Qv2#(vW (k!SԣRM[DbNTdG-g:P[§1U\VjOyN3}]T%!D,2ʵ=tuV6um))6@R0ps[Yx2e!~hQ,2{|WV$(SmږzYx-^< hٷU"-QbzΩ-[|CmB-|/2#VǑ+ו\c{ |k.gl|v X7KssS 3}iF9|GQy}hsWj zRVTgײ./Vr򖡩%xjc.sHz~ w9gwG4*}l&yQ\%6MSh Ѩ B8+Bȿe+#&qtIڌ>:*kka+4=+9:bωh(eZu#+ᩒuE ǿW>4 #AI|몶,LN&5Aj;˅/S/¤WU:ZQ5P]%HK4|'MT>|[D ~u)nO?YJ,C+ϫȖɑt5V]DtEp)[ᵒ3[d .:/m(>4 <ֵcYVQ*^*l$غ ܹi%ZַTr8Pmq]`뒾¦GoURI/:ne5.zNG}Èֿ>)pǖYQ'\?~33b*"[+Z-1{N+4~yE;I"~5OZ$coI0M>F0xfEPKkYUsXҚR.ʈ.Δ'{{sNlcH+RPiWۄZ3LH/ ^-4+ΐ[']5S5ZU%vP!LzӫҰ"ZS3hf @k|"ͼ`\mLZ-ρ=uwܵ"Ԅq-sjʢo]`o էmFZgIv~ bb3e,exʳe?R9Hrl,a NZ4E~Mjľ#e+[g_4cT-+VuQ06Y%T#̲1yP0i\n;"k{]]met [ BPa|eZ NX%'Vƪ{JY&H±k߁hO8{Ej0nSuT=Te*}4?\U:Pyv)=$s蓶Т,bUXH4,J=ym=ndݽr] lO حyT矁zN`n -bʽcpIHy Áh'׈n=1j~o=RAO%q7]>v6,Rͤ,,j9&$S%хZdN^'4)ҏpBL<⠀<jzYlח-i/+Iq¹J~C@R (z.~H)T3hMa$؂1_+B%`myR[l?g? jfEQ=>iiP\vV{iAH:SC 4Nꏹ<޽݃zv\v~uQ qY7[i]DS}LSk P9xz, -;uRQAgԇʢMҊܭM 8wCx[$:|ieyzy̾u)89ުUE;գ}GEC9cg94ۛڼ:kZ&OeyӦ>f柾&W6if|M+CW);ebLYeƩ|ʊr+T] { Yv]@Lu{ҎVeϯ\`Kdy Qu|=+b(ԶhXǨ[%bo߃Rjޒ1XO}%FmWq|yJd46&UQLO%ВfV*W@)C:ĥ66⨺*qMT1)C-VY+^JWN YI7:fQK)(7V֕VRau%_Ѧ"^Q0).m]I;)S KyeRsDg`RVBp%4A//.@RXĩcgM<5&[>9]G,U("!ебBԴ-Ϫee2*EFsKkKVaO)u|ʭ19昵ktjQ39!vj R9JuzoMUzqZ#CTCnC\)>!k!ГbeChfL"C:Ek4e;aZ[;[F܇["/a R*$~4{ 4\ޒ▰mLMl.SwӌASFfKJZ5~lźf#aOpkxX/4ѺjI}Q5֢\\j)t TnsZ;jt c,X_^!G''@p5ھطb Tk"Yas$ucXǧqlu_sF0̾mX^* Vf¤+S p*L G[㕪}]1,y_ w&EٝjPJ.MQVCџ~+9NŠ5)Sy_o‚jv"|"o->7/iN ׫S_ѐ`!`ӛZJ^K5(QTqsKRڕ{o2Yw>YI];"mbgྦ>SWU8qMcX~( 3d8) )S~jġ /MſDCK3Z=IWJ ފ}|'+@^j*԰/d{QF.x|gU"Dӊ'\;Vq?D=Ǽ2,n9D.t.Y9pFb2TjސzkQzyNkx󶬨:(Mzk3t޼$nV H~eٴ qXV:Ȧm)Wd噊wviz]rBO_6Z/9UUUaTk}0 0 ^2w ICQMnGB<"tϚ#|NRuÙt:"\LmGt1̪̓:*ȅhm|l^5D| x2RJ^-iP$-*#dɄHKEѓQŗ$ʵ"͵2%'Kmu]d{gsXuYt_\ך| $kEilOzԷYjjKB+iEY꣐կӺ+o0Kfddm/ٕx])WOR碗y(<!ܖn-FckVawmW׆/F]Peuu`qT [64&,Ǩ~ qktrǫ--Dyoi%I)V="йwZ~*AOdkQb ʺͺ)ugGuE_j竊BY"A$Un[ <3/U%R/EowaM 3 q$+J_H-AALmmZSqd#.Umugٮaԫn[~4Lc!/ϛNN'G(,ʘŁqZZwZeN-pv65yUtu~ %WVEw[cGI=g;}«פPZtSE]]RkJX )!4ڶ~M)]ڮ*N= Zwg[ɵqfThTe=(<xUa5$ĚgI|\7"#\^ӫKU(87Vݘ&*5 5=WMmBPS$[ؔyee<]al%Oj=fP`EvupŽ `k0U+KP JSUj-UHB-ZIE) B(C[ꪆ(xN?J(oO*ph9G4*_%4"BO4!E6&(ޖ L*ʣM%\s>~J"IO$j衅@Х!x!8tj=g/\.Mu*С.j^6!'~Գ YU+KanI*)Đy r׌ށTS5h1 )q~Bi1SEcPB)զ}'UBbmJ) oIihR! D!H_?˯ ;TC TQ~$L%{H閛|F4b0 ,ߕ 7ǴJK3q=R_fSW[6́Xʼn_ԇ-CL0+usmð.*/kf^;.Ƭ ܝHmePS.KpӾ/{uEDzOJ>e5G52&b(g~cD1e/㾥m8OL+u&7ѼP]C0QOjm9NUv]XyH6,4ez9ԻL S^r[dUuNҋek`Z ft)ʠ;14n?nQ*̯\lEQ+cnr4,Rr1]9@\ԦU@`'\ƅ`ڢ?j46:= hٯj6i۷Y~5zlULI?fI9cX'⨏ahŒ7:-GWJ뀝)Nj 3&VV< e|B2hJWn:6ܪ(1i~ryHE)ߡ[+*ؐ}kT`a* {M9T%Z](eP?[J)!q0ң>*y--B%B_R^aMiD!drih*!HRTPHk >G:떯QD4t%V^ B<&!eRSQM0KAU1&t2^ $JZ{Kzo+x #V%F$$^%(! X)RjN׎eo=$s_akR&{eJ-)"0).2M ҍT`IT)PX|؞f53\OFx$En 덉:YٶEY1lnAVBn0OIRR)&XnN`4Q#tǿ8Qr(r X$:J4Ǹ1-!\1<愩3ˋŵ/ڏlߦVZ=T rQjU6PIx*TW/I>x8rΤ`?D=~]z ֻ>^BĒ=M3\kx=ԃnՈodMѺR.$:M]椪?WGcWֽ)but9Dc$1dI&I3.ߣeJ}VSw&U7oQܨKL# ,[~lЄǧoW%HQ:ef8OIWiOI0OJRwƸ#UmR^z* #SV.C:(H\ȾSpWm,3|TVkLK̪$נ/e*ϱ44OO5NK4pGǺ[ە ω)6Un`[p=Ma^EVkPCY,ҪilH^*+ũK{6+d͸+\ʳVMcG_iҶǪL0pWR^h)H+r%e50HT\G꭪Z9[V!yuZ"< t> 2YS!++J4œ3-:^cNn!=uvNG=U󜗊*K>B/KIs[FնNVg kf,ػ2ȇWvԶ5(*=P杣dr58wJA檬UFk_͜c۽;g9T cj[]"4ݪd(5U@d%ŦUz yhq;qߊds. #~;B0 NgY]uD[$oTPmgBeԆ(xi縯eFb$J1h Z#gF#DV=t;*5Ga.䒊ȧ3LT>& R0O[h=E(7AK;N5%Rm VŕyJR6XUxk=į:LE[@xj>ŪL[0,_5fY1ai*I[bܮ!K<]dGSI&1\#6,F_ˬG柹9g~6bk4ͿpUty(tNfvo"w=^u4gͩ! Cs^l޻i)VIF,$^ěF]$ɴf87_uuq c #mݪϾڹmw,h `Lp6hWBI^vgq O^kGyU#aTJ2UT8 Yy,m_k=>O?ǕΕd2 UCd8FƦMZEV1olrŷwSk>)1! fyMt WhXډF{V5'<]ּ/.$)𑩹~w.2Zm#w"C͋|%Q0F&KYl) SiE? /H:h6'?b!X=+8DZAgvƃZ_z_1'ʬp۷%\d:oXT7)>W!uPf),Þ@fTeyP3:U(BPЫLJT)!WvG$4:|oi\lREX^SˬUF3=6U{m)pT9OO#8ousgVE%A_SXT鯍;F-J5kzqJSrBҙ9iXƗqƗ V+ufzyG4ӂE5T^;}oy|-+fI WpK2׍ɌהKjS׵ m-Pc2@Kd .:$L{~]iӪv$E*u5NSvP[{jMW%ZkH`"gZ_8U濜"]+Zp)w> RM(U?v.ֵY4${i.eFGNS~ްt%5k^%_)Ȟ-Æ:iuj~~t&VZO~-;iu$yh4fx&N.%\mJE"mb=B:[5L[5vIsD&!wSMW}|mܔ`U*-Uuza{b)&1o-$|T:}UAJ1\-sψ+GE$JBطsD#ڕ#Ԗ[PMȤcuk]&jbl죞2Rk"2AwN7x&!<;a3I?䋆DR:b>ڌJI@\YU&;r5[\zr]ZxBMA^VgmJjQ]]k2W[fxW򱬋QEE%4|ghu[n3|ɠN)fe.Cz ߥԵU$;<4zIy}WD%oA]UVE`֧ YNw}Qy;<>aT`ʼ<9 ڥ%c\MFhi҆)6,_*}?(ꨉzk&ύly#qX1ުҲ-sF*^zp̣UZ#QNۘ$P쩨c~", Ҕ5ܬSzWR8ou1aD"߂7 ܩH*3XOoqkX]i<_T :js4L( KcpFKarEca yCFO,.RQ p(DғXRF#*/ڤZSB}/"^ kIAE! io2#eXYn+Gl:|ZI5ߗ<0JTJaDr )eAn I$Q tFV~yFyPZ҇i<$<|8oy +Y%z%a'76#Y8⓮< dȥ,6YvMJN["&ӦUta^PO1f9]Q)ay_Ql+J;]6A|M*i8z *0ϝYD7E+mnc|Pe&βxYli|eW'qBbT =֩zzKؼǠ+$Uvb-2ۤVz<ɇ( gK~'aXdwS}`J[m-:F/)8 u]\eXg6X!S5W57bٗ٤ \#f*.T)Ux^|I KK\(64զF=RN5k/\.(etR.e(ѪS~GM3 1xP}cV$Z~U>T|kӲL"l]ڔCprQkawJӹ[y$A?WQ/"d ι%m`H,#_+jc*E͝d5vҼG_|({ԫr .ExQ~TuJSW:%xֲSKK7i&P[iz0?u\}-j4&uJ?bx1OIlRs$WRIg-[^SvXҾE~{>O8 ꤦ5xPuv!_MqJ?Vް-y [pҾS@N˷,b lS&wVOaeAzTm4w7'^6`x1]zg'|(?ucAbU 8- `meY{:=^m7RFpG>%rH1KF. :$>m1/SEOUi= )ǢgjJxQdSs@uo_YTϋZ*hYjPYUmҏ.' <ϓAF%(?ʗCZ࿺$Q^q\ ޞ*N[@?aK;ŐVf47,xE賩KS {>"YQF%Hؠ֗ZsqڎB`FhCG g ;@Z8,"_Eߦ@ +ioJsdΓGx7Gn_ se+H0x)8dn7DŻ\ ~! 8{.suP=eMcLhI]?)-0td&^qG<'xǯ3unveOo~=A㨏=@~Lb\煡TU=Ri?U1krJ-޹_عb.eZdXcU6HWz!;{O]uG nrI.6ɻ_+ B{{4V9y n5?dyUCV_q=do2a(ec^+@oZ?F|)9r֣ \r]ErC=E^?K2ǴU͓lՁ=J˲(kJ#idu 欫3zֲ-6HEH9}hiĀS1A\N₪QU*9"{%r_16qeU7yppMʖܧo5/VǿF?9>[fO? oP^NN$'3`\/ PvL1"Wit\6%!Pva7$5\4r쫏QHWݷ·<+'$`d<^kbסzEA<hNi^ӿZv8waį*ums"%#tmYF)!WpOec1m5dv&tӱʊ!2k|SYܟkz{,"`)7vU)cG +C D̤}Q:gIU=AePUVFFWՅG`଼hTVQAWr;59KFxEH( PP+E,mb?UqGL2w\oTgμ/% 宔$;H{=FDD :~ ?tl[%N=4^̹1fo[*R F} ctҘ*r,mK(Λ*[K>eRu7q\$M-iPY$8IH=D?JikbIXE)\؅g_AhxBʖ|JWrfI0*Bʟ6ea^ @r7ًc&>=}^SvSn_#b6,/nڗbNT7Nv9'85=wm%+e| eʶZ'TQkO!s8p˰/฾Sul(^ hNHQS1#]>d%]YG[HO F_(u R8\ƺ?yO6&ρI?L%7 ֎ٲ ^/ȵ=vaVVjJ!L\z94ķSl]]Î'h3؂N^>+h)>qI@O銓W+^[tj<)(3u="(F+SRt'I|ʣ554oyZ=C4 I!7Ejuwwփ*ퟶuW'@ۤeq[^"e+1pS?Eps +^}ҍi;&%Ѹ+4XUo7W~{oK&l1WJlm V=X{ĝq\#+*3-j-|m@jP!ǶQ4\ќyq P !+J} *wFQޭdY, Sxܟ.e 'v7%Ծ6=/Z,Z!OCQ.¬A.BX)W<]ZSa#\E2׵=UBS[xr_շyx&b e,\GƹipJ3uRߚإ2rzY8Iw֮9tlXQT}O?hy6g`lxx0,zd}itkJZUuΉUT&mk}EH׸Ptʯʤ)4S攦'=2l+4vOA"eLZ K"p4Gpk0&x!FՎKhK|YfAY|+B}IU8E\.:#!Jz͜t:Y(!"^%Ƕ0ҏ"!!ׄ}~[_Cʬt#rXR84W4sA)R:ZTtKM!=65]K[gWU%N 4{^-n֧,s,(x˟H @*[N2j2 #^1,?Fj"iB)sSl4G V?5/L&|zs4YK-d!9$ .xNOjjL%r/}Iux"YJCs7Bf$m!$Jma5Bywz$(#'g<k^z>։eťBO/wj&uyiIR[[1A?Dc5s9,#*&G_^Z CkZ5uavQu[Yiyl}a󱌆[+JG:O+w}2,EYG121vO"`sȕW ]^J&=I__5$W''/Ai5J%8pB.J2zS)۴)2w'2]Q Ä; <ܫ ɤҏ/C!8dKjEDqW0,[$?s_k{]=ewSO٧2コvi7Irv,Rc\p sʶ'-]9hi KY(SmNZQWjX CEUlUdWjח*r7_IK nBuZ"t1 \îo0,!K.ޫ>>:Ȭ PlEah }9|l NBO/-anjRބNuU?>wdӖ6Y)!@e\uEj_SY1),sAnYf \Iw-8p4gVQR52a_'OJ’ -ʤfGoIfAGraJnzYeQFiomL/h;j nV Тg̘)U:2{}X SaL(-rؾyU[jjVVYxR)ґNeyT3pc*rz6/u0)5BRG˲Wfsz߻YZ|j'( 6)i,+l"=oe{J6 KP>ytM’CnͧvKH#^)++!tDOIezaGM :1،z׷_`W`X ?RYSpvҦӰݓ C,3!{t"S| {JMy=ڋ5=CpΈߎ.ܪ7:%7{Yܽ^w~|9f\Jj;4і1ghF4! С33D~hЗ?=wAP.fy/q%.B~]T|oe6?:]mJt%v=MfWѩI{Qo-"Y_JN_bb-؟>T׏QHj|Rp&OZdӥv)ƿ'y~ɉ%'B>efϨ=PHw|GѬڅQhlޜn>&IvRVOl%)Qkq/Ki8I0Jf9<Ձ=|^h%ujE׌_0nbsŵv 5`F{MXU8Ei~&؎A0&RRU>m*28Eo""ac|_k?%??us뵏Dp3Mݹez ynp;ګhj oʝ>;+g<*B<]/uu^YTZ[ <))aQDH0k$57ce*ҳ6RGȋT֦yNwHh #JځIL*u*RڜK 6/՗rQ˓(O=Ƌ4,I]OW)%Tџ-.;˳ŒX^+ȥ$k<-2d3O]#o͙fFee7P ^O=}PPj,/[X⮩+lݶN礑gi 3礎~!Q!'l9;6"&^  aLTnp efSlNoZVIOJ:b!y^'Ѹrtr/d0/mq=5[+]E#}V&?ئp`5j-X1\F` %P`ѫB(:؍HYFQ(f/A-r!7t$!9ٙ_1$8'>;7,*7=d'Lz$+Ik,MU)eXe958G65Ԗ}2|v}rey(,W\b쨯="(YKsH)`JIwUO*V Yh$[R+#yz *k,X=t Ce'tyJʪ>5Uj ZNeGmfp5 I^C[(e+*-8RM\UFrz\d[{FŶ=~OGl0b)\68n/\QZ֍k4BMgKZzjQw~PHY_Ѿ-T(˽K}kOڦw ZZ7m֞2ۓbw}][T{ױm+b1nƉE@O]y{lKKX}ϑeӇ?"YuUwcKGcbi_=QnJ^˲6tFEAO:af%0hs(E%P_CTf_8aHE)ۯKM*pپ؟mm4CgdhtϯX:_!.#>Z_v6b-fҵ6%bh51$KDYh qRM3YO)jUV6os)m6Lc^gugpdR䳺o[u8M4i97soE/uȪfu;xxQ/N*!ʰݿNstPMZXfk->HLwX4S?mi{NֶV7$a vk zTx=4.6`+0'wCnO̰M!`\sU,rV"9'ܩ0tSdX6kj_ǏƹŔjΐ $_Ӊj]K"`z{PMmV~Lb,q1VI[HiHf3M' #&L4`秋]c) -J*\d0t"U65F2h{*1h:[b)>ܾ)b] 44$%M/4Nm&tɪ,4R65J&9{0ĸX#Q^~WPS/O&I}-uTY.:j}ͺu^in2,Ӹ}+Q1t峙\u*RS %Og}hI_ ^,OWT)| {Z֪2H%NUu =眲ҟT1 msLIb!?Xhͮ)0<&j T[#ZZo+8E>6SI 6Mac~~UmlliQO-vOѲϢ)q/UD#`:Y-&xW):Eѣ-SjReeJZ_8pLkULswMZC"xa.:G@SG}HHa7$ m꾎⮆Rq!BsN1+6ռb,"<'E7.,]slK7f%UoBPl[UKR69eIBKA#ICT'dB<~bjlBf{rYFe*|aIq/crGy9ZlOitw&QM婧-p#6ujJ4 kUUM-YD:&6Ȯ*]!q 6dvx%ŴF'LKt#;@gÄӥz73}ʢ2XS.2i(C}BQv(rzRWLK4Vȴf;i&]ļnՈMEt'N2ӭWw %VR>Falc2‰!q!#=uit $w~PNJռF+,/:jw\MG_v *nDzt9*"Hα{>]V(9#dXN!OwpJGy)H!肗.fx6݀|#{ ]7xMo=%-hRLU Q$gye'n쫧AߢAy-]U ꢴG" K9NWq+y|Ecb( euzIMI'ΣUWF/M{lo(,j߱W5+ҚPS"AgOYiABcяM5VԢ4Ә< }FY9[)O\'1OdŦUD\V)J!=?}x>E(/`ԧ.R&[I#,KUE0/QR]G//s~KCe?Gⷊ-{׹P7;ߕC<4°"YQ-O-INNZΛ8z%TUhL!c|=u[ *dԛ_pUqa\؋V!F%Sꐋ<1dlJ~:ouphܵV[yGdR>ZʳA/OnE Nv_Z;Sgw8.-w! +"]ڗeWg ?pȆ9oEj4^=!Rh+k8jz44>"ieQ.m\2=1ya] !/U7l~K;Fman:IvbZ36峧v] ^:"8DTw% k=Y4,IY9T!Cq_"IC樾(ϝ:#߳?K^ xgITe^O=e Xn|F3_8ԝOyJc8.omc\dSs$}sn!A1"9F"f=|D%ʎ$^2]B,%99zk Q)@qgM18I&1=3n|;H5s\~ 5{c^xr,v5F]s#nEs\ttCO[_l+g<(CH;Y-FTjw\"No APmui#/x/yWX]EիYBq֗eoܹ.bJԝw3-21ۘD' 򪡶Itݦ:MkhHĚftiCM$KĿ8lO[%GN%z+:ٳE, Hi2SIQ>5]X h1IFy?eZqzYhsلh;`uG7N֧0뢜@E2Kƨ9IkeON Uʸum\W=T\e,=ef(l;Zl\>]7J?ae)k%Vt䨷ȥ=֝) /OuatF"odNאb3Q,0_-kԴWŵ]`Z[ؗn1˜x(.SY_.X,;0S_ W?YdP-Z4کhKrQ6oTВS;", bwJ4I݈_-"]}}RF43 FyI꺤5:\iUZw/(II+V4 i̽i̾QvP68YK-5 aر^t]DW-\l[B gp籾Eg9m#9)5T)H5ǝs+NZW,єu+xRԴBUHuM6S]c's|(F*r9oׯe~~)ƙ4Į B;TR0T՝j_ smni{^֩q^QIBYC02| 5ECERKX0wruN7PR$Y7aZmR=XkՅfEF]4vEo7u,{&]2-m/3-kqŕUxGj%\DKw<[I_k~]xt Kk !Pv4HѩrvԣuQvRX\]2eiFECYE@0 5 %CkBb~V@Xr) +;&wMs_R8׎Ә흆Gi,^1FY.aU4[K &5_^՞6NۥьtZ/ɣgXQ泇rMG5Uqp!ܡY@p"I!ckTJ.=2-%V_F*1ic:/RiLPFq l#EO%lnI4,*ր;zGʂJkAkR*zTTzƨź!mYn*K"UnhqPW%xzO~Fa⥻_sW4^wE6ȃC}`v&áٕ!pdzro^ fj|-z)BW '.tԄ5O^ӲaR/A1cm+[Uc{vv\pUpiԗʮ9n̪aB^y* ɣ1?\O3JV֡DkPY6\ۋ)WS-mbΕTiU+ R%]S>6\$L44sJٔ[-򤥩WZ)EFaұݢۖK,E[FXxl;W1ڳY_g^KڐKв. /.̼z!df\ILz Ң9V%s޲5!杚X稗Sg>`ZJA-)8T$j-&JLn'idcd[E y 3hH z! шxm}i]eY1*colY j?g-oD=E]G̻Og45` ˫=uĂd:ֽVPsm]} \[1x2ݽTT7-q[Jr<Ɠ|{Fc0nV%|I3y٭{+zlʸ̨O1>gȢ0F, ‰?5uBts2VYK 3#{%dkmA䢛U>Е@G,jKgN;YY^)vYMV]'orr~JlrꑓeY{+mʩ ҹB5De6 k-ۥ 0_BĎ'^1=JMG6eDsD. )dRq0j2I"XF%ijcVGէ͂vsbFMݿŦmRM\S̀U-۞^Uiq{&^48VjIL['i[r kp"s;"ү JrأC@tk ޴9(~*MS<9~ӺFyz9gjWʳNG!p 7 W(-ĉ~Uk^5)!%_؆ Rͱwk9(>Un#qȾt iOX% p[̞cSaƫr㿧C m\}>6Rj".L"Je;iڥqE.ꖩMZY=4OpU&A;Uc'!s<9nbn]Y{k_M%IYD.%S{_5Ox뀺?fl+yǼ$l#_X*7\hY9T4n=OTeO\3Ub7/lSOR(Gi{FȶvWV=ś^ *7uNJ)NzIS&uRTuAõ{<(.K7tZw&Jw.}~eZPum쨌{*xe)CWyI ߮΍a^nU^{{h{CO$)sW=J̾2}.3*zJZDŊ#ҭU_ ?s[ER}J@J2,]MYϙ[A=B1'CwPə9}&ramJb^B=]D&'_GwKIX]M5fX*hUQ,+2a6TE%}D#pH"xNE!Ěp4B Tj4LCHͣCEO ú(7r|`Nv񕇉%B= QZ3Bj v3HY ɑz3yO' [udR('5ç@y\HU= 2VU*%aFMH,U{]pwV3%q/Mz͉[xEyO*an}iDcUER)^s[cϩ]FSF#VO N!=籯ջ-~)iM*+>jR?jΑf?i#"M!+pxQhDh9HiBZE$E>Xt"̉aɣcǥe!"hOBXt̍zȐF9J(QLsHw@<;$vAXW9,@PiK $]9!EH7 6&"('-4'hDI5xN+a_JG4AR{'kdmsgb46yюH*~,#߿ujjZ7EG# )#pԛyNCP^ B%kv=7y-Rɭ V#̔,ɉ2>gZMXL"?0|ywe"%7~rW,G>6 Y{5QWk+6Q=X%Q;{7¦iII2VLdYJ&B^gתu^NW7ɗcYIRd2LL%OE"u'X&%k]XU>mW.)W;OeDfW\[)~1ҠoޕҦYQ5YL|+"lNqaDDRhApOSM2_ sWvŽQ{GeNSDEϮڔeebGWw?e:%4 Q$ĨˣB"Tͫ0Za\ky%jDCOR.=!K-k6'lyA,rƩ܊VY-)MRێk #J+\çQJp$}眝ԱȄB9H.-2\e C5(zQ\~FtaTYfƹ3袑R1#nz娪~(t{a)QiTx]6UFnUEBLS56{4M'.oXqR֊I16̸쐶Ԇ9 k~fⲊHV\X*$`kSCΗ)SB\ n1*qCY&bUS)qb]:)E-4[XXRT3*̥IfXdR.a]USXk)E4#ܪ* ,BU)B*}IzGަ!,(w BNݠ*ܥf X_1yf4?RA:Vk+ P>]cmZϞy\fQyu8YB>UBbajit%U] aK2[}rHgBh]H~IدҮ)L)#@]Pқ:J%wl|uԣב34šn6u :%h Ȫ}OY^4t,jVm3&˔iVFP2o1K10K3DpEE ;UaD"M"DUL]aJ re_/#߶Эt8V}֖qiN,~Wߥ;UCB!UU}(Z+TZ,&F'^hYU[oƘw=[\nrG>`iU!d?!5^6ryٵ_;U ~iNEW•|I\9T/[k"׾_Cr+ZU:&_{IR7ojK!v-a=fVvh=)?,) (-G3IITbdr@oP9l{FuR'TаQIw(on*Ph!]dzTш B@@bv}={O#j}.ky7Iqꭶ%Hf}u=u)o%δ m;$gHS6t{0K5_etockis.:-ITO۪ӓKQZ޻bT-B*TI, icULb!k:Q YccSS~Kj؂T XY; `ΙeL4UxB%/X%#XE)Atе r^aSjyRYkO 3vk$̯2CzHJzbEZ%.% }K 3TQ 4CKUOކ"9TXͰ+̬uM6)f S_$RlOS|BRUTMKFCC\Asd~2>O+Wl-=4QkƤNQB ;)ڽ(4[R8TJB(hH~rh 5,qnPA{VEghՙXR(* LTm$B>1R["eM_:X4|zQOP)fѤ9Wè+K N| tyJ)Gnk(mRjKK"IY(B6T%8yu ҘDŽ=SF$n"Z󼷭8uYjaݐyqB vpL!*ِ!zV,!0T7M:)/=m+sIp1$nJ{@3,Ы"~e!OSwV֕ai_"5!q]jڤU?X|Cz ~qilblEmGY7J5Tu*Ko!~TY>vIKG'i/Hw3P2 I"q&nRiEs!4mfU{.3}l/2Vj?^RYb2F!oJD+ٷ>#Ft+)ߦ*_!y7c}ɡר6M&uJ̫z%nGuxvȎ#K\nM! ؅/-7_8w} 'Cv:USHD;&Qa)Xg:0?1VX sM)I8=Xb,!3 1'غՕ~x@%Qn~j{FGEYaSmþg!pXSO欎KL z4쫺c]E9 :C*T4syKKh% |G&^¤'*|9`,gu{0hBgx7i\:x@x`1:ImJx4q7WMl+ U 0, 򘚳6)GbqdT(MG,'Rᧄ)/D/Z#`wR@;kJdR?KŜ?exC2PYGvȂS\@68l.\XDŚ$=L%P-'C,*؇C?s/ HuQI!j|O;)t5g"yρ%yb gYJN[οY^ʪI4CN)I~aeA_-u\5]7/E~30𬾇u0۽̙ҞsP+{Z7ɷX-;׷޳\ڿvh/ҚʤKs3r5(ȧwW `Rf]npsTrῑcj .%߂wԝWi.ާym5fT L>WuB- )P Jxj35eAWzO)q,] = $9i8N;CQ܇*`3 fqzВrQbſ'U#̛Y̡ ?YڊGfv?[QY7f9%2ʗcG"(+=pQ7?ک?9.m%hE3mL=URwa/AV]<ϖqw^L~ .4{KJ˨DžQx#|FA.T^DbT:NtyC4J⺱.iDo!ET5}))˸ r^V5U_EL+i5KG)Ml=V&QXZqk~z\+(׎%e{Nh7OK}޳kD 2 В ݕW[mp*W aZͣݯk&4U%rלlT$lǨ黮E^*群t<( a2!;dҍgGNfCY.]{v6u>vzɳd>FF@_n(Js]D9wjFk>CW6`isx_tIҿ'0$oizh.8C<ƀ1/)?3)+z4|繑fH1YAiYeoMv>m-ԠDCV>^ꃅ(Q1ėAtГ(يH ! !$ ͼcj= ɭw3@rB!YO]DDKj9{i΄=,CŸD ,1{)a67B$o! : #E骢)7!B>]Q4ZM&txnF#Ge ϶+rd /-ûCZTe]b5jhչYOgUNO5k۵YqNƅ.i7-+말ɜssٺw.#ݩ?迼~/]7-{RY7_T+*$QWy;XIO!NFj|E=biFaM.''BmEa>5Z>(s簥I&;FOzkf3 ِ_sPUI%tҔL; j< r]yi] al]f,.k:F5k_]MlbGID>POtUhPi晠t՟S+N] ޸)+D/:˷jr;I8NW!}6/Ty[K|rէmgP~2-(1(sD*˚eVq6Lq;&@;dƒE"<~iYNb[vrx39oE (ө-J}*g(&H+͑OFJ|͒'67Y}' ش,iSH.|=}Q$S-G`ž)=EΙQI :Ge1gy{SӺBU֤MY'*nF+bUԧԭkM&YщXm*l?!fXT 21K4?J&kJ4o= ucũRZ86 k_K fJk>֕Z,( FnU>fňzjD%X2 MF~[Q:h46t,ՊV(VTm/tSx{#[w@=mnNkΔe<|$A5;JI'jlkǣ*:7oZE!&퉹ߙĢr<ս߯W=eW-#̓1 $e.'”Fn.g⇆@C)sܦ!pt*Zu~3kJa\H#'IAU+jI5wK\XO'I^PT0V!oKڇ -`)wltDZq=Yªno,l]$bڕroTlC7مFPĒr +jM1XJ@\ޒWhT7Ae -TZo]PUZZ;rad$#W;TJ'e6NV&4KCdQ! Zn7ˆ}*➲ow>Q۰ߣrj:U(O.E1Y4Յe]HM! ԳD+:>E%{9e:#+1I6PBqdR舽Za,H.tzEH5 4}]ЦX C}LB0+a z Vڧu)2fa!w֦MY M@e$Rxc90re!Rj >)cMkbXv%}zD$V!_w4!Nֶy@p\5wHDR (3BOTwA\[uaGN:V9OSmZQwإh" ;JP7N)A0ӄIW⟶: fk'ޱ> D_PYϽfeDD zCFh>C~Kgƹt':ªQPq2 y^dnٽSIznO),g I9" 5|bdR1YUY#A8V/NPeGRקWtm/=&3'W`y9༝#LLS<2hyj_-N?؁!|~=~X_~bԎoMD#+&dn%hD)6$F]  帾 BT $RM!m5|r@H\ o`i;r^}jЭJ:Bஞ(.)j\-jE*}ȣ%^#?4佰b=w*a_SC \yg4p19XCrhךY1S)gF)%)}}J{r^PSkJ#էb5e=d19aߒ'merAwMuD(z:EXѝBH'MIw*)0#ag{;NQ^S/K#Σ;6gv q*F!'O._I Ac[5yjJQ z+6/E.-?-M[6 l+̣ŁzY:FHV>FQ5A|u~B'aw-Hq%yNcVeȾ*LŒ&=VGYPU[nEoơҊr9 XU=8ǰohlQlUԦIH Mê]vd ]HKOAaf=rxC{u=fg骈-Ҫb4~ !TtTi]#V5eCB)ӨXs/:oa$-`e:I\z#V^!;q7rTab*ثDgD@^yqw ] ~V$7rjmy"(]xPap^Tԍd֙|]zkQSBO[MFo{'$%TɬO1N Ū*ry1 9JHL].Ȍ_bmt73|/@x훳UVg&wd[%qG5mA Sܻ K&m*(&ւ{O¯곎tr ;6'/BxzYyat(%1kG.H]Kʰ,[SttxU TsYzN5=ܬݣU$z6UT:d%e9TdS,zK9vR~$kn OKE&MVu71C(FAܕ* "i"A`,m qxl3'$ &RU+:ICbG$PLBr+TR{ni[[s,n9?4,Ib hF*U:UxU},--QUe=AF M&:D. D+Je SԅK{(h]YA@WӢ3E^},cwNJ)MF2.;긏-]sDr G):ϵZ_Qv؃O}>~5A'3l+YEbx8mL<H(JSa;tlWV%qo)S˕]AYVay =xX_pl}ctr92կ VO)J܊N5M5ԕ(թG^_âĽ#f;Sd#~H0 訴OW_U'nش{'E;RU0T:j\!*XޜmlT*/)>WTաaI3,u,gv~S0Ϳ)a|~f+趨ޯN3c_MbR`-$= KnQm~t7vEN؄/~1%aQY.o~̯j4n$.okRྔSOբʁq=wǝy0R.]xwfR^m&_~"S,uPT^"%t& ˖I㋸#G$T ܫ]8%|0{4P>hJTHۊKsj,xaV< Bo ijQmQ-sk;aVgޗٸj%Ҧ ډLLn`;BhWW1m᳘0 0V<~O Ke~SE/9rVyB#ִ6wc 9xO7{XLqRJdtoOIMnTg19'Dx#hЧ7xzaYa? 5#]{+#6۬St}Yܮ uNeS̿P5F$o)7iM %ogZpt+=Z9 **⪯\1W䚐<ESҭ£MCR_Jy&YKm&{r住maoG9(eM, %5Ź9XT%|5 E'ޜ\")Ҹ(' [OmMX%F4%'1iM562ĪdRÌȆ|_ِ]cn)x/[S7ڦBm65[o*.y3S\y*MֱcssEk=xÿ,gѓwigw+ݴ>g"nk.K`~u cWRޟc xf-U@zCVSs\J1;[}kN-],0<3dU>xz Y"jBaB.&%MϾJY$)'Q[,YE Vm6~N@AC"V3Y#h~iY5ɶ `}QB,)֡7,7zV[V~(<ׁ,it͚Pؖ<7o5rqd\{E>i趈7iNTAX%1ՠuiY믎6C/KeGEG$)EhB ¼tJAqw 5U4HΣlP뒽,vXNhis\>ͱVCɦe{kue`@pu/͝L,M&f8.t~ ʀ/bU=CdU=R4_Ԅzʊ^Z2by+^Ey9Ch<]QgjqkYAg-(iϾ5fJⓤQ ,ؑ{+K.DZUhuZy[3vA?s(j6ȧvU^ԽTZCZa5irA=hңjh,עMdרW$SQ{'֤TR&6w ;Mu@&51K&)f>$y07L|8+ jlWlc$m(iHU8xɲߥ_s/^8YIzOܧ Xu:򡽤t#Utƿ&ssTC!l_cfկ!rtHn-2\"4j~$(*FU01%|U rǿxX)KHjª'ktĩjCxb˦Ie!Kkd?(:3W5%MlKcPc2VB#]9U\t TXG納Qšr҂bE4%.ۚ%JQ!5 ',TZ?37D<<(y7ތ^HDR^? !7\L<1^*.&")߭]Ļ=gpo}a`C‹' i{ٚ_)ޭ)t_!5yY ,[Gkô},ɿ}ུr!Tyhm3Nv%QN>c]ZaYh{ʒU,, 3>jطiǒ%7t-Z0جԣʞ7TC;襑kk:ӟa%ູ\)DotI_RMK0'mU5lUGu%'zDqH>:$kXRlP[]Jkvh1jj!VɪDv)_YJ[ե\X)KF cM+tM*Z$zϩ^N3ednWXs{ί¬!>w.JVtʹN~yE~Q21g™RFcqy.gf&Pj-fYMCwj}`ww>Mk}Zλ58슮2Yޜgzqҵ=SN&δDWV fH=>ܬj[e9e9 2@Av%{F%UWv]&K=Sc7eM-J;pMI sRő?Qm[ 9 ? %=Ddes| 'aꔊ=^pQ eSrexsrY1Hyg['t[K$;B¼5cOQ~)=W6=-qiAV|'qFX̠O=Z{)wQWu!4Ⱥ*Ϋ{P!t{%*e=íޖ0ljz4O:pou:;p}#]>oR'%yhxDJv!oR@?ty!&/HU-Kp}:\ρGz*?)].(I}Mҷ7 ] 61|Nū:ͳ{hUW]Vdpa'q[|k򐤬OrīmUXL՝9 Z=F@G9/YbLcGRkk s2aW1M@]sKmFZ/- Z!iѯkfMzsp`[;qLsXɖ~I#6V95G]B)C*C@Ps+ԽIFJ[ټ 8g2)8ZXz2j*0I d$:ZBD+sLs0AE{`D](N;2 _=y-U{ц(ٳ&R%|&bþEOP^3OhߣF씒Z5R-v#_#d7Jެ!t$a8(C.QK,{Kݘ|yqa*k0Uo'm]%&ֻ:v9]MV*\6dzRISST~(wabw]aQsvi/5o9Kf^Øja9 l:$%B[X=Q\EξU_cQMӷ :J+qd9j Dj&X'Щ)slI}I[.dHNUd}jk\΢St۾&T>S^2y5'%m}KS]*F"VƬ N|˻ O'Yq^E6@nC WNֲ)GN DE&˽QRLYӠ΋\'TF9m_t粅}HA{ jw;ZՆN쬏Fv+|)BSpy#N jJkq[veNP]A\*v0r±H-;"s^U"!iWp]`Ny_onɲtѢ;kX 5mixWǜB[Y4Jn#qQ #m+kq;Ou:#cC$z fx#Qj%ɏJ6*bz ԥ^Ȱ5M2*]]zl~& N_Ә 6]ԛ85PO{&^՜L`6mX&UF>\Vo^-@rPSVtu^a#~״dؿBV>g^f]/5m(mhr ZAF7~jӸ)Ċ!PVJ Hnp*چA-?[B0C@I7mQOGg9pDZìjel֕  鲞[_*MUIն/DxE)JCIzfK{4b1/Ƣ$s'08SKBB BH CŹA@|i0|FKڗ@FISl6ƣ쎺 wq* >S|wW]5G76DJ+)gRm4H[itgkj\ 5?;8}d ć)i5dot,Zen)`hUUu&x$k $e oYLQǫM_?Ykc.-oOe[Jeui-Eӿ쿆 hMg+9FU=Jcb.~5 &nLsaЅk!fĕ"~1^mm%uCZ>uvp(IV[l(#.%Je~J`$ݙ^܊< #O=5HM5;$T72 w}b嚶$N;5JRnԳlMM#:]S}fZ˴6^QJE۴([`onkpcnDz:1 1]mE')ޫ8~z6P wwm@k..R?:BnJ뮆Ga+(£GbQ(H >=uIpAF7 y*3J~SVCi j76N=woGgCKB7x 8$h:T aa󓦀`TS^ftr9]6en++9o%Z]SJ;HgI= k.~iD"X!Ԛ}'h;!G@~f(tR5AJd粊j)I#;x]*)xyΚ7JƢqJ0:1IBjR7$W7OU_P,SgOxqHVs'jh#,*9'1{DYHT]%X21LK1p €&D)֛(?ܡ G/Myr#ǺW$ V )v-v^ z`ʰ)zIWNdڊ"F*W_Ŀ5kx% ګPndUdy] llkorqDSaeeY}lل^]D\6AM|AJ/LYC9E5W*dz yf v.ֻ)޲r95$>>eoѴ)~޲'EyꅘCI&Qv][bi5iU6ujU?cP-Ϊ>~^WmB|,>ep8MpGMڣɳ>K)$4kRq cX)AJ"vyb%w^\Xlۨ&]}rv͙7 SVN(wVTyx8O1HXx )LsЯ7z Ꜧ#YTܥY#[ DնyH(;`)߼0K+q@RӆiP}By(y4 EoɁoSM;{'|OIG}k۳Эn.xG{tzޯ̃DyXLK2e5mh~2aSL˘j\zOehUֹU^Y29C@lu%bX7Q_̈́[KzԱ1nՕmmKsLˁiA~ [TWDߡmSe:C>揓 e1,61"_ªÚ&WEXWģ.*Oo+le^i^"UƿEY~NfgʝJB4Ub*Bt.{Ӫr,r]մVUuM1# xrz|e5b-f80N|veYvXcXVZ%}oI^3~@VVbw_>]TˌCeExN~{DNl^(ZA^4)^% f`z:Nsq_2H|+”b܅)QZKUHsSTzHoNj"p˕F̸gD6L2],=(/pٳy9jΛĆ1*;J*#O}x^cf{͏7 /gifxt\K^@ͺ,Mn WsWMA<~kpFҪϩ >7RSE& =-ZmuѠGUEmAy2RYwɀ;* yzyOZl#|MNP4o픩N$1rhBO콩:F3jȈxgF]HH z*䠠T+ nPn{[ߡ Bz&X־G8Kc濆!Ate£lzV.2e\}!H|CmVI"VgKa$&Z sqwy!d4W[%}qijZª?4?\vaV4CV)uu]Uzeziuݕg{uo60žTZ)5>qx<ǫGL$vCn]`ӵONjVq1,Z/mWiq~ Oh8m Ǝ|*#D3-Z5eFevIٺ$3Kf.kN;e/* A?WY:jRg^rQ_s&%ne/ ;y-%^&PY}#v %׍MtfŪKǤ闫ܡuOIl_KئCDYuPPr&\4W(CYWqKUmX"K'VukwB=eG*e0Z4KG_^~)v-u^4^:^!zX]{a/ܿ=lnz\w"Q, Rbtl1\5ֈ`%tg)]q&ą0lmYษ6E4fUB!ख़VB;vI%KhpoZRtXGpیfm\ygrڵҴ`Y.SA!ʿ"tW4xPslZ[v4/ECX`Gu.Myxe J}2N)>\Ty L3ԟ u<ŵB!+䩫!X2k ^"DQHA(nh3~UQq ?"͝Z][U&;hRUJPB!ZKhek>[8$Ғ IR!(EĺĤZiV,׮ *hPCKl k,^:M*bu[ B^C ;ZB %{ `R AuΟ2TrKaE*,!UoTҺhJ-mq. \BD_%cXRg uRBed>&V8G*U0֘0ho\C%IC@5D)*g 35+i) !8D%I fKV54Z5hˡvP—x, *XA4bǺB)x)xBUFb)dP8YERTU%SU|F*5"e-QJJIA"U 3RӐ֓IVHE!MSFuqU:'ϐ CG,P*TU$"*2-!AE!˫D$PR6M慥 *Җq+JxhIJ0 S$ZT6TUZ"MݒW,$RhHcrԸI0Hx_"EqRU$aU;Զ)j 4e4dBRPpZL1M)'");T&()4E +B0 LMYT0*d~hTԒQ XEfƪ A&Yk R2Rlˉ E4tJ'-b B()&D"A 1M ShKX88DRijB)XRWYIޅT0 ]a-V.Q^)*!$ӄU()"Y4V1hj˴D=t4RͱzЍ rSJSD#9Gݶ=qaRa[:ՔjkVd7VaQgn VW|!Nyꬆ:lO|q~]4l;86([Nߏ%8е 5U# =$uA@{H+H pyYr0NkX-:|ebSЈ)emebzI6.+J%Cհ}e ^'2ԈXcG}üE2ɳ,-}dY' `gZi܁MNp%%7[Sy[ڦ]2MhׅmUǠY鰈/}U;$kUIo;|ϩz8O1Cu/1PkiIԐ ]j8q1h^l/I_/`;bơ$%#%B8okM%*}}z:Fl+7dЈH NNadGٳrQM4PRjPc%eF1{y8I:[FMTFj{JBUzbJm[6:mj !wL( > >YW?<4/ [r5{{џZ]ij6Oyn"p-˳wA-oy؜ڛGl"U4"N{l>e?|Z6ˣ\tr**;Ov^2SLߔQ kͲQ{_呃DЕgʽԟ^G~۠D |Pұ @&S'b|ϱMh-O7UJ=-|Mpqiy%pY̳&iZrs Ͻdsm)7ZTkbaoE:=3(jԨUMل]e#T} ; YZg^x MQ˩/vuL"|jtVTuGf+D=Ӡϸ,✰lgry^b-E;5( s0,GUef Jf%X[,ʉfO#.^8m2K^'$|N$>?nQSLEk#HUg?\d7IpSQJ{Ƒ:xьy9r#8@̗yx}_ΫQK5eq-bzJEWr2*;Z&V=C*u>9'd*µͯKDFKFr%Ruo?`Ke:uSYՊB5phW[wmBHs|5Wvˋ AVlV sCxU(_ CEx:&JPDu5&쇧cM|x #Mi\sr;f_n-'ZweǓmvD+ςi>_w|O}n\uoebܗ1CУ"HɊ )yI|_o PO$U% $v`tqI :/iK@"u~ ([frqUEg ktO/¼g})NKZm|m'NƸlN')okoVH_jO:fy2.1sdU6bpS% z;CK- s[!|jfpQv!ͲDsajXd'nb\.`԰ܮ RƐn4f̫ܺ~L~ wg+;m:x'oL?Kƣ>|b/P;yM7.ug{y&_NqS^Eg-ӲoKz rvW֑wp?fAxbӵfP .L1Dz }+-~YtvI`ضQGrZ7;Ƚ|jZhvfK4ŸJX'7%E| gϧj{;zSUD(Qg=6IVV!'68/i!-쪩*HeT%P~U66 TWY\Q-Gu)+l ?'ァ'=!/Q:k UyxZ_-o%Sy{i"AgήJ>DM,N_["nU.AL'cJ9kHavT|L aTER{2fxjז8/͢X^hE{R,-Z18K+%fQNr3U%b/Ĩ= ^h;OS |$V=MKQu jE#3 'c9#8lE Lo$#`MC}5`fXa{nN\V+sF]$y!qːġ.D,- ~q5)8&>3<`iU|G4Y ֋qV̞b4^_<̡$JTrZ:_ij*MI ڡG'L/E/H:&Hb5f5s'5|[IWCP`4orTN$mp(x2JrƖg"( (ESGrM=(9iYPZTF"gMWZ*ܰP^Su֒ASR7 B9AvJc`)T>~ IBMvG.h$R+N']4~L}, 'E0©Wz-R¶,q'8Y(8Hμ, 1fUWԧq!%"| =W '̣1k.hZHtWU)7֘O]t*)- wh/yME4qQ-REtS&hN)ʈ%/9~w:&F|ms^nW$T2tkr|sǣUE+J~ɟH.ki~e~7Vƿh2mNF->sfmL{npWcAqrVkVw9W5G-׎/q=r;+`MCR4ge(eAriqLu q @75wޝ==e {]t"8+i$zMZWp}i/zV|Vr9][ccs"gzFzb2LRߟ^Dc?Ǒn5NRkR/C.+6@9BD!R %=B=[PJuG4) (ß/qگ{!7>Ե pMVocϐ͊'hl<]þzhl|rݵbw;)CaϵơKFVA`qV5!w|AND(KbQY+rKʡ(WyAgYҔK!D a:O,kG hl2I hV)E(éN;|p:ECjE1|cPӢ2r+KO5D-E_T,4O- 8!]K\FڊLo6hi^4\?WpLomЦǰ-#P(rQmM'QDj-+ wdV)TU0+f ^^`R괆 g~U/?-uֹI!.3|ޡݵJ\ސ P)+Q}.lп"Y-Kp_D`W3}k0/U(Mݚ*߅Nm˴M}M?;=j#{zVZ7yarW5sLKB!L| [۔ QcBi'{ɇ݆y( M~;RQ:ڝmA9̆WHzѤ%Gȵ3ȗB( z!Қ䚡^3zYKZlqfv}WS?wIt_2~ l?%x+ZIW WN]V Χ6y=g=\iX"YUh0JU^0ՒV1Ů*z%sV5Pf yV; #DP1#wifc74I^T~ϽNS[jݘj0Ͳelxj'Ӛ_ry-Ƣ Vj'c ?4,Ui|lJE~>Q;=_"W۾MVEfP8~: [XOkI.:fOQ"51SgSva(єAu8r2 >-!_^Qt@&WM3.Me'"v=A}&ʁnJT(0tLwa:|;Cܹ6c~<ʞfhQcXsݶu`ޫrqJU^kޏ0E)fz=gF4QLŨ;(?bJ(`V>2FQ`ļ@{$ '1DVp}tAwg'lzP5uDAf~<+Z.Gz%}LV[t%ݧe|i^OIY*b7vxY]e&-&˪澡?'>V~ttεF}hyknY% V5nU:J,q"Ԩ 6Y&[U x't2;YK_JzJbM} 6IB-3X+ʌ6ρڦ*)RNi"Z|EPgTu-YT:żřbS,qocmf;XSSLSgi41y[KRMq1QQ,jURbWf]Q;Q{Nm̞hx N4[Tp5Mߺe^;Ed`jYcObvշb6IKbE%(ЙY;BZ=g!sPoEx+%5Um727:aKcmő) bZV^!ݷG~T76l =UmM5*-$b"*겊)YM6h^㢲3&6JxNtb5¹Kۥ&]^+2Y]mm(/@RiFKȯΥ3/ZMXbE#֣[^.u/J)$nЌHxl{z* Mhy?֣<|KWG}, *mV !idnLScfQ驑K;5̣Tn}S$zوa5˦Ⱓ!<21"p QQpu^*z'h]kt>TL?$-%Օ!AbPEYv&;3E%ź`a#:|IGԜ/I  U8-j"ThJZS6s_ gJ,5B'mzc2Gh P\;iqc^˗mZ^p)b!cʥ={{rUz[7Sd'_*PͦSsj(bfUZe%H@B)(V5YdQ4hIz6al5i#Cr큄]UϿU/i-!WyUL0IaZIz볢:тmnUW֙6tkХ[hx4Ki64G 1hm;^(fvص ]V +ֹƸǴ&JU zӝBr mSgĮmJ?a\u+BڢDQ k_U<PduuիSeCϟi!d=+9M.ԧW D!"s^hKr"}Y4]IT>dRƀ#=D$õ?YL\k؍qc5LbzE <% q?MvC`Bb4$fʓvHtFεM8?(IGV}Ljg bK0AV͍fsѕI\m0M4|m9aGn=&զ'nc}@ Y&,8#`BH&U70vҊzh³ϣ8ϗ ߞwkN^}2ij+sVI$ju0 {Ǵm]Cp֛SedݢFV1USԓiCҨ+D.UPT5Q[j_kjRTCzUAxU= $U "+ "OmC*մK正ژc+4FE>ɡWo-U1MɢޙTYw=D5QL(dF >(falX$r' G C{fjC*Pʻ3Xٜ jJCEeeW9G2{ˬR*oǭYwUa"S!CAso-qizۢU3Hmyb-乷t%Īڟ*R`.aPTlےzp.4'8wFe7=ޱ30á!d:6406x}ǷC'"S4du=ONjZdb=QR(:-\"*)PQR35IlHuh!WU;_;Un)Aqaס,"T{l8[b2)L>Lj.-W7^RY``,q>VޱaU !z+cnRJZT}i9y)Oc.h`$*ZwYr+{u9g9uj< {ln9sI M)CFB!Ҫna\<+_ :堪W\JiN)u -Y,qHkX}%ag]Kw^k=*[oz {qXG=GHƖ1ʍ=I70+-]*h\r [{c+KHE4r̮ʮ%/jZcUV!oMX0(b؄O\G?W&҆xbFSPVStuCtH]ZwƮ$ŷy|k䳢2f*^NYr%E6#p֬nJJWsDZ[bY~Ȧ:lk&'5{ Z![̾13h]0)}ZQVI:l//Ed=)=ZeէHZNcNW%׹튊95zc홸D>)x{~ģZi9Ǹ*CJ6/CGt' 'MTc9ȽVA|;x̒XfOx*7|ϙP<{es eAB;65 t!+q"4}vqdE3z\@ A|9&;J ~ H5ve6il!cלρ"nlfE- P g UQv=-=!n{̒ )ǨBȉ/?XI$'TIܝϿ&SRML%hYh}G/6dtv-ځ?quȈ؄W<>X] '= o2+6)_O=]6 .]VQ6Or0rd*#(g`o6yceQH)dPMfta*$A_~U`O&9p\f]W~ETsb?J+G.ÿ7滝=Sk7ҿ9|Re[6]೾{6)fe6lU$9lmUbQuQN1&, z2?-?4wҐ]{NGTˮzu-PHg|m_NaFL bѿT6Q`ui/4琔RGrQy' #LR0܎ '3 E]Om{t鐫<9>o $vcKmĊ.gokuV+I"XnG١'}}]9"drH}X'ؾXl HgX}(ߢ1e-]Jw#f=hT99h׮i[&i)mVi*z_wɽ7~ dWlWw0ig_n=k{HԂƻӸCruiỎs8\e_*Mt+h+u`)!TYŽQU#)ws|TNUUsbdtκe]zSZ9Sf42eYy3!8}H3.UUEuqS=;* >$&,ʛ0%_B%PaXG)7۵Ui3KޛK~ @|-jVМ)۰:DUdݪ{̧JSړCߓ75I$hHJa^QλPIT_Vfz QTDt>L5adm9cRO9; 8[O=7fuphκ/6o:3'ȭ0԰u5[TJdF(O@Batvm"2vg<~$s:]Φm2nHR}I0*T%moW[:$#K~ġ{ t.eNi`F\fq`c+[zX-y_k^|6m5CWt-B$IcM$v,|ؗRM_'k.`E=e>znCMީ֣E+ af|2EPW-+n,eՊV[E$$[C'/O7~)9rwG~+?^±b{6OS2l[S%ר-}O1I%[ uKP y{랴$[qqG5\UA4}L=4z7V!@rMmo9_(lSxWL}2Ic^6d*mZQ~QksצOO\e Ɔ$hyuE- |yFS^:~1uBNXw޲;-,ːYtVzۥКjC0 XYWUҺՓE]qTt.|_Zw^7k +*r M!|M#K[%M23nlb]ã.i9|yKfꢐqIS: e@Q EbtHo$Mjڌ~lJ{8XO\va.()0ǽE\7_umΣl+#:J[h'+xTeV^YԵ.U3O{&V@3xZwpPx+z+ vkC<7%L˻4'yLUSօSNm:1'%xO=kr9*]l]=9K:wg\Wjc9|&z]e;⻯LX|t rʦUWH)e xD-onǦS!\'b\j9^5@bgaUgYYM`Ea^]#*;kXV՝lv)KO>]tQ+AD9Ή†ֽ(<!1aI9{;I {~ ?pezo(Wtl}D$%zDy93MC"PSeZH6"dr%mm$*  |OqP ?#v0yGLNJX$9=![XGb| {INiȧK*BqLtԶ`"tSSN=ų!ΊE*0oX Sid)PqEkTΐW%-_`' ~oXbQk|^E=ߔCG\e StDZI"Wg%{HF-rNFzA[g]4Q-FpѶ+K@2v,'QIIT}ne2L:%8`u c(~Tt]ъA' Cמ)tT^qm&Xn$U֑$]$C@taP%"><suJxgtjFMV;*#.$rl\xgm)pΊJZF*۾V-=*I=Ҹ}$C8 [Kֽ?]3t;2IY?osڕ5c] /f!uװ˛t^7bV >ʹOkYtʶk=L8I$$7or9)qU/&in=\Z 鲈NIEnRa",mtܩm{K$Ue%r}VgL€BH4ʪ E(F@RSGNhX]Ƴׇ>D$ͩe=eY!?K*ƮrKXj d)\[~28̚AU%TZBL+T,۵IR]DvڬG~dImS):d dm‚6I,^kSc/5Nyb5wVKyĠ9BUfYadMGʷ^ECb4l#VVܿ^MM;uxGhNYp}jTYYA+O@?5Қ `ԯ}ḴU/q?6#ZTIB F_12ƶ`6i^نoNE2=r٢dZQ%[pYFY77)6c:r4GI? Y4"ž QRBڔ*+XXn úxz#T*o*@ ¾Ț 5蔤OhѺMORIQ0R%s2Lbz`)<=fI 6CkLͿ$VWGH w7n->AZm̑PT=) W&Q(tj˽%9k,=Qk$1o?˿dnMzʩ#m^&dz+Ic jgִ!_ѥcJss^(3՘e)iIlm|N<~Pes"ϗauwˎzm>rO,\Կ"aIԑ3˞|*5KՐV&[B`iuw U4\E)NY-~YTe[Сk1qdT *ܿH.ųKlVi"OGYJ2z}"j;^9%&VS~E9[ecN(z*%RT\2lȣ:ì#ka ,^{ng[MO ~\AKLK;By (XWa-m=8$=e:=ޮĿzV|S _ ¿g殌B{o:ȴw=EXҴ_!.,qﹴ*YF e2%W'δ>vѴlZwց~4K4%=WteV+KVJ8)B-Vюr+S[;Dhv[ rD`<ő?d_ 3~sp٦ l5SQmέ}-ZQB>|r-.h[Cj^:OW1()hBU_|O9hX׶,"pK}Kz>p#CUZUc9=?9`di8*!H4f$Î'h-'|i" 6ARٖo2AY| ϩe]DJ$SҬM"_P0)͕hιORcjMd6qvT)$8i bugj^!\)y:脨Dc!;ZE54Ay,+ q9ɍ+#o|Ը,23@09)v 'fjY;E}K?b'bcQ%1^k㤽ۗLsR?cn!ğ%ԍe =2HM|{ʗmdH@F`_SEWPG)ĭ$ uCgvBOBu/:JYPN8Dpv҅9[e0˶slY{OpWnZ|mMG:;P7zdPѮҙŧ]Ld0K6_D' {J~.Jfa[p5MN߂˲+n9\T--;d7'M[q" :Aan>3$y@|-s^ Bo'x]KtYeNVnH#x&)\8U7"9J-fF5*0)8D5[m nMQZsRPڦ31і?%9u.k St9WT Sȗ y ym:e`'lUm+*rOElxiV`s {;jumoqG}IvbEDtPUH"| 1u@hH7&ET()[ Ql5b>p p²ie!Xw PuD o,iƽ2)0ap_7g!ӥS˓JY?NZ%mY4Q8@@'f]笏VO^ʂ?^Udέ5uE w]T XLUw,3 s+uY9|o^_j-]Sf#}7U-'LzֆP=$b=hepRpۚ-IȲ E7'I`}o !8>O)_,nfь#:ulN6T?.Ͱn16I{zlݡ>:v?i^8%Haڥ!6h|4j>ݹH1'UQ2)*.,21ľI^Ux)S$!/nYoiks\Ҩ CyK)*kJ?@[Twq~͠OvQ X<0 #Y%|rx$[KOYTs2JZUiR+R zܿE Ipgh^FI@NYY3MkR˲IbBnR9$#ڧ1{ 3m*;EV=Biu3ѼjMuJ-My)v-zjo cNuu@{GIIv z:* _}.f`_ԇUkjCBVbyLVL%UTLUӔ-{emW0J[uO93(M&HȆ! am.)&"< ۯZW%qq@\SU?D7KL.6v+Uǐoz{S[uʠ"CЪ~@o)9}Y)/aPޫ"m'! ˽ lAQzJj7ĢR:|, mZVX+SП'#[ 9 [$v>56EmWJԞUÝLPٴJ!r96e\.t(tbb%^f71\Wd=vHc=jk\|*'YFBFUNNW-a -]W e c&T%bdA*6uEI6LNZӝ%g2YqR'b4riS_H9OGY=/lsTd8]}߸ľ75suI{?Y'uv㱊tHclN,IѾ mʌ!:ڠ RH#)k}Q,O1*8& 9Du[l-:MKmI)N#(Dž Wz;Rd'0a6o4zt'5-C]jƥ jT"<Ʃn Qa漶}5-ϼM7.2zIvz` 6A 5kaR"{ZO[IPt м=}Xff:UW{e`-U|~îB#gy7>Qv,wyC>ɶ%)y1rz &M\Kᬶ0 AzI;i>=}m7(&`D5TBXv 7vV7aqzy_,]T_R@  )es WMq\VM^LJ7G5ށ~+ˆ@Jv!;0' {SeDǃЋެ߉(`.טdwxE~f;ǵw+|Bbtryˑnu9Os:欒vѯ(崭rIW36("{viXL?w$txdFE*~kW6jj2d t1 ! ȌG>H'<8PRh󇸌 3+;*/+ DZMdX۬?QHHk#"vʈ"yojƼFN8K6$DEmH)(L$YY% Z`-{O*a ,LZ)[%~Ăn3dU1I$,ﴋrWGEh"%IXT%Tp t5$Fe>P$/۬>z 2sD?9=pci?cUĕȇXt,mHҧK5o2FxiJj;K*OTY;]p&٩NVrϞod7 nivG,7'JaaN3(|/)E|ID/Q0SP6^,4\{ﲉږ~թ5[GG98TEFa% DL/IW\% 's&?RR̮5țhw@J|ƢDMJ9-g ZT(pe%҆z_WV%:L~Ɲ~-w^ӴA}𲒊D9K_CU7@CyDҗ2)OꅐibR>^{6eV3`Eޥ!B*v %XUM9&jia !%N9JءA<]zz6{.㪳d3go!U,^s衜"j̋bثh\sbJ_^9EQjWZkCv/AqOJ̦ɣiv&1SCϑ^u9DLΑKP%UzO;d5Ng/r( ̛UEnx txk Boi ȊžklErؾAǡ(m޺jRZrCnz6/=mR[S޶r<֢SG1{u~+qDjj8%tUW2LġN 'q:׿9BJJTи]@X\j#oMp;Ai[7.V+sؖ/fm[2?X^85"v*VrMJ&sIYU5dZ5kBB}zR0KSn?ۭqȧm?cjorKXD! Cd}: cϭ++Lh{ǰ?/\I`aX.̰JWxj.I@Iƪx>F#ǩr؉rhKWƦ2ȓpzn!h}l2Tױ恧:ߌ^/'`Y ÷K76Ic֡Yfnn%{?J)C_M2m-BCL#҇ЅMU")F79OuwiBBZ ۚžh嚪Nm'?m\ZYAUWh s cAztNbНX5e eG*&–PUBi!$)GSD"UcUZT(\bRp%TR.4Ѕxo=5;{JtM쮫Jyl26&%lJ\zHGRX)LfM)hpRVb)Gŭ>VtB44iY'\ͥ^%H84%R#;C@kkljm]Bp ukOU<U֪[0LE*ɤJ< h~Ĵ4%]oQM-uBz7SKY1MЄ~V"psR!Cjʪ)=TQTd7$)3bʒR$%Ҙ]c2u\y ~(RӨDJ pU|- i+&;vd"SL4wՈg|lX;dICTӤ?]j vضVE NSK! !"!3ԹӔpy\ۆ8FK_\RPo2ՕJno!b0U*ܚNuvPQM rjBYka q,=RBi /YKY| <PcUҮM, nFV9 {C bBCNn-0\O)"'ժsVMD PxU)ejZ~*6}XSeAsBP4kʛ4 tRR5WȥDZޚFA89CR- !״ُQ)Xc;a܎B"X&!&0۵ O~&DQcA["Sg),M@yF2@#ڰ( uMTFL򜵾iȡ 4*ɼ[=DSUx oBdiiª˲ԃؘiߛՐ- ko]Rv-ԓqdF,9H `HrzjGŌBuj8I)AEO^F ~isزUz, ~ؚMX,ӓ؊8haYMZ*XJ[%_CY(P~=ҕt\$-BūLM/Hgn@G#X) 7 uDxǰ_e^$٤|z&fGKZ%⹬ھ"%yo:e)xE@D_=2L;|̾J*2VZ =}~ͻda8'}u٤SVdOUdUI5 2v6l~-QRfsHʣ:d%z-1bR"E[TOCj)O%gU);( P( ~KR1Unaʖ=E 7d-ҒQ ?e_a״NML[Ly٧$ЙAζ_X;Gr!{RǪl)/ɿx)Nx+n)bu=H[U 0Ϣ)!&_3kpJẹMr@. ڐ5>9jV欅sS%}TkVWn[ @±"8eI\Sԕ$4:&Q4(Ptg=Z{(Xwk"r8IؓNP{h' L [+I{-r">q[顢d8fxşڗx7ؚ#ϬI2y[−?G|*0=&̺bRl.0֘>J }Gc6/@D)<|O^fq=G(lN7Z1Ai~1=d vZ% 6L rJSp8 zoh5J\e]U-k^Gt'2qymqCj^~꺠6%V݄ݫ[w'j9(I>F_Z:)ɧ]ҭ -ēH6b]Z[CSЦ]|D:{h_s1 54* #_)V Z'~Q&]$ ÚUA{\fDI~#6hkɖMJ0iJ]fI_jn*-O$:VcdZG®HԻ<2^RVg-J+HpZwkAЅ=6}+A,1Z7#mxc@zΤ6u E+kb৺OWCKJYIrBb%a[Q,1A7~ ʺz:}_T˲*Ϧ-wRWb+ |ٍF*8Sa =Ȼs_^ 2yߝVgvS^YׯԔVf!ɨ()z\5$ţ3*:&Pe|┫<YZnjk(CuAOc{xͣL0Bby*9wxU[T7~Jc^td:T[vǧN;CAyɆt)GW*!сyv$t/)%1{ ra7lXW_}?F/X(&>6Ly X ƤvvwREE!'=3[[b, !97yy ֡#)𤋮]⎞?tJ$ %E|Dph2 l(/,K=fn6JRZ ;(WѯbE/Uf=\5Rf!Y}D{H׀8<89HrBo&xYaws&AoPtMrB,nЊCG׌xu$L&koǸrW#c|K}?cZ&0u~e녧`]1md [G_S~=HlӷWV%N$ ;gpИ3:𸝚#Ȏ寪Beg\rQx~/ֲ7y  # /]ߛ9 "'(׵OSQ~jdcԛF;GβʎUi};n',7}[}d%uL~-ۤ4KY]Rt.,ӒH {lcY%Ct4>{Mϑ2J¶%q ZvV݋vQh3#R>F`)')ʶ׭ Kά0u][lQ,g%2*i2mΑ=ǝj5ysckĶ +㔤{TyύuXv.nj=k03!ݕeSzl1z 9*` 89R|J~ŒYơ"7*+mЎd|,4n &VMpeǬmoM}gFħI|3".s;v41ahwls`GẎM:t=5IeURۤzrP}ugY%,jhJ+/athңVYvq6bS̓WYd{bANy,/)?6|֞e%BRw?F2ABو`3*Jʿ3uVxc]e;Yث#)wZVU |ʼn5oQ;75CH}{VD;(~]g7H!.EP$Qل4˨xtb2."G/: )tJP-#VTF}Ziv ɕ&Tn*2,{m i|,Qm+YLUnBYNR"J۪YbF|oK)ʴUᴕ۔ª-ntIѮ&A*˶_掝59+n+ta?ׄ&F6]M[^3Eٻ1u=``eXuUE d˜GJ: ZjRJ9*f#?2۪Xv'.4l̻V++fSMf:lsKnmݑxr͙ym>vԱ/Rg74XM-úY? k-9It?__"nX~9 |/)+Դ%=Y-wiC|KZųWkmn#@W:m\y "< .I^d]fVuq0),p-$]U$.ô4`V,ԅ&!HR) BC!U 2Bag3?'IT|1(me!^1§LHEl,*l\ȷ~и^Sev-,?W{Xvm*ޒT_,֒f.y? m`)+/UКĒe>gb-B^Uɠ覛NTR<~PP\=M>JE*lJ%NMNd'gII_n^Gib6)G-~mZ]?Qmml ktXFlkM{6漧\:H!le"}{Pjk~.VƱrϝ>ٳi?{>(Ӑ77.+g|94ú݊zܤYbѹ j԰ -^ǜBý >,,*1i"m ,<+R"1[F`nՇƍH;i69YgO,Vrɽԥ;S&h+uWF-waWȴ:+.sV&Z' Эʪ鴏ky9]6SBOQ.ơ&*ۨR؛ nݦŵ 'nOb:ڌjP5]OU$B%x&9Ip2]L瘪)  L`"$$jkD6E}sٷַ6o:p'aRI5+4`C5,qKYb7O\~4#Px|/U'(ds)ʲK{6g}~R5.ӯK?ag\^*YIzNӒOwz3LR 4\d;TU#Qd(Jj*Ȥ:w41AkRʱӗ5 %(:)xUPG=6A9Mu@_Msp@SO[f^B F7[0j ̟:oE+>:x$gTYBd)5RF'+ 1@ri˵,RțaNiˍpncx\~%]h6{lyo-_xBe9:+A-mQkI|-^UYW2F5샀 <3 ޷Jbx(,CS!?Ni~=5:a"Т&w hRB"y{sxūpb4E0-B$u wL-%P@,p*'xIf!lĠTՅlc/wP7xɵq[y/wkWU,z 9q+"]_~Q:BY %BQM,Aꭍɹ٩TաE"!yhTU_)HM5 =;}Rv+s2SUs4J7Ƭ җFejPu]-#^v >Z_15]A>Yc&RD}qf t/On־9Kje{Mzr!|4NFyo7_-yK 9mod [IQum." :wքT7Hd_Cvm^Tdjˡdb"(j| -2ȕ+<>U\Td[!ANrRi4 %qQ^StSVX"!dO":̀Uۯ&vWfVU5z`SkڈTI*"7Xp?z4I6Bȴ<=yn]Pi쪰!\c9NYg z!BR5zh\nneϯI*뺚-{Rv[ QtF)zJ"?JKT5UUt€-5H]FPVt\5F8bo @}l97rcI\NY(Zܰ|dY K'+bUgV {xʢzJZ@N_ڐZ])gq>$s˙X{ȝU@y#'RcqJ]l3e0f(f]}iӆs=E P߻5=vee=$ץԶS^R]q_i9(owyo1&[XYǨ!:/+纒iط!,>]{==~Amٶ JU:^:X tTqnum~[վ " aX =$ݔ]k@P }r?y0> oi3\Ck;!TYHmDM ɘm\{opr>g|kluS +H1V={⢉qR)՛gFmW67f zcJG-idkl=ZLG^Y ;PoqQeWZť*˨c3\פwz=G;Br*{H3M)?]=X^|=5#c<)ܸ)+j#jɅu?F. B/Aiai^kY$gH7#=iw+L57Ows*۴=.isWEeNٚ.ѴT'M+܄>$%G_}lzWU8F 6F&gֵ>{m_ 1XQY+ZmS&UD1Io]2DAM(1iR볬GwTnYUA_- Η>DH֙౸K?F ԌE6/UK/*2ƕd+ S6*IA$'oPܢl2Y?SGr01 y)%Fض}bK0L<7gca 6+njRv?epΥrl&)ރ4lz%5{=f`ߥzsº)RЭ) ] :ѐx#[E #B}LXʢu%Mb*TF Ex'"1 V->/PUZ2iѼڸ\;vsj[=ԦwKJiDE)?Kc۪+Ts xO܃Xà_ho:¼lVMFUO!Ǿ $&Z qHC^gspĖ0 SBgoXFN4%tAq .:6W_&OҎacJE.7v4NWTɡ:ۧ;hgVSՔmfA_hմ;Bog(qlw7D_\c&\qq1QNBFLըpN+[:*}'Zжx įZWΖ rĿ3 BJǬB8/LDرw[F}a(SBQGv[-ǀ vѸ}Z߼5X rtܗ7o}< kmn4W2 (Z\L}[3=i( P{ʌ$jܥpRWM]Y]A6\hmfvoj?W[Ji kZBsmiKf5[,JUW& TgM $+XS;rU յ Kt }1%5t\0(%vA cW_ =&4핽eQ^eZ6W>zi0˪Xѵ;7̩vLSva.6E K&]ĵ؛?&b2ZrhPO85 lUkeNaJF T:Rϕ/=&]V* b¥M /_UN)v3$øGBBz5&3\\P) 89Sicؗc:% V!0gUyY*бz]WN_M^2% ] J͑)^%eږ3+2$.C4@r+ީ?J NAk8n18F!6? ϐl(.ĶDbƳkCmS<$Lz%%/}餛&/nKk 2:餘̜0 Nqa e羣J'.0ԿFlԤ~4iOX^MWOpXֆ9D[eŏpzef  غ^3&3sHctdEJg3wbj U9kvF36cդ_2ڲ  ^3 WWlꑌ;i Y,YLܖY%(XyRa 5Oa<+#e`w ئ5h}&چ<-%fF/m۟XOE.J4cedX&eCAOݒuQi)@ MsSd5etD-'lHě|Qi"AdUXOnwdB̟(*֗YZz.(*֯f_}Fl")Pi#ȟz 5ܡvzA Kz.F^V c\%e`G(*z2;ɆEqUkkqGP7F۬%Rƽ=FroE#^Vi/ rN&ӍS~M2;ppk)?#MB[1n*Z1{!B&iW2@Z*`pT?qP jB.hM.8Y]X5.ƒaOvQ#@jݫY2Ѯ2{"UDQD5NuGQřT^چ WisFc~-2<Gde!Jpva! m,cKѵyB;JB~o(Q5#LDX-fF4BW4pop7lxUɨBJ"L $%.&LiYTbz #,NL5*R2aV}˕X 6YVvT+W7"H)NN6Z Og ~\s yU˷r1OdAY z}y3% #JF tŝh2%RZ%T:.R1_r[֗(ڈ. fQhXSrOfYQ{O&:p6+;bزSTCOQvHR3u'#3lϭAT!7}HwH_dEھ+z(qZe^^Kתǭ{pKڎ2s|iCGN*hl徖> TfI\-WOi YPø[QR*{i 9ҟ* yit=Z}"iР:H l]jbOJ6}5(FQYՇĦV=EI7+J˺̳kTCE锪\CՄߡۗ?b4#6-jU6Qg(,ϙW+Nj+r}bJe_ک_Em!{ *d.HA~eM YD͆ >ܿ8%e"nn:n%tgnZBECV)46hN[T0 Qy_o1ḡ]ѬX|MdGfp 踏G@HsF#4)*,fqu Bıy@ pHӓ͊ T3!LaXǰuDăt窇 x}nȔD^ .쳌Х.s +GߢW\Nio'3ԅ..*&3#>"ڨN.KO ?{ˇ :aٹyP;Q1hq0G \ w I![. XDZzǶ^Pw#f}DgDyC8Dڿff^%ACĤQ*Zv;~#>Q Zj-2Ҹ *=Ri0IWԛTե9#Sl\.'ўGE) nXbهC;["|E<\pjX¨[B ǣU`ȷmm6#oį[Yc\hj** C< z% :xM0iQPnF]&1kkF)Ժ} ?2dIlz# $t@k8f=hX)R/_rVH5,jSZ0 QYknk5]Xi^4} ;NW}Ǽ~CDۋ &N~9+c8QN[_ Q A9QGDGdhq,N\ScLxiv ZWrԹ,(a}o5BƂVW4Js,(FUWY e-J^ %&k*jN)p{@iS J! Tٺ~|EmoCZY_n)Ty~1rC)'(ڪIErvmx_k8OQ \F/f~QᡐE$Ͽ4{M0oo/E@2\[4 Recb5/7AlդOA?֧ˌ1j%9q$k}M6JldzmJT}y{sF6IR>uuKoRTθ >MOgi6T]VI[#m2z5d{E$.yH/𧩋Nޓ*Cuyr XZ/!QdJuߩZtqk$Gb;Zbih66m{~bOޛӥDmM$qԂLYdOlFP2̍نɑ?GT98֦'8(1Duy,QAvBP:Ús}}w2ilYeETPNK~;~ȣ7FN2?CBwJ;ʕ$]sv2_[Û;ˊJuuKAcQb|=O#-*.h?֮4XEZ/%(fj)g׾VC}䝩]SRV||1qZu@la?h pCzSLE*)UM:>uF%=c"օrg.S[}O#(-kNBҽe:RS|'؛3L ŏK=Q Pxjt)W(sAoU;+r0lRRa]|XTt+ʸW'jG2izxZϸmZ!|˲-jc)Jԍӳd}J"{\]W)hT1jҞbwٽ`2%-IhVutz CDL6< zսVaelUkfVTP點YU9Rv+U4mjMXgG==k]/7~9m~uqr~ͺF7ª4tu~Wf_,.Ų\ێ zr(ȓziWy1|EmYd۷?FEJb3=,*[+rW5\Ĩ `# 3 XgQi~\5h缰 [N5f5@*l҈K%=q`THj%QU_iG%1*[N.K$Q(* ̴q.i {͓jſ'#ԃly]̾>^y00-겯=@]߻ pa޻F=F"E.%ԕYZq<[2k:Qpc2܌Kɫ7OeSĪ=VWU捸uWʍ`)OyL@$PZ7`_}-G&FȶiviMͪEzW-aL8k黊::nC郩KH4o q'Z@&liĹhǗ[[T%7JZA(mۦ+I҂*jZ P[_o:sYW}DEU py'[>7k[eG O=KFJD:zW'J-8~ ؎TG9 @Z3:L p %bEGEHC'*@uҏ)nN[!=~Kj& <[~*CL(v3D{ f(Ck{WtD7< ]] ^O-YxkwʗJ(l-^eql>1~ypM=EC;hۦZ\c\wfiK>lRdZ4o]GM TkJV-lPR }bԳH) 5{^{Oú~tG-/y JМݏ6thV-TtI)&y2ŧM ݞ(ɤjJxO򪒌ۤYT.+KыSzސQV7ALmu~kdfmMl]%=D>'~^e{kZ5 ƨ/MYK)+4~0:ٔ:3K -Sxi'M&&# E'Tаc1]f"\zRd'ju%X>HjQIט5 ]du]N*b٩I˜,5k,c #&EjpA>zl-oʭ{cRm7JtJD/jZ[j=W= dWD$#$^g1j䜰Id{LnPG1짞Hu=FPk$OI:i [nުEw˪x{)qCXj]%=H)WBcTCz?:okS5?mqf-yVݙyyDi&Z 2J,EBYEY&QVWTWG5 ! +_%] <CL&/W?ִ TooNEԔt5Nǀs,+F}X~o-W+"q,ݻNDҝ-ÃEol({Zuޥ\BbPR+YU8ǜEY~܌:ݣJ!/'wuoq5,ȯY̬f8/ݫ4ճ~\iW~{{V˷#;.liS ֤4~m voUWX6=WfƦS6Y y3L[v׽vWxo9I\ø%^! w?񙛋rBm_v jOY3}n+ΟfުU@~0!?v}D]sHjߑnMmZxO}n_X.l۸*y+ïqP|_tBJ|m3xa!ʮ-Rtߢb~GW)-{]aWm666n3xozC ť񿄬Gt͕; n_%b).*~wv*\lΪPCU/9{'qVUuxgvnux CU?*`њtؤ|74Ll%WUnLRM ƲբBtj?;}KW𵡡,cX8U =)~ůSNvU"!^%UfTᡙq. 8nY+)W}[F*іv !02\Ҹ5a]P8Ri0ʷ+PCYb5H kGiXBL%H?2ZXìR)4dRBĩK2&6UXCRE).BCUZ8V1,hM(尿uG^EUlLiL^ 4kUz]CipT"UC#yKUj/֦RN>%TȦKpzjuxj)T2aQGpiZ5z,U쥈;VB!z(B)O5)kw!R\.z k#O) MpVU,pЪ禔}Ȣ464BP9]23u(I({Oc)t%j)EZ ɤؑE4SrBZоaT%~Y-Z)WV!TM}~ݰ.|64 5ڪJ*ѐ+ Zt񏵶-r JZꜚrK%`D")UZRLS rj1)EB)%ت2WRNU%-ŹRW6 !=hQ4dK:ԡLl r)NkATS?p )-nZD S,jQB)x%Rh M 9MUbeZ M%qH6+K2M֚? ZgK)(#vIQG?S]]l 5R'bY)1HE!H?c8e~ k ?$չp^yvNj·gȲK,F_\s`YVY@I;̲&߽O^bn:h:eIxprDA53A6=_S@zKFēt ʢ%ӅnXzY a`Y}RUnkݙT(<:~V{j# S ${\5U7԰i:T5YR9Wĺ֟YVg7Nݽ׎Q紽2O)H ܭ*Yk8(!f~y[-1 +&,py*27M{~g(ƝmV&8kl둦|AOH:\`8B>svq$W1XV7.Lc?:9Xf[, OEQ7னm+bI7^Zqo\7x SX8oWSXpᔡSjWSITE3uhmTw> Vͪխ\W_9n܃YLo\PPDΛi dQ ҟϡyM5H#0>njNX$PF0$RJ!"2/()zh׮^Sc"wyr型d6W-"[d"]"\cu1 sagu~yƁ]vٍ`yC r]ED,\FXSL=5nc5ACweljsj⢤!5OJ& 6'Dʞ9PO(kzݭjcP[:#5,>W*XF cD:w'khBиcܢ=*DnYIg)6Fa_;Y\ucBRh4e?[ПQM/A?S6}IҲ%JQݦTxhseBf5qGa@w(*9h6b}Jp`K2aT$]xn0i#]؈X )/$3cT!h"w&uJ1"ߥ >Cvaz8mǒ0,껷C݁fyiZ8J@21a,K8-E-oj^\Y{75aGu'hؼWRB~ "*?Un.OeY1={Vhk}OECD q~9p⮮znN ƪA)/sH-Co9dWGt6 KZe=fWx1ѓhtߓT'WTĸmv tY qD7 ʸ׵Rj, 5E=]b2I>wٲIRb : ڷΫD3e;jCLoۄ&J,ʰvuV}SӇ7+9 CHkI@K/ILeiNvO3 ,^T|OlثNc0hsKZ,1 +DZ!APVME ":=p߅BG k *#,(=^G" G&E38#e#47d›+y*>=O^X%-CCX'YyKJ#umG@SUWmrJ_^§0Ѫkkb>*) 6ʨqKUe_}XNԥ|&%]ީee .ݚŕҦRHĦ1g7o)YhL%XgįbY24N͡LS']@bWŝ'JgʈmE9_?58G}9:T*FYvVr@J,,RL)ַA%ӆ߱󀐘UQHA;u)=9WqxT".ǹlU /dViD֩󲯝rdJBT-1N2+ZQ+Rk"[Cȁ - "1BHv6\E$oXjJB d)VZ ޠ^zJ@4 JȴLm` k`3h-t%BSYI O͆@7Uwԯ~UY$s ~NҠ…TҡAM,.0J!`du<sʽ^j<[gJ6tT9իV*UBĩT A&pjF#{9vg`;y/Nw_>lJSKFGiKw,6e]"=h6RN̥2Z9P& ll~ȥTи>f`DXgcrWcOE'L[b5 cG"#Ǝ2Q-OܷQ 2-^dF?zL/5>o@N߻[<,5 |"Bh؄]'*/cZx_x'ugJټRUG2L7ºCNQ}sȓRQZkRn)Tčn6]B'?i6/I3TEF`ӆŢXd[e[GEJYNRY=`kZJ!Y_WhfZz1(1h+CR?*Qo~J&04湸[޺>|fCRX YP[*oltΠ0 4uG9UPCw{xu=I|3h<&%mҩ) S#ѯip*$uĻz7d']b, 7iqkjYWޱZ[-`Oe laȅQAdj\4C2iB2 H)OiXD6k] bFACf/(Bj0mkOʼ8#q&Nl ۄEOz[3\m!TBi<̜=ս#_##O:@wڕЦ-)7f[K]1zquiUY]$@µF6Ei7Ej,򌣤^wPyo!bSU@eWҮ/j1Y#Q-pI ?dR)/p 볰6lڲQH*3IXenUmP,Y]Yf;yEK|VKN+7 lz+tM JálnFNɛl[Ot֭E}ſ{J{`5wZM5дRg8s3Ý2V{_ˣdu!{Wq,rCڹݫ+6M {N Ff86QwIn֛/K[̮si^3zmnſ;|;$|֟rK=Y-ei( IyKH EPHߢ/<66I'q=ۺ8Iۢ:/ ^Eef2ii0ԶU~%pNהs=5ZDk7#iHd>FjiYNU6gr(»kō1f!rR밟;TT( i^ԒREߨH:?' v\qoYW;!G^;N-Gב *G7?In:%k|Kq2,:xI'|oQ%(~|5Nߓ&$U&Jw5MNMWg(JOKŠv)."?/H"|FKK]JR#pM=a5J%>5#>==D R,Ÿcږy4>D@VO cF&UZ#cLb9=篞{.¬_ᢢ>0-,QBTciY;*9 `a&O7*Z;F'aljCՋg&a\Qw0St KR|cU+m?I#\MT߿bE6?/.ų˒T6Yg=ugO9FN!81&2n[T^R":n/ O~ʻu4ݩzL&VY^^ڱeHQu?j @oc'Z|* ~\5e^o5]ŪJ:N؇`vLp @$[}!>bԖ6nq#[G]1/eHFվ @д&joqLtP3>Moy5K/W >`S\|H0k¶MLJ[=4XYxh1 !8#B 1nS)" D蕒IG9ÙSxؔ9 M1% 2`̑“U/J^)2d#ސbF-")#'[p~=[,ɹ6\#淇ճޮmSBh+7ha)T~!oGcrxf 0C1 M\a-!6GZ7k.,c)^^POqقFưi)kyi( 35UlR'E=c!:Xo\O!  +d\]7lR9-Jح5Eq^*A^U=~OI^`wu禗I9kβJE'Ivꮤ?KЦr]F}OD/&V=&k&pV)vSWkRE zljSYG6nENJ{H?5es:ܼۘ!E-U8-[vcXEu#|1}MƙtY_Mf=_CՖI]_CZKM29jrR]u#uvĬ电ΞlSY:8'm<+DgSDں*kb:ӶNr-LX^%/RM5Kn_zTĵ8nxi% zqgM*Mr;SoYI@1+ih{/OsPŒcvepЪ8 n7-…eGhـE7i`ӂzYS[J1UEtR3gyl!z 5e,@ޡ' *̰m]+r[o 5{kN{ɽ8& ,>Ph;=a# ͸Gf?l!T)N)^T1]oWr%)BP}ͬ)=3DEà,RuO ^ϼ8LW]ZT)%vkzcoа+<Pf kE}M)j^g&W61"6kyꛖԶ*аiJ/5&SBcёX)䴮k~K-k%⎭ a 2j;@|t!cNܧFIJ-'F-Q.*k"Yte1^{ڍL!Rܻ-dg9J3?:x쳪_6NhKN+/vyr>|c.J'쳠=9o0ɽWGt~⽦-% d8bÙ|1ȋ)z8U]v{;$ꄿ \xj[.U"4 km'%COQ-]-lоTcj-+s^H'^52|ahY|שwQLGܷ/AWz$rT6>-YtmB|vTZ✥LC`gPYkF'!B4 ynCU [ڶܣ^ML0V ??Dꂿ`]`gdߎx0txnaT=(T^\sxiVX7q }+ v@sS&rsuqW*]LY)9ڔ|9zԚޒQz\vyD+#ԬulT&Mcޯܞ)ʡfV9XRKzj~m-\qPd;(<^A5|M#ѫ{T]vrfI'^%DNrῆzO+RU?Wlg +n[ydQ\W^l?]vUε15rK xz<}3\b&G`fSwYXNl_ͽT:35HeJ;ɲObYS* Ӗ$8v\_YL1.CXQH2iNօiJ!S,2o,ygojɟZ7@kލMշNݱC ?=U]q$ak*BK"hP\_Jb8لb$Ԣ}&4F(aH2O M;D!ӛYa9[lAfqam* 9)n '.J\&B/ac/nD֚|Jޱ9S~eUC9#PS2Qa:z??N͝~Z9\|^↠5npkJ,SoUi6.;иemRg>A+O"!RmH,ʍY_kY^˷3hivh\TV]VcA0. +v1'u'ȣ_^UM#Ɛoߓ:)uC;b0\U}KF0?!E~ E*iU,ħUeoW^cp1SNS1BҾGr.61X[{ЯsIcx 8p+wRtW* l1 ~,Rɦߞ*Oa+ MIo{ht]/Y*bGK &9oS'*}%9adr9nq^uQd/i_K,=TUMnfԆm}2Tu}wbwE]?M/2Hi*+KJ Qv)l+U|\րRzR!zWy\e1^8S[EZiEhQvܽ8B؉Xh 2v2b)tMYwVgm-J!myhwvѶ!q[*SLrJE¬ q۫(knѵ=&44=U4m;\V)­UW*C[㫝yj[oKiqi .4-۷f<~%ک4Eox员C3Ӄ5٫Μz{ycqW!1>F-3o-!m6f|j\.7y[oV^%qi~/`Fٮx]BSoVh=#`uVWgo"t~؋{(ul/9%5vHcfiW 0QH_RJH) @LL/R꼰1ff6(A1}Ne~oy;N^†\I:\0a"Q^}k:+6Ɩ|kf\ZzOY,Y^ޟcbnsi=tVvu$ei>^Ț3g*JywJwU.XR>0Z#/ aa|0ҭH[Zx\ z]k[ nk)y+2sY/Q/]G)mS>\i댤bQxVV9CiuέeYftm5X}[ıdUx"2?:.Qcn*f*U9]QU`]<ѥ"@Iq|*/⪡'BAV-ѦrԶnzָ5-ghKSlq]<4qfգlg*,uh`_#K;Zʹ*FN;9/i^ A݂!e2!5= yb2vcVnKJ7BRE):Zn:X8욗wgnv%*ʴt7ZvDۥ7L+\R*Ai0XLc:K)&/S|$3+,2ؚ&lv~Ců*_*q+VZӐM0K%BʤV_gmm4UT M!Kj kDDhSl r.q,3/L29'|26,(*x NNhM9D.Bt4u?z1j;9,7XM03 ʩvˮ,[ch!u0 BCBYv-v~ଦi>uV1vN g.3ms]RulTx:KgCrh(}hP!ø}㗼]BĻ(6DbҜ>i+{r!^U-&@n(WRO!%Z׍I=c]\{kHXA e ^䩣:Ը*AFA7X5E[?,*p*BCb_xUe94 7ueY"4U.ÞE6 ̃d`a j(`5Ժ唓oXl­lVW.&AQUS5ncg+ 34+gT;J P*.S#{"~?fXܗ:)4:;y0*U4ҥLSpê!n¾m\!lF]VD?Ǫ7o NZUnUaOyOԶMi=$ ̽gat:Jy`Rbe?db֍K('pYd*fW[PT۔lк>a*1)*r Ts*we[ORESeoo?A$V=~;_¼wھ6.Ё7L*hA@kc~ SUy:^Xp:b%׈ g[&! ˙cD u+W)Siʘ(<߷Bښ 632!5 L1ı3hB`,ľ(99']4 Z )x@ȳ^+2Us fBp!Bil%*ܻ; gCH ʱ2rP rlٝ>6!RO9CUݥIHqL).22C'F(JcRBs T|}ޭv3 FH\_EaJ8uv0:bՃ~//)S?gm*o[[xndDD8\Qj?PS,R^S[5!UV&=D?%ݘQ'ID4 b!N>pwȖmEJ&(-Qr(\cOA*M3^u_ˡn L1Nu|CGNOT4TZVe.#4vb9Tt;lR鋇y( rM-N K53y;+ PB4lڐlBFh7Y%GJ,ȵ][6wX譑 BnXw9 İhӳ5i$jҷ,ohZIe)X 8H>=V3c$ko%VCy=513ʨ[nԪЇG{cZv.c&zAtOZW}A+ JNQ̪UX\Sťd%# ++\ɮX$S/. !k3e58E&뫚tQjP'+y8z֭iOyqPdˢ==O6/M"oYV dC@SyrΏk#iv?YmOtW,P'oQEŵㆹ7γֺ`bs卐b~Qvrate77T[_Yyu!/)qu zujeZ6'dkI?Ei.ܼ+h12wwRI,|FJ0I#BYN 0.4te=N)q;@XC c,4&w{Ĩ§TJmk|)D5ɖu)D(~;5:\-rXxc bKU).zT@vƳx/]1.A!Iޚq!hav/%FL?5|'KٿŒ?yđ*Gz:`0(l2= OSu޳{KvlH( c>, :ijE[S2ն*6YEVU)Wڴ!]s61t@Df9o:\ھOlSV~Am?t}~BxϿRS2Vҡ%7F/vM;h1*r+/Aӄ.sZd SHAݭ Ђ& W[1\5QPrYhUtq)WF)&SuE{7[Dgagz xn) dR4곬օ/섂$=k`GlԻ[ pISۮqoKlQ'Qs5Kmͯ]).^_+:'N^xfRoyeߥBzC}i;OP|,FArSIt!ǯQ͡Y¹?=ܴ TJ#" ~1If,:$5lܧblbFA󥘪;@!Q(/к? A>7xx\4[:j7ILJj->UDNL 0+Y@dڶCח'i)cUij|he2SbSvqOIcTu^n]V4+B-IMwd%0r]?| %Rk"Zҗa%J8GֱJ;H[cXJ$=yJO,A7!DU$:ȵZ_quU#"h^['lrP'A?P(*T<a 0ܓ:Rbor"!ຎO?>;dز#r\M$Q:3!G13/`Fz@rA!$Ըc"8ԑ-J#S^TT1(1n݌Nh?D9C4;Rjw{,Ӑ'[;/1& +NTe!)BPV+de?bݪ(3j*f jx2NEk`ҍ+$ƢuW۔Kz*):dOR[3'*N;RsWee vfўtYU.~0j{WN+Si5榐yF%)I*n2Ɨ,3I6^@/fX#^jFQvkdg_DhƩWa*h= 6oD]q:'`iˌҰܹ[/4~OI(鵝5?GOl͇.ؓdbPµrRK 6IMߣ.e\Ş[ 7ղ!,TuUf.¦spb /#$(%JAbY>(MkD&.8wO%Pe׆?j=!U94'Qj)Ȗt I"(` őp}DMJقrNc"-a-~oϺW0]lZhȃtN`qv~{p_ULgVіPcLTXFw8z &W3 ARZzvzKd9^S:`V#}htMqv6;>s+ۘ>\e; c"j^ a5Sd@grfKvs O`V}XPF>փk)j"M`VE.޽eYU-^!.kii+&W=fx**:E_-s^Eoo[knnUcffvW˭XSVtc-^a^NҼm霔3E(M$+`ŧ gEhpjQP-kFսTlIv]Zb>2,nAD WzCߧVJ~nSoK& 9{&_(]BAY;]F[?;,n\_ϕMKbm>Wz}. կƿZ[ ޡdtU5ٱZ?i),]PјU:ϜVOU߹rBZk|k@e4~يg o&-SU9wضO6էbnk %Ʊn/v[Se2goy,.K)5%S)\PS_SJ"eު14I/K9i}0E5BNsw=x&fij1l1K)紦?a kP =lzXV,gDMz(FRvoC\[:xphJ~2Qwd_ Դ5A?6Gsׄ!eڑnmEKygviF<2}.6I崏I FȬ4s ĘV&@ג.Ej'*H㎙t;ȗĞMɨ SS:Ku qWȀQXhn\ݶߊ`~'-**gV`X8װ7bauIDLϔ }Llԉ-moĹ3:7"Iwb*BSxħL˿@JԄt|w9lYU N[i61~j32ֿAHzI>h_SE߰>Vw]wջC꼅`R7a,(/=%oB1@MZHA:ѕr3~f]cr;ju(YEBK'0K%#P\)$G% jk{!¶%Hn^J1zm CKDYSmTKٵ}$S>_ $T,juN˃xE"KbgIXw:83vI"'QbѰυ=QDkvEi!0nL<;#Sqr7u]}o7|r-Cl-ԛ0/pOV)z\V_j.f:AnjǮ)$8J0 7P_ }%AI/Wr$ B] %חD]uz+S^fYa'4c~pފz1-cNˤTX%oA8w9Om c+?kTf󫪛]6PV*4K{)vcjP71m=!re^Stz ڗURD _(t)x釴uedxQ$-oycr3 xbűj[DJscjuFP$dt&u9F{VӄҰ'̺OaPWGN'g.cM8K,lf{&_s2=6) xT]]6 yγS_[J7t=9Y[8n?J@/E6B"Yӫ ‘Um:A8L=~5ܻx#R}NU3omTQe}: Cp cX4 "Dv8O)N#[.\G+/9dSo&Q{Iw He&{u}IK zy8 Y/0"u/sDx=y/RԪ1j{ﺖֹ:N!3ec.Z+b7$:6 ?N%iP'=MiD6݂q==HO!?O+{QƱMr8( SbԆ!NKlO/]vB|.#Q F.xx(F%TV^T \# yXEx(LܫhԅU`#ǶOvW&x[&YK~?* Y[' "`ڷ7w(d(!MtO?Ĭ&>kޒ2ӊLb 9+s+U뵐ںυ8ef9*L'lWy9[)˂YOAZ쪄ȫ˽Ȟ4|&s*JJp- i7*}\(MOUqSe3kUlY4TX.IDz%@`]M$ﹽ4[_W=$#яJ0Be7YrN`uY m06W`R J谕Xom\uI}Ocv^cy2) {jj"u.oۍsV Pߧ(XC@Sm9$=4tw'樍J2NHv/K0ȯpiک4!i֩J~Ij'xyD6&ʛ\.W B$;%%1=#r8S|{$F>P'Yj2!=u ^x4hO=luSlpj{:SB1dG)AWq'-NI(.jH5ʖߢXK A=n `kץkf<ڕoָm͹];_0QseVpU<=twV޼_:mk,qUƺgї|Lν{Z]=onի]]Ce y,x?3l^{yE.EY47)jrK$ʪiswlӴ-}4U%.&Sz 3|ݘF5<-~QN-1N̽߉/EAYG*Җ)D"CZrSGZVX9JU7ϚbmE 2BdƅE)F9J)wɥK]T*bK)&T1eU 22ȪzRr+Gj)FiBFcOJZšx )}0ĶtR/i JF*fƿ4%* Nnv, ߦծ^:V =fV0̂ZWT 9MMI~R-XBBhqƺ_{8|\_h/dyE6(ie&U0"|U- q)A(B4Lf[j iqʫEA!΢,BZHRpԖ9{I![ _Nך*'j`3']\5y㰌39sb^Z9*.۬ҀBJw$o)&}L:B!8SIJ3iW@?Ʃ0Z9i3*)ɟTv)O'?eMNr \c7p:(f}gAL4}Rd3 B]\4br-{t,v {[$BA.1M-Sx_TgjkNͭsy h<.Hw6lۋ)ZSo j 0[*S- и()Ԇo҃ (T HY(IFH,HK99xڽlz*Tx5ܖ+„ݖ,>X PYtv0CS] FjԬwu6#GBRojuUCIB=j4=̰sEw"62P#! fQܤ(Lf )M0HoI#y>'`c\vpY< g˺S9FNw!s{+yjeMRv}VnbkN‚t 1$=pD9DnJ)h>rCC*'m** NH$$LT5Ȣ,v"*)Mwyϥn%TiU5=FqS5z%lZF-ϩv!hfActzPҌmA !T&S:d1sQ$t+D"2 a1@DVȴJBޱMRάoe]tVUPSKL]xzk.U=EPcVc Slc^ߺᷩeQ61zX%쟾E]DUE͠Ul=TW }O<$1,x0|vI!š>ވXKʛԧ/}քk-N-Ģw5 bxQH!*["I15"]QIHM>3%2M-3t{S>Ԇ]`°4M{f甡Mump\f£Vĩ7~i˜~2&35 ߗiڈ4JMC_Xf]DNZn2NņSfr>ыC4Q8MfG7ωNbF\WS,'.FaE! bI>M^NYoa0/iY޺M#WUb)ݚ$td4骊pcH=b~ͻ5毦Ȣ^r뢪KӨ|[)h{:4lC{C.N=3O"܇[6(R]S&Ȗ2 g6%_ޟ3EO[_!?gA#S0e9D0k F|`;vkDo/iV4{pⶌTܞ*2-2Ⲻ i!o(yZQ)b-[! C/{K(} K c!$_6]SPXs@^9$J(~)_d}&OatXUC,Auq 2X׷v1wi5!>\ ka|*:FtS±6ʊl|c( K- [ QcM'7N_ GylӘXYU|ݳ# :1(SiKFvY49FPc,.|bU%+mָZQdƳ|z0Ut>,!1_oUJޝ**x^1.ML?: ǵ9>O((v@xv9CqK9XQ <Bp^[l&ҤlkNIe0ޔ=hC 43ٌKR9X)-qJLbG~ɻT$ڧ Ƚ")AR^!CɠM'~W4oHr E::+H +N^)akatS>5c^:P6c>J%Ij٥Z*:*B%LHQDpu =U+KMO=ry $mk: B:z'Pӕ+rjaoyuJLAO,IoZV;qFknoQf5}@,PJR+BSq_UVyTR%q%1aQeY|R u@ȱIJ饖sP%olJ$ I>4&5}.o*z 朓9X#yMdHL &KI$zJ,M?уGŴ (|_)2 wF8HlC|7q`"b?T_oNeb4({RpP*uG5([0DOEeX!JK߲6 (ki[{Z]-G4ldjxrǢ5\]W_JΤ{gkJ +e[i[*s7c&Ի-z_kz)gTTr|)i˨!={XVC2oa<>&;*,.X5KRԬ*Ҹ=G׬ڵ #2UU%_9}K~YQYEcI(sJZ-:0\[C q($}2܁5oqJ`{}_dE)*Z;[9k4i@#T#R-`S~ R \M+Ghl*DE>"tQR 38]Sѫkw\UinS]vo_kPSzwy-iP!9_|%()lk #S͓C4)Ч;-R6 Rn=OP ^ƻuW֤I`VmKӒ.ϽK'{lo*7j㟑\Ij97>7*ˀQbqv\wèVlzM&-.ZĦل:'QQDk$CO 8El"M%MWA?dPiXf~ґWAUƧK*tnFla27| 9r.&]܍,2H ;zu&rؾUq*&_sMCP55#9Tb9 umf} u0rʦCf0Dw--h7֟j{*R7"6n?ZY[rYӤS5XSwR湅L{%hݘȱatvV3rm/ļq9T_{vjrQ`[Љ.5HQ^b?e#W @#:)CoU1ʱz=;8"FHVMEqYR 'E.۪ ZwN4(Z ʮKUXECT}n҉A7@s<$[jzI,W[hEQ&&ᜓZpmwK[*7wJpdgMLԈWetly-/:mMCtJ񖘫[n:7 ^ӵ4 te 8bDuݽzxxaS/_Cn?21kIN@5TI~JYE+Ep<)?4)@gr=%MQy^z-oF*/{(ϽBX#V[,Eh XV ^QOdv(/=lHJ)Db|k8^~U_I{ *m0r-j_* M(w)~:"eQ!STCw@QO 7Ss--c[_W\r\$2,KXʯSm+O2zv]lnE_763dh7{4m4GMdm"|# ^rC$R27~ɾ&AoZ\5E*%I_1.V 1BD;eB:BL.Mڶ)lފwD*+YcYL"~s#s+.kX:BTmPJ+ꋝ7t^ѨMdw]wbv@~o`[$KA5ZR/BJ#gp{qb nU61iի4ҟlX >m@4&K9:#\sSDـ4$$N:Wǡ|6nC͇]b{Ed,96o]e[_i@.nd~$ a2޳]; vmōϡ3\63 rP_BR1KrI$7"bXV8 n`1x?d$N&{I|Mbm^*ʕ*/ ߞ#pm"iN:H9KՃv*55sRtJЮ} b1Q)N%)vA{mSuUgDUumotlڐ&T\mG,'gXg[/8&t)YOSн?OickH[MכWb5VrSeAsfwH"dndz [Y8ҰC:tbW)4Q>Q<&I)ȨqwVj۷ꪒo|j%P**MXM+\ZGjx ; 4ݬd%vEaDc/k`⒬Tn=ɧBCOLr;}:NĪd1-\]MM,.Ц'Mgr1\eC41fS%҆Nj)SPCԤASW;={̹*q8*C:tI_՞eҔ4R1ntxɫe|ymBQ3PVFETTFɺȤwZ&- E5|6 2lY jC,]++CXoaxf٩<q'ĉuKmršQϊVz5tDwi̳v!Et/Ajds<)JRpge}۪*6&Y7SBf2o*;<Ԝb]ҏ)+:YgdqQP?6McwZ*0oH $RB7aw[2<ԩu)ʲ5bΞ"8&%[yC yNo߶B|70^Q9VܧĊnuCk]VS +?H%k.Ec&zZR:\Q,Bg($>}U9G}or1Ve9G{g;+[/9zyYVP7#`0S^;y`+ a00@RkGuG#iKk8oԟul2OS7o_]hk./)颚#>6c]IY'FLÚڟyjvw=,/j[*7!Pzv#bj*ϰ6QFw?'4W )3Fhyq!ԝ ǽR[f)ɧ j)/ V.mieu~lL5!=jC )Շ9e^S(dz]UPm*O-xuRG#lo4 j)$ BۨxTR&B? =-iNl%1u 2QgLKDn((&uJ3\uޒ6w2 Sti?D/}s&TYyM|NPe4HȔ9l\ã:&*iY05nCMSu[d"SYgФ5R}dto'w|΂=/0 be~qR(twiOYZزJB?XְNnS LB p }z%({#{-pwQ|T5ڣ ¯Wē MU9uխ a^ZV儡 3wT]_CC<U#vӦB1`XA,KE,![U- rĚ\s@jMa]W5eM.kŶt0"h^"ĥ %,R!G\&]y'O5m ZGChIC8ӛaëZL;vY!TfB)K|/Q-S5/V}%J Nj'YGC]w-E?#y+x,z|bGVWh3j.!bƗbŏmY!9I M;͜yM)a_`XG"7-V-կM,9᭺U+խw_/8ӐmѢ>n8ԡҫs({lġ6&~җ!<r q"KƆ)3?|Oڝ8XOW4bAh^nZt xu#mGJe!4 3&]BKdoKx!^{LS_~nUu]m1rX1X[xMk]Zm&IDZ[gp==56w DF5m2żu9X y3 lUvu^QWo}ShJ[GoM>֨iyeүFv<%}_B[u1%B^UK)N;h*Hi.,7e3\lZ;|clZ)ZS{-N߀}M!n-? +{IiA\} ժ҉E=%Nځþv^)b4ؒVfMo`KIC d_iY}b%ס1X[ģ U Rۊڱ"QT:d}{UKPB?? 9h"m*΁ 7&kzjn5E7eWK %E,oNW(A$EUB:6եBMi Pil")J!\rnYEi/!cf4uynq4,ihoPymHya4^fb-lHAR[|k @`+ϕ~5+;f9'K~PB)iSoQaƭ<~F 3 ^0(ō*9Ѷm!⡲:俚ZonY4(S~^>z5Mu#o[՚sg+5 ^3bBmgsOWRv_=(qSbPZ2o[I;yG~W|29] 쳼d5U"Rt*ouSfUq 4۴^b]Fm=!,fu^PXFA79_ϟv-%'PR$6Kt__kYMg*ڕ<)K{K:TP\8¤/MV c`#K4HP95s$*2HM;7lmخR2Z#b:=n"kR_9SV|K=z.%z{y w. 'C jY& ,@sLQ-,1Oe NfvII3(jN^Ȧ~ QVcZy/>fQzEQ@{0-u-sD_i7tUK2fTUSќ;G8K"}QXWW|kz¸' DP8 8oAroO]9Y\&$4aojעziRℱ# kz4JAEOt.m=u0ˊ]t3->^t'l+JP{YRR)2/i`'׹Mgz0u]q{L"p$KԄnҼLRypUEx]߲MFiP%Mp#H!+K9@*򮦦a;@RDeR󜴂]*|cqX6Gҫ-:%4&yh=*iRz9oq'yjS!YYy =ElnS9֍N~2soR%} |o2a5%%.l 9(ܥ),O5IeGN՗."=4ڔU9DhTGkF+bA(qwxۮn8Cna 1`dދ OQRԶ01s0qX!A!ci\[ ;/$MFb7XI4s$ sb2Q\_Ϋ( v>X9֩+THkmu6Eï銥^Zo?=5>IUҌ̶BaU@8$(Y>O@ی$@q~(Bfy9jiߖg)t9;Qc]jСüWY5LRTBՕ$E [r?\BaUh=?e{3bۤzKȲ9El;w5n[IHq^svu | ~UX]h|aGdRjKʸuiMR5G"aV%cBV8h缉$Hg0 B# [FPD 3jp"- '#xW场kxMgc_ɱM@o2' /J4La7yhRt7F$(Uv?`2 Dn4DQFoN嬩!Ŋ<WLj8|"cBM!YG*/ !럅;A&ArY_˲;o#8K%T[m1'E~%j1#I= :'#5lYf!.bրҪ E8.5 *NfE8lbw)KB}W/BH*zEVCrNnWlBb#t[j3 >E1ҎqgB)O)SߝM4RvΞ-/ PMZGvk@$n5:N,)G=yE)8f>/15Wc.Ŵ0$'2R!ӴtGתIb֮JeK(wNRM杽&yhw=TrZ&HR78}}\)W1HG7(t=M$TxJSZd$O]}.,-*m?f  .o XUAU&_Z_lxdiU~%q!g8U^F܌>dZ^Sy7?JumUp_= [-7M3Uܦtǭ{-Auj%Mj/;ddPMuuҴH9qNL9k= P+-ZHٚ |ؿcvM݁on5+/̽w=͹%xiLdџġz ѡE2aW=JB]@iߚi|k ִʱ|~qe+&q Caœ|Ea1vc)E!<򚄢Qgo =$SZ #V(;Dy=A0O] ܣZaqpz!/XKC\%M!r[brä^ĝ מu~7a{/)sWJ^RaVSI`vJhY'i ^5xL<#kv]kD]CϗYSiq "a9aD3Le]E-DBG etbzyHY܇٬j԰+.rEa*^5([~^|f*";l|J>黰ՆI,-YUPu)d:ZQMq[Л!Ghe5kH, -alE0">)yOTeS&xeYe篲R2zOeRt,%LA\H~-1璪ʘ*-*f?U R'O)]Ά$K2DY:82:# |wz"2WdC7g]BdRf$pieL,&ܒ䆏`[pZFk7t..HО+aU 82#ؗ=ȆJs.F=e^B.IħiWj︪ci`iޢ: l>ijW4{MUު)V?k$h"|yMv|JdtSQePd]@dsRAV%؇1o:f_k~]4*>Tx2NI*)l[w4~Jr)I|ʦ&p1S 1x\n={ H/SW+Y+G2Yi2 ħP;TIɲI}g)-K:+ ¸\W%TdfʂE#)+w+3 Zʰ-TE}uUDN9NIE%LC(-CX# PRBMu={ 3Wm ]"Qn (rA? Z垧8]EK(K-=t#^*»6楕P_4§W| rܴMNWP2 ޷3*AUbP$]-5zdsz&I40}K+JФ[ey".&OyE =K㡣},Rj['')WqI%$tGBWYR'6͌d] Sw_|knUXb0 zltq;//]TgyÎ袓Ԓ1yTg]WvV (,ʁ5°2H㷜C|1SeS,z ]MRO&E譱EGT#/-EsN?Uc,ʽa"W=}p8 bH~Ʌ%kR*@ɷ}]^Foނ£*1&Cp zxޡ"U{:,~ԬɄ6b5sQ܇?E9M.%e> 4à <ϑld͉$I2aFVkƢA3@E߰Mw?aO"49*wDj>a $X+1kέ Ñ$cɡ0,Z/=^_]Lү'G"tq9_dKBъg.R4Q ^P k -|"!CJK YRz\x O2I^QVVI/F n:]4.3.6{ao#ezBIۜ$`0 }ryв]Gdc2*fgnQ{AJVos%ndj FWݫ{QO<'z+8i^4B`y Mo9P)JS`Z݆QZX+BWZ^ŋI;L`[K>砞I>akaR5D" #R[8FyI | [̟g*򉌭/UDF3eۺ#-4-P 4ڏDߛ=-(0J}FZiq&^z+[޲c=IUVSM 0IH'YM[TMG:=;ZdRMH-q7PT LzJJXA;DIZZڽ*駌%Q>hi|.M1@UPօN+H>=:ME]n2ҤmJaS!81C1xJ$?q? kvjJK#4hj%Sֹt8XnePXgU&R){e_q5F 13+If-9ѽ"T l* x<\gjݥetY紲Jr>W}fӔZX{jKn+ѮF#T4XLe>vW}E mF഻ؾsu QŴ =t*/WV`}xյr(bKa :ibQ7*d5N.%UϯuWL$$;hyګ?8&XR9ϪWkVBBl|-C`B}_.\OQLP%]ڪJrT Z8/u%zI"/Z8NQ'.1{u(V$ۈPuDR7ٖP3NFe P5!I~T)ͧ%Nei&hRQP!pdjv^/*6IlׂHYDɥ#i^v^4Of{8u^ )"}Pg𦚇{ȱkS,Ǵ6'3 tXO@p>ȣiQ=A{(;ME=!|$6哸 wM?c]lwMfɁzbR+."DW!~OU,=*i'_^J]+JV؈)?z:P~ ~i{xPHT16k]OPUS㍉n6Ӷ\HpbBך?~&Tj1`F5wabp߂ j^^{ift-q1tƭ beKD(v}MQN~+s<%4vl$ro9(=)_6%Ik/2) nMsFFƮ=ʚ?E9n3߮ԁq9 \*N>bb~493eܶWν+m yОD) @ʑ=pd+ R'Т{?#HMճ~쨸[} +2}ڗ}7͗[G|0AE>zi𦫠`sE9n*J_ k} 9 'W=uFcUtgSUlNIQ{]6 **{k;7 ĢD /}q=9^_uR&o7<_Ruڣ$bTNE5}q!+}>4h,ρH9* WSB jɫhXDFDӍIo{;S.:VϊeeseNn"W{-+4bY|3'F)YYUVTUUvL~,2L )E4a"KK4%߯GX_;'KKXL}VcNljd@@IsK}ƞ%SWʁl>Dn۳fTxPwQsۉ=bz˯uTf94#V"~c-?5ƤeWu`n6{ ? se< SQa`YhKp D)i!]7S݇Vk <љW/z{V[..^͕ؤ-$~Ut Iugצª {[PckqlNrC2$ʠ:VY}iiɥ1vKoBJy+/gg\ɭӷ]y_;[ .{ZwC# UdOyƕġ99%5r{PX~t /ߴqABR]\g*rhi+\"Ԗ+ L.s/86k֙PB6} t *)$6F[H^[A!=d_M@}LI.k g22o6 YJ\F0#r;6t+͇T (T`J}+hR@?cԍ* 038"V s7x[{$]qjROFMNF_x]Vz'/3*q )}UcJnH؟Q5֤"Z<'r/9Zv,B5#T{M6ʍw f{75;ce="TM:\,<'NC^2{m:T+-tF2k%dׇ2Ytf_W;0"iZi}=[V123P|(PJ d+Fs2>˭Fal9W|AؔV^ѯѬXԙtH i2QUgJt ѶF‚8 ĬKwԢ|]5biŬҚsqRsuz"Yf칒%{6!kQ4e d{H^D oA.WWZn4D7=U+йP!q YdlRWxP+*"M{v X^wİRGSqgx꯶6($]BD$!OUh3`RUfxֻuzn_57ãAO"#{Л}iykT _kiPPvcwP:ofdt8TSL"6RaVj7SX0M b|k(K0bU`DtںkcOjBjBt[6fw'WyUihxUgM)"ٷesa`ʬQ%ukFFc:-"HL^%ʞ9|nJu ę#6XѨW~izĞ"zo9'=Δ/~qfq:Q$\o-{u"%+I'3B;A=E=rLjY1,Uߞ(iRr$aZo%-:AQ|o_=YPYHzƶ}׍Qn"jJYmgv'iak#fDu.˜(Z^Vn4rswVՐ lҊIg{ikN7ʑXAn*&9j j[ [oai ٌjme*`6b~vBj~E*gDxrf*'02.wr޶q%${,͝{Kl믱5-Aj!k2n(n"CqE%8mZP;Wj `i*{̭EH!{Gz+7Smۯ]9O"GqzSbz}EeJgE<n)1}K&<1E--O}}hZ{-Le#c0q᰷{D_4ZِϰTuxS̭;^z8֖;2F$ySB)$qHSѶ\Myiݠ ]Je5׬cuV{* Uv_j"~]j(uG|+1WZWJMM~5e%/Wav&v$klޅfxSW_Bd%J͑v(P5QL=I4-zR%@יm҈JE?^gB,[x[V_q IT0h嗌 aZ{*{yji)fFn)KB6dj T7̳U{r+Y@a:ǪWȣUcqbeWj60U9ICi5dn mYxkJ+a㚊Yf}%FR'tM`j;E`N&øUû$ġ"粃qVu(=fvZڋe$.sˡ|-ŧ #@>/ޕ]@*GRk l SrtKm /⩤{˯E $˫̎xզx`NRd+m5^LIOLV"] ﲂj}?MN#@-&j (ԾfV%A]eS=Ua{] &xuUutr K_@Fs$6L@uԞDMA2OPD iŕI?I]WjTJc4 ]bt66+ faUVYjB+CYCRljQ"GnhH kFb@6mYz,!P'1$k5?99i/UF%BWӲ) M!POD B/o]y$fl!cMΦyE5%EU$H4,JTI׫ C"cYc:rWjTNyv[M:zs5Hk:$ VoӓB]PM$Soc[y"{ojB@5r&W ۠Hia/2inERPErhP,%n0X*/f-l)*J~'Κ<>u"Y{nv7SzݾK59AU-R}ۢETu&;óF*>)Pr(CS5dH1BPlS`!!)?3),. 3+OZMM 6_i}ky6iiFBܻXKA5'1_N3Ш9; }nouIU?@EqfuV¼IK?JVBxnW{- a -2}ol$RO} YI2~eA¦;)]~ڍs[bVHyDHk*,;K)[_J2hąMv5*^kmumEyLz(X1o8ۋCX!;O_RXՔ*RJO4Ӻ\35浣MoXxX֎;n{ s̎ e Cm]<_āJ8п3~/Bi)#M}WY"tOYISO V^hXn _(2BUc!z[lew=:CC0𪨕Y*}"e|;A` ,rRu9`N4C5̫&E"Q*䒕H,vJY\Ȓ+}?-߁>ԕN@QGcb@i 64\K6E?SO˩E 2Fe$QeVuEuU@Qo"1#u(XΕe' _u=SYu"T[L^L&|I-ʦuҍ]9I5N.]9'uo(4O3A۬^{s9x4jbXb6m/;WOa*yM=U+,, U0P{T|PFyoQ9 IF{Ҩ>=׫5o$E቞Ҵ㶢7'#yK fW3]}cjCreZQj\]7b- TX0Gp ^+°ϙy,b~>Thvݣ/ν LrLփ!nU []M9.~̴F.4;_m]Nڃ5ڿdqNb7UYu]0s)9N :hr2( 4B~ߑV_Φi;S7΁*W7J/n{7p)l|?rl NZP¡Ba^=V7ܺ:S!9zr5ޗȜ+萠3u̲պVT~}JOHO~$+h:8)]Uo,;zH?BzE ?}_׃&TIG/( ou-薗h)X_l5H3'%% $HןF+.TIDΉzbF"B|_kɧ 2G#;Uzɨ?I*ӻkZRV^@I qIρN[~%* ʐ Ҳ$uWXBsӦ~kdxɦS 2;Uf8YH-J(#QMvM;\^FU]["{Oes+#:Gwv7m uRBt Z[ >E߹ǵU[6ߝ|>^!I7jP?um )}ge}|Hv^~Mv9BM4ȁ[D_I>Y@9_c~Ԣ۾)NXTSPi!~o^ٵi%-wm9`l.&HBৎkU>,ʌM̍Y$)LFdN@6&I2YߊI /ej^c(Z5ƤW%X{:ΠROz;t:ڙ,J5%et?XaA3I[&R0Vu&}(,$srmo(Egkꤸ˓f  eDږioԨ-S7cy_} J~e&ゖ&Ggs(n++!V,5#BNF944·  f)@ǩ񪿮]).*+zdTk(yҢaBfϼWkUgfgU '>'nrs2'HCؑ槨7,h`CU(gK-#iXڂgGB2zC݃{TStO .B+ yEmp,#]Vqƅz,JkX'+P*:,_RFW7V-鲽eG%mhѽO$7tym6*Ko?]*zyIGM#@m:1d(׉m?A"Kܴ_vg#lqvUDžDaYQ蠉BgK'~e*izuA!̞˔sk)ʹHP

&vf} /Ή6KJ=fJʉ('yߢޞȶ]iz46,5/N{JP"ּ%9 ͥqMuMUurGC:5Fs͡*_;M7·J+MR^m9GΜMEBڲvT}dP4vͲtVAr2ҭE/G\@s,.cWFxY^'cK VJwXt+XEuApI}+moȬa]aAxf]j,/0>hTk~W/ N+iG--r+!v2<+V CAk|eOl 43|އ10 Ͽ|Me{Y%z.O:~⚪zs.>uƟ9z8T>0Ֆq'$91U >\P*yi( er ()T:׽ vѵ܋Uv\mFPGaM/lvt'x7΅5%ts3b3ԥM+T4;M&._?2;>\-jLB7%ěi4^#n"FTmDJޥo}ևevj<-lOMV~5v|6S_"]m"j4Kp':"~)°N^f*bpoK_DK`;(қXUVUqcu=O҆fI]=QqaEsﺕ)>-ξ'o/LľcU|,*KWWSvY!a@@ChDF(~?4,ގJ)iIfhdћM4^GF̢wmxd*8[LuGG<Vo[OЕy:[WuYk-ב/2mj[2gʴμ56s: TllFyEȎdRэQNaS. 2kZBP۝l!iwl,) q$ N/2(Q){NV6/eз9+Er" 4Khб'⚉*D] +컴Kԓ^ji9 +ΩR#,X@xRgBi(LRB@6b'R/qve2q'}#*s;S]c˫uMnʵuUd4,,"3[UG΢ sNQJDݤ>U7ږ;葩 VL;ΙfyX)dvVyA2m3,$΍5gpα99vԕ^}\=%ՕՊp+YJA(5IP"3JBK?3VdnE]e9aGhQ5Ѫ++j5=f!@";shpwqRa\Vm0r F;ȟ`ZJQEJ%h'$U#YzPOXW3r;o$ՙ~v6[L+ܚ^IUĊ+ oc"6U)1Bi>qL2"xNE*F|}Ι/lRUJȑ dTk|] "My)2ydJƧ몳jՔ ֞=9ARnJĆwe5}*ҨԺpoȢǂqv{о=aAac@=jMD[MdMQgTwip S9=˞E''zȎх"đ̄Ɖ)\KZG2zY]Jԁ#^[닡+~67ߖu CB5w3MJwB؅Y%$"u]9':slH%C RS#iij7u'6sպ@o$}+)]4s̢wf'g2I|i,,sM]cSat6jzô)M J%89 jF{=ouO _S}ևR3&7WwiͮaL΅ЅБU"5G%CR5$HPU^ㅸfb}n5ľQm!`6Gu T )R c B6WƢ5fw9I}:i :T~kouSWgߺT fWSA>E*Im4&(^2cH1jpDHXW ;"ozn'd{tW+Zj|2F۴OzYS OФ-wY^[OЗQbd @ʠ,p&<$8PBȨ~(-w%lԪ3 A9Ƒz5)g/{Y#o%㳅U1ӽ@i-t)'h&! Ba:)(B;ϗߕ++cF,o]9}}pvg"erXqS@9/z밴k)~hUS}KUCe2%%(e=D7'$/Ʀ{(5Oi Uvu{ttU%msՑC=UEО@ȅUR5bTeMǦ ~z=Gؕ|O i@f]x$OJ.L$xPg|(181ozI͏J -ujdP($"Nh$u)v}}YϰayaiGJN ηmȨ}/SחUP|қkXF'+y fPn$N#u@q/9SOWB%4< (멿FG{V}r\:(͟ߥJ9ӤBبF|QҒ?qPj3P'[L{95өH iKgS@nTxoWWI2Dǵ`oq5WYqY}}Am뻬ZQ9YQDE6dU1X\Af)J K;T ^}lTK]"4)SнGS+̚ >wDޝ!/qmuASȌȨEIBJn0;ʯ+&eD\&dlݗUT#oSgPN÷ݮrYOb'+ml6Z΄0m;iaUA%ѷ[S)[ bޝ&,@ea=C̛N7y,^4J22_l|nErDg=7+*/v'? = }K ?}4R[NnZ!5ZfU0 _TӎuY'`5E&dyqȲWء7SDUt3%vpQ=WT{ ScMQ!_"eNۥ*񨼎(Ygz ;*X&>gz$-[UJ4Щx>if(m( T!}nΖu#a֞6ؑ$@}+#l~Em (ƩQ<u9Ʌs݅aܑVr5)!SFu${/yyƻ΢jQWNUuE&yV4} ӿReJ0BBAqR $%oQ='ҚW~5TO?\F 26,s;:[/ƕ]lBK_NNvW:wI'n)<ۍbZNx?Ofu7Bh@};'T;qQOVPk6=sB "_ՠPlO2={PKkn^?S IXI5+Wgj_^ܜL?r$OuW)|޳/*׽w3:nABɹhOOq}2D/<s(EaqvVG~/ftOؒ^4,ZRr k ;ȟf/e;/AiUY_JOSW5 u1?xh?Io G5uVDLo q{ΕcQy5ԞG2$S;UJwHR&W@G:9JtBd$*DeEnU/z YV4m}hu5ҦeTss8SX[ M&v$2fb\)M+#m=TQE3v$Nn0o?*iAm&# ]uШ@Po;L^#dR_2ku&qKOX]Ҽ<+-).иRHƸ/׸zMqS<(iI}S7С߁@ (tDVNp~Шȁ29 mv):*il'!P@O#vl&[^$u{U*?Ww(_WWDq]睊'0(I)ulOyV*ª%d ]WMgBz)#v&"jğSdXF@AED*XtbHhBo>;F{:ϙUhqm}gD:u~μ= H4Τ*iW!t?i$,JʏYa7ԅkF<7'=fOs~+=fFeb*yְQk+AMEYEЕe$i l#<0D%G#pB 2%&vD)UXxSR(Q=/ۇWgtck\+0;m5tKX_J Wew“YAC FtvX* MCz`ao(%œE4Q{RNd9YxQ%{);$j I[IPC2*3aIv둝9=tDȬD0)-RkdDNgf?.U&VһS\cYViv*M$,h8=V6&~R;kNO"56ӑQ7BiIIT'uZ}cVzS-f:P;Nr4p!G=VX `5][xV(58]s&:kUc6Η0 Z`Q!VϚZń ]sn 6?3uΚEnI3]=dmHP^oSz;+nV쯶=72Enox" [G$IVQw[%k;WaٵK]~l*kTlNCWl_^ayUjL9%/ZT)qOw!Q|R( R~_m߉v]eҡ9ӶQETlU)!W$x(G1Qx|pCWǑƾRȈĩSNWJC4Zw T`R+hM;^ϡԏL( aZ/*{庛"msXA1<,orl>s#QP(,= ~<;ovUvAi ̈́OB}>(yyp~ K[.z U@FGV6جzv ioɁ H2G@m)o~E…~BS*!a*Tr5t;f_C9J{/zj5U EzW'yAЪ$k!B_~WiYSENf6*"Xs[eG=Weas_mywNO/8)mU=%`cNJPl1}zyiVNO}Q&oMA]9IMIϾXm(̎ IJ"r6O9Qq(8q!y6o]X,رv܉/lp0Qk:#U}jjs- _cQ^q:opZ*,|I-1;+oimJzzs">bc25e/w *lb Zص X.RLl[pc-gïVFJGM+׮̚,M$h8D3V-7ѿ}sidZ5W;mq=|siDΤRYER}T"D3%FUi~Ю:\d)sm/+Jּ%Ρ$Pbhad]J'5/䬕y eArq4lD]kO}Y-g+J;w%74iJ|X99]%y3ln+7׫{(LP=?J%ޚYO,QZ4s(!s>9=1gJj'R}aq?W~qYo6;+Ld@/%ZQzȟmmR_Uj@4((%_U,0뫋\B1ִ|P?6ne+㬍5r+Uh=L}υHU <լ uQ?SCWDo!|5-U~zU'7jY Ol̶Zoeuy\,搽Z0{R뵚RZzR"d"/S_h]c[Qi(#QBG jtoM ,?5ǂ TWgaΝ2*@HP|fBeOFM7;pw_#aȮyjŒ̈W;H5'1| ];%Y~]TH[ w)6=yϥUUmϘgrONq5=Y;&*jVTjTOm/_+_ޠ8w.m;ʶs,*&w\3_.1˦v-,vLB/bGw(ѳ(R/24(˥P4LȁOB[o$Ё+ѩ"~m4jM+aQ K_FuemH[Wy4G9Q1 DO2V\Z4/ameM9jZd8|XT%A5AᲉZIiրu6W콈`* T6Q==OG>}KkgDf@؛cksloaiAV S o@cY]@;<KMn>{/2ׯGPblڤB\aU6"dO^yMfUƉ╬F /m+esl)Uz[(8su"h?8%( 4Z̸';F0(yO+Wj[vӮ[I+$K4ɫFGbDQq4ԟCD7^8?zs;1cerSLcCm^ 沯|sok+%M\Mڂ7"Pmt m#=b΂]Sr3uקsRX4;q"u^L'h>1 rZ }m]v:>RQEQ, άLjm#jшx cR3bwoIwQl;>Pץvœ^[^,^$7|ªe0e FP#|k|.6/5 ,lZǛaf NJ),*I5>QԚ% !tEך=ɩ4gH?lH 9 0"1AG죮BIJT=#k`v| [LWМA|^ BUf,yc4ޚn36F;9,DZqJKU3c~>YW]4~{v=ZƑVy EAZ:*wB7"(~Ͼ֩/ P90l5jhPX%T׾))gVϴ))JG<nīݧ of=wo%SoܘMNT$QHG~HʄS34K# d$|1d%<@GonzWSa7 ))4'PtFGq=[a߃^_mV<*C׌LPWG8b U*0ӂS5J+,~ҭ k=ڛf FI(iQ RzŽ]0tV 8#JqIG5´'TUzL:~8 }WyETg+!2 zU򐵡=ajϺhUUB>SFh )lb߳۾/μ)K) a(,!5:9z˒Җ@^ZHQGlGtnׅ}+EB[9X 5Kzާ,kBTdEVQX7ԑ[3tݎ C@t:o8va~ bo޺}>!6MaF1eeJS7o=[bQKKuݔJOfqAHf-ANXe(ZR=qV"i|f7wC1)'59TX&9s$P&2ygٝ~z?NgOdC406Pm YXDeHkcityU_>ByDzXj^5Q1WUg!U |m A@w"M$;D)rXb>ÛhBƽp4F5odLJSMCCpځ.iVzos@/ {lCT3qj~ \#.qiL\t3N(|Дp-,'QݦڣjjNo ?0~]5Aޕ/O;{j }Y؇[Yk7AR +M֦T"-bYI;j1xbԞvUwIp[5Lq$9+՗a޲{ 'ew^}W7lvR;7aQ;|e_]F*N|/5Su{Slp7[ |OMwZIvITť5x; ufvB1lWs1NwQ1|Vg-\h4觬i|{;-߰'̴ %ťOԆUjLrjѕZ7P7CV]7ԥϿئ5*WC\ny{ͭISJȮ/zP:A ~,>dAqխ,b!{!em|"SK[4ҨQr=w_lrTlM3It)uuSrLoŁUVUqWf/ZxoU'k'm2׷/kL{zTbT͖Ri)NpW9uI7kɭ)yrqMɩ:D;^" E?[=(DSu1}TWťk]]O(OQw}s\Q䭪w- U8Ot#d쓶FU:1QT}CnYH 0.]*KI`"l'Mкsε~3bU1:L( mۓMxGk56'(KBkkx[?rs{k[,W^!++}ڎάڛ=yl12=aηJyJr m?BPf$.Z ;F$|Gͱi{N4)E.>m ؚkQ"*h"~X}euGB|Y$J/*ЊS-4$IL,cfbP5„3'ѳl딗_f_hVvвH'"=5'=V7ʻJ!|D>iXFѯ霃t'tm3Bb CMV&YxȘ# (†I>.kYS]Y]mv5 T5u{j%1IuI40)_s=vXf vSMtd[h52zry%*bR]T+-*eN[j՞"XurQVi8Z_p4PS JmX|W߮OA_c,,MKq/JgH>U2-nB[Iu3tG+btWXW SU_I%!(~Z9qҪ*OS s^ns¼ k]oUFiǤX7n])=yNDظ[BxʳF$$_D'/-Zbغߓֵ*_zb6X}naXX gm*WR|īNf&_[եUYu+p-ݖvӭve/>%uG]qϲodPiQ(mcf_R51/%2MӰ'h4ylVgQZ|:7Nj&IYlfq9lU'G#N[/9`K~سS,rz%G_߲"T -/e^jQjNz bgЇ]%@Dl)tBkJ?tjAtXD,Hg~rOD_f|th%B\苃zϡbjExWصq9h:c(.=F+jJ0hB1{PG:SC hSZWBU=Xō,[DHJcj'V+ Pk!p %7.=$_]BRe_JDS*F0*/1? 7jxגɹO׼GZ蟅\[F9Ym5hg7^dX #7M58'_;o'}PvІNI %f,:1IFfo/q%SA^{?t%GׯҥSIG|m>-Z=!,Rf6cAeZ+Gh$ rE]huX+[JT({ B0^ a6ƅ.|p|e4j}xo9})'aK+ZTpheBX%# < movk^{{=PVlsF"; F;dHkbf$N*"9\\m_NGpX'b2=ouՒ7kSbPgP";AG%2#X]$SP'mZ]lZ&/(u4f_;mM29D0/hlZY0mWWCX纹clC~gL{VeXtBKgY[{V啃`ZͲ 4=Xq .Ry*f]x8LSLn9D_I%"ʡVK*QPUȸ*WK eAL{疖 ׇWb|-%ɀG>Ttgee*5"B2J Als4DU'ߤ)'bynV4n,G,yAkb.@kZVr2I&?(\,M~rh_1m­zm8%3_Y&-mݩ:ٜME<µBg=V]x [0ӡN$SuȪ CĞ2bDt*X[œ]W(^[hoYrmmkqUE@)HpJOxD9*ѮEG[\;\͡Wv ﳆ03K=$7f[yqӸaU!8m3K^c:2%{o2ˈCi5 >]Fa `ڮ"ޢ3/"#$Mm;9CVz"Uǔ$(1 UM$TSɚ1hE%V*Tk2"!/-Uco }jƷVx3axBC[ДFrzUs{! $1[2 퉚~*{IV* m@dO#X7šPϑt犧[y靻q8vhk8Y??vu6Zs<KZ~>WdsLޢuacXrԼDȴ;EHMI E0nȧ:R`i}&^Ŧh޳[V2w%I[+1v`Fm쵔%N(+[hs$56~n:i&גڵ9tbܺRy|Sp1r;y9\?}mS[m=΋{%6tt~ g=UL{q [sY־jjQiEK6(QIط~˜ِn#}/t{iJ iP$" d7X-nUBCb t#+ܤwɉ@3AߍbPߋPҿeLed(* 8#e{oc1M +G9 9>T]L:jN"F>4~6g{`=vP_&#X}ϕ'z*{oڷeܥu?uאcwO$KșwE@UL4(-|$mSN_=؞@(>3;PORaL(l˾2ɻ>=dc +Hװj%f&81g4X}kxvrO]=QfsZz̀վ_wćdQj#uqbS}қz;(*UgSG mSZjiR{MGu?i- 2t]G^ۄӖCXh[)PRc]]T=f9UT^ĞZ sL ]W }c du}7e^% M5S)rCH3h*̽EcjD˨)4"4ʖG9mx~7^1V]&V)稏!c/Vm'5%堩6oTc` ,K*Υ&?57p,eeg91:lz6(_B&tMl>mA6c]QR_O$c湿yjw@or=WGMK}9i&UtZ!ޚjmַݿ%{NW0.t3ö}Dnn3M N?܍ų7>; m!ΞKz>(ıoݵkp __PlZ1't7$B,G˙L9lO(N]OX@4##˾g|o9G S5) ]MWߪ l0iӜ7K^-\(a!aUȿ)*yV=U@ϱ{.tV[qouTcRG0끬EQĢPk B6řuWd%}yxБ!cs#s,IiM]Mbt) H 1`^9(AE)/? T7_r.MĨ6 //qUQVzO][ѱ]Ǐx`=i^{L?t1ݵ/eb~^`ˮ Ykh64[w֝C~NOPr+J# NxbvU|HMJVVUJ3e\U^lk$4ٕ犞hWҿv5k!OSJ]%Z=snT/!"Ys9t ;-Kn7iVH*S.*֥C^K6~ZUArSŽdW?e*W4VY@U^ZW%t?MĩpG>#Nq.w Qx"++ SIJ|u%yIuE{6*} /K^iw-wGv*M۶P+ŽG]E>sNM|Ƭ5 ^b% NO>Jkl{fIvvTe{IK\)ӻ;{ѨsҳV^C5dOT)7)8M#&𧯧nXc2dlk'־xsfjZdQAYA) £)y)iNwHNV'G1]t;f5=6\Up4:u Jkڲk)Vlj uMĬ:1 ̸Ad45 ^ZVQ@z/p8kۮNH].sR_B- FGrXt.3hHd4K-ZdnJ,h ԫjڭ0U">Z++Pw!Ӈ˻禮ƹQk2M+B]l:Y'k؍UD) P9d9EP #QОa!H]y߼љŠRL)ZR˰Mk(P ,׏(|ъ<Bւ1([ouod0IFM d)!(,$q-ʏ=Eg ԨS=w)"UK1o;D8F71R #jc$Hf?ik,?Luc|sϕۮŝk&"v_cyʝq캟B6I=1v_j;I6iˇr)/xuVt;KiXVm} zs7Q7WfZ(n "}1>sHY9gEWtf">ynK&K?fOx_D`nVA()Ve".K7Ԣ]+_mSօF.eAjXk=q"&]|jYPΪƬ* jxED7$u*jZ9M]3}:=WYC Wŕ ,w"x] M iׇT1>2^YR\Ct n2(Hʈau>yfT4d6K(zMSNS_*_M*[pt`IYIʪe ~S2w&Ӌb5D_tj;N9wHj{S!Kjx͝j[3.isUeJՌ YJ)}->֢Ž񮵫oYc2zj)"4K+g})8* EW$^S8%%PCHl"JzUlהBέ1\lJ͸nIb _4hoi+vd)U] P?qH]šqɥ5ZJ ƣފfߥn}\įۋkv WwVL36>ܙu_z)KΞ;ZoB)T)[ ӍrU,~g,h,[wTbq+hS9gqˆQ|U?~ɲPZ"k9Kx%\`Tݺ_b{>e{,vz %8zd{`oD"(НZzq QWlzlz=P`9S>`͖dl"p0I[֢#7~ͧ6Wӝb+ҾkڢAK}yu6f[r =un*<$9pl4 {K%;dv*Y 9Wf-Sђ˴}/aV]_Q|U)SG'`Wu^~E^|^MnMeĮ)ÒG`qoYSA!xۓ+檩ZnKܵj,h2ŬJ*xC*d]r ^64~u Z5ecBp^U֫N,[,=Ǎ#M)-m)6MZe"ܲ~R |9Ѿa{H1!!Ĺ6-a!iM+Y5÷4њA9DQE<Vsl4n xUܬp27eU],oV~ߗG%PX`"7uW)$кT>dґMiUz*URfjh}u/,eX8W\ѯj)UAĦNiQA,qVŭ bUl|ЩʒBK̭;4heThpWLPu:NԔ~YYRlajK~]^r.ONG="KJBQM= 1jƭR):T.uUlش1.-U-pnYū"f7VR-j W`g YkR ,mJh"%ooemn Zƭo<Eci VD3,3 lfiCC;]JnGgpKbܓh8i\hKR EMy'g[9XD<6\5:a/%%AEG]]dPt(eZE!nu8Y8ZJhs%2ݹN#lK K^ZaT9C[`V*E;)Mf =*!T-U-Z ],Ibj+Fjcfua=FvnM stQ/l: )z!dI7O=P9wf;踬&*"ոYSTuU;z2+$dz/(*Z[7Q{5,cդ`+Nʃ]DG&a6W1ͨ,6e,a"92;Hc+ XДv)r_3TOu/*W+ ,)EFѕYUw~T<}])cNPG;8BYwAS= +WXs&칈n"$aC=unKG?8e@~+ZYm#x'vi u4[hgܣ %\4Y1IdG(U\RP, W-bʚ;B8}&;>a$@8ˢ|Y,=9'5py8[qf!!ܿ4i^l'T؞LZ x69B$Ɯnl=cN*4Q8# cn*=|ZV"JRa,V CNh k&-OLA#"t_#ERCRRڳH; -̼n|W!8Eq^s\VǰtCg岠Ȟ}r)E 45aR_ySKޝURlo(rud1-[Z%,i'䫟W0,kN*> tGQf~)gԻifӢ\cL%OyE}ПqR0pضizo$q9MĬvIW}h|kz;ڂӄsss85lSVHұiVhz@O\61sQl*)wTTd%Ec{Oq歪Пj;6w͟#~gVˡМ\hc^^줻Kk;qLOdRs! luhm5{[vUI*wԿAɝ.y,kuwno#~n7)ل%rzNTV&( Қ^ZrtZxǎ=uMO%Q= \]^$xyc6#iKc1")ɜI'H!HB )+\t wn, GziE|+r)kfȸQP=Zm?σ )򴪹%]^74/Y\Zҫ̓45LTt|!Xc[sk:3g~>l-h G=,;v}K## A2e9#;Lܔ Li BeP !! 9PgWÕ\VeH)6aoY&::6"SO[=ɦz9S}J)J3 +BE͢':Q27{N[7QOTceܷ?I5źEEhdAzUپyM)l\HXRm UxSmbzڊ 7zP/hLG} s/ QW'xZ:)oiX|kRMfcE,cK7Y.EfUDjȪ2LƸ*{P _1S) -\_|Ϭ4k,;pT~sN4JS뉟 HiU.v6%ޒ,+>Fk/ń`HېdWQ٭3$ǚOjZݡ{O8pfM>v^c6(έK*;j)YQ3F%Y׌;S긎ϰTk,(*-$_|(('$%>/,(?E^~Y"IMTT[Igڱ`U) 7.7h_^BUS<1E@qw/*H!^ qe11dSt<}1:(lk4\ijr;I!d%(M+jG{(cCpAXsqHP5UV2D;"gf 1 GPnoR~L;.>6w66ݎ`_ `c{zob[%KMPތؘC^lj爈?bz1K&M<8haD8{YldV:'!fz:q `&hq%;eYmvWZl;g^gXr=EnxLfryNz1øsT_Wu)QI|z_P(|keǰ;R) *ۉtc1Ug9IԊLH%Ev7N˶3 >Rt5%mͰ6_j8cJbk(HKJ)bVxY'^&1inSt9!E#zeU=uge~aeS%}G9EUtKru]PԦٵ|iu')ɱYAc_&n&ޫ,zPWXeoHeq+-ӧ:6WCyc5i/DqW'I0),d-itY0k(IS{reU}1CVtPoDEUS.Ln9U'˪;Ԅ8C<)J ^YPt'RPǪ; jRNښYk**ue 9&ԁ8dUXVt., n:ΧI4ϕ^-,^()5IVO)t2Y ZWR\;jϥ}O.ĖoЭUc ~|ѬcDG%E7Nj̎+̀`+mhuPͷ̦ǡ8*keI]:W3/ 94/!x՘ ~ʽvdzo# +drenaVIzzD)>PŨ ןc*ɴ7rR3iE},kBn_:p8N+^ql 0,̫ldvd托J!ˬ7 lsHZkP]#v ܶ}m!*)W_!gs]Ճym̏w~M6`];1=-8E֌Y])pT]Bc[T?~ zz)Y'wEBmY讯#Rl7cZ~hPs`$X(ƳerV̒3@|Lj\`@Zkgb P{fxag-l1j)Vm3. U˯(T|EOAXYSxMkQQ,ḿDL1IU.փ}HF`ěamıfd0cɂ/LsCڔƥ4Ҳe,MCJTZ{[4 ϻMqݖqaiWx)d_;0Fzk^aEce^kFL Awיw"CFU)vyZ{ RV9l-(HݕJo5F\5^f=禓|jKF慭7ǵc܄pUIu$tZ\mCz! t$QpVzuUw-bxPoP#U*l}]Rr=At^SV.Kd rQAN}ƦT8Ml[$j[tcn 2J76?BWv.*zlY0l.Z+dyBM]egv&< 7Ҙa7&=JR lA޲v !LHBB(ST^S 9ß|%ϻڍ^w6t'e.*+ȀWb$٭FUNEK%)I)b)oC*,}yc4"R>N*+UpmZ 4{dco-@'nytun`hJ= 0psɅ9P߹k 2,f]wիMei)Is1ȽZ)bi6oXwT®*ݝ2SSm J.I0~瘴YҼdRǙq>T{k୭cbͨoM]<]UڷJʴ([_ Mxp¬skOARUDkX-"*_ƾsU$Z̩;T' NI. ϖ6Ys*d|uYB} >fG)ɲؕx'%b݃VmkP_T5ELjYrq/VEi{Cl<īFPšK'DgL"5$B'u3w~BI9_TFA~٠S7v~lFV=_*mz9/ fz!]//AdH%F<,9Q cr|ݔiV-&/_`lu 凌VtF5Ý`ICfuBIsa|b@֕eQ307SN&T]i^ŁYO\[Ǟ)赥#~P`ºf,ѣ}Ȣ:Cv+tI_K5 OP تEf.Y1/-w'Cc3뤃onZ:ށĞ95[5еV&u0rEӉʗj!r$ė0scc+y]O}7}M2N+RTйjFוFxYNKPNSP}tNX eTm2K㴊$Z Ү‡F>dpQq; ({;09bu1B;^ 8H<<ΜQ,&<ap^Kn*~=a{AejԥvE%Sb/ixVD6ŧoYMhM:u!,?t0M!#N5/"^z1,%5BT+oUڪTXߍw!G"]5u?kX11l⚈U)YJ;ST%YHjT#RwϽ4:ijQQe S+Ly ݦWTصvs],=UoJ_Ⳃbt ܛ19*-/l1nSeiPR/=jCi""D"B?ӫj/2ϡ:ࣼrȝ=m"oVPS/}g dn2yU[. {zp%|J6!:zg=q&=$D:M;`RY Oя˄2+ve.DzMQE-W}K7 lKL)˥XR "3uG>U%yFZb/n?aYpyn*WYS6Z-j,Ȅ[Vq42ȥb),bz&?:-Jq_pmO`H*e),9llC~d_9HGMM=oU3jX+H⫫ߑ~J'qӅdXSO!!%bI|t'PD;Lf0d6aScghB XNRʼO!.PԗfIy9t ²T6j/ARRK!ElVqPf|Ēh\eϥ3 ;{x=Y3j-6Y5%AYu't5sj2˪rW -Ή9z3U*-mfuq֥VB$'ɮmpV_^3Y]'MT]CI]dũyNRгt]Muj2guEM낸$ߛ6|s:P@PTѫ)=]'ܦA-3 Lk":C& 7[o1d-+6=J;k J+G9*䷣WuF J2MKcQzV{}=vRQdld\]YZ-MBׄ{R-fSP$ĦUy"z/ﭟACD%5#1jT6.P4rH Ͳ%kl{fwQDC|ob OI2 E!׶"qT;Zlj< ۅo=6WD%/xk%YzQ_z1SUo#c2e۟ڈ02>qT\er\1 U{<;S4ɲȳ+yz*¼PײOMJ㟫9ArhƒJ*7G(+S̬<(א|*tnee;,CYf%6ȁsVRx:γg0)UCJ/ %Š8yի wv\?݊x"U 4Zbd5!6l-D/#8#jY" 6Qܪc~JȖ T}"nKi csOէc2,ĠBǴʒ~?)X%ERUXp1K… Hdw>%YP"O*E) 811ThP4`qRè;F~{Y>F4g3B Ҏ~oY,>rgQߗe[6q:ٳ_^reTs3Pnsr[^;sh U!аT)[OHh_ IMυs^)0)7>$',ɵ#ӏegwyroEe~ O =i*r6+|I拓3Z5ϩGMlUE`q,M U$6A6, ][`wB#h~Caە=*gM~)Z-mtrݐz-R ;D5zjƨB׆ik! q8D#?z CYrVF)܁ڠbWzժ_%,M,)5gPPSE5QעiI ^YEZiXJCK[dSq>|.qy _%j)JP6KmW88'̾5g* ژ7ۜERbS=E;$wU6Es)@Wm ڧB\!NU ([竳f άU>2 Eq.4nax*PXR ^D.)xK}iJRSZ*i`r:E)&1ɩ3Ǝ5D%߉Z4fgmE9%xŴU 3nv5YFwa^O?3k:e: OV)R_9tX)=?j"ʥ󠺂7 J|v5<˞sֳkC Ygi*u唧j]oCx)6$) J(7[z?y/{&k.לȢ{u߁bUoU}",lx{MASKt_5,Úth65e\` =.Ze߭5_ظaPo_Bjޒ7m,ׅM{Ϳ;Jz[w[/oٴ sW[C) eZ2=VI/:EIFqogDu} 6G4Ģ0:!WWiqe)rY"lS;]Yz((U{Xr8vt[\E0I}o=f3W?wެw G̤ ;|l2Ƕ#YşT)LjZɃ&='Զ*m' ˼%JX3_!BRvCo-!8d0<nȥ8RaT!L#yI jyޖeH]ATҙXUC ^5DXgRƅ5Pk/UWzͺKhR*!8~cKclSU)7jR^vilvܺq CNl {Co1!,B .k"M UȠyI}j~=E}XT)HC4%%"ٔڬ&!ie+pE% ~\pqݯ(ͲXtKYTdJkBkAqJ}*d/=rh=RQDD0Y,E!leZRh2(M.~qџ%,RiM,EZCfU$<~Gsq.GPxE,CKCA"V "SPsGS\` JHAؿ*AR@$ZQI3ӘGL 5i5DO$3 =zI.HVB/JY /o{sxf_ a7܆a$0UϤѹ\xjRs]KRXJKE@iijqvoлDQsKw* BI"z_UM. .aHCAuұe*5SUt)W"UJ)jgK" h, oec.,B\t:n4T55KJkMA$8D9M-Zj)BRWSE)Oʤ\=5UhidI4Yt"iT!B̮-g6UY)Fʤ~S9qiXlCًbA bJbQ")kLN^)OSK@}8e[ *+iuq/K*m*mQ~ P(D QM5aY.ИSCrvVZRo Lhpꦔ6ϤDZ_ 4Z؜=jlSTؼuZckTnkmG_U]%VtQgZƀTSǥڣ(;ĸNY`WD[*ZmZKRBѐ`1,ipmUڱM4tSOUaC6RTP߫O1>Uum))ղa׉-LL֜*8qtce_2Ȫhi/}/))F%^ZНչbSˎqmL  Ҏ8\SpF=&!Խ;0SV0lҖ-v0 6OA|Yuv]Z܋z #>1JP*+5u; fh >CXgUg8C#W=xm [?S@PuTj܉NOe0'K!a-Ĥj "PI46Z[bNc!ubSoָi꾬TT0viv0Ht&u۵dy/=2W=ҳ=_l}M3:5_?e[eQc>:ѱj3D|IuzpI&q9.m93HN(]P-15`OQk70"[H*W%?S6Xj$rT\MثjsoQdNGAS'z7&COB3PRU@KFJ+3ٶW.'_ 9]3{~kľ z)pe%,r+q 藝M+koA # AYUƑ1AK ٚ[[ =i,W!5#Ug-y9e*G7;sҞ< zKjTd3 mEȬ|SxBRT7 Bb[2'?0jsoA<UjtitRԅ![ &./X+nHI=f^wuPrYͬ7k5$?A-BDЗB6x vٕJ&YOY]]5Q0q<A$Kom:8-]!A2&|gazf,d EP7H=MƄ̗RV *|fISs5Cy e=kޒ|̧2JPK(#kXt"Cj߷Jx=%aIIv' aj+_U#ԅ."r^(殈W'?{kE;Mڸ{O2^)+27<g| i7wi6LړN;>Me2Jp걉D-dβ3yo%W'yRFL&] }Mk(頨SyE$M9zAyOe4-R bSw; ȲuU¼4-z%<NYJ*;{֎P,VLSL2 P73RT5L.D,b. IF3-:{Ӹ1%OtpKY)f~#32ThcUG%$(Q$1PMG O[?rf{v+Bw)=CJZ\vOzT&!УFX9P YӒxG'X(K)9d1#B=c$ݎhmY;c^izOQ9ɹ&~ҰϱRfӵU(722\gѣ^b9]ޟ3SM!uǬ>#eKn7Y?D#;47q=R 7wzgE^C|H-X^ܪҭFa^;aE6ȕ_P*~nTe3w$|M`} ,}͢lV]F cШ5FE![^CTT%GJBVX^%Y4*4i4gWuȹkX)Aol&/bY^R6JAO11+y(N2}lq"5UYX5=yZvN|ʒ NՕEոc?pb5nW%RAWbTC3?l>wB}"Xחl: %w!nj1UCQROb& R*te-Id%k/~[N2"CS :rl"tgNcwYѦ +vcԖ1D"xL3z|ܹ&lM҄5D;lTEGjsj/-*~BZqD[Wq9ɮ*4$<k}˸Jݽ7[}Xǥc k[ci +*b-%a-U`+0r kXi3*x=XYFY.B!qX2,lM89~}N]FU|[b2K:uKAq.JBeQ$H,zaުu-Z|k94kׯ ,ZP.m6D֧ z[O|:l><$\{DCI`尭*kx ] C]s^׵e UWS nGjĊԿT&:|?vokq?LFYh/:"ggOڿS&OxiHx {H-g-!-AQ`R:|{+QZ:n27컋TUIкrօQIL zhRT#y~PrGArwɹi^G?Z Ϡ+Ug[1; W3*{k(O(yOyn?"q"@8nl~)5n" t'N$ͳ}H2h7ind(IDQxKRCͫJ1{J!,t[_K>wrgUۣK:1v߉.(]}<7=P>װl*hL$C&uI{ WªԕdXn:!*[8gĖ: ƞ)Ӓz"`~GU/sK`ӍCB ܥS=)@! >DCI?(׊'O*Dp7LȒu%s0~LQ^"Cc Ñ tߨ_lzo̰_ NM3o wEgxR%1Ԧ_ BVJDCJ ,YdF~E;A7q -@vzIN||zik[Bnai_-Pk*J!s߂]<*RE =NG.V!O{WgܡJ .PU^E\Z}1)~㟁?&fPQb ޱ"կA=Bf!xM(mKbW%WpE5ȷ;ᢴhyqϠ*y,p܅ *h/%`Nָt4xUO;\Ga*=:7؎|iWI~H1;IG'.+-|GVcřVr_&CsWU4El`е=^knp4'{_JXL }˔:n퓎horrelV} xoeT&eL"uMb Pn&sXg~?͙Ѣ6Y#O)`Tˆ$byWP HUl6,I>6oY7Ϧ\)0qH+ wMYJ̑ӨɦyiIeYR 0`"*sE27%HY_E^SG,H !k/OQ+NꨍU+Nr'0lT^QSr{)e,bȒGXqw~mPXUOg}ih(Tko6iZZnDk+)zɇK)T&}M^Ĝ)yRM{_iz6+"~o,l+#υ1v9QUHRĤR*)9&`-r7E+ ]tuA+*܆##%Y&J z;/DVEsG*O]ipQ*߃.z{tﳈc`]3( >5C|ωo9lds,*jr)nUMSaAY}/1oqalYsZUhS׹IWY /)`#ߛW7i{Jt=|~K"i6>D{x$uM2שPוQ!]+ ,1HK)8jTuv!ФHE8BKBX ;$%U{3Js#B^uiqerk_s67M{hg( uk֥̊*\qBbZEEcoah\}[ D%9eT6WtWXڦCsӵp]}6b7SyJ-dyH=` ˣE.;OiUyGQ Td86}_72*jjZR@I/jyhOSz `CNߚ-?jz\QI%Ŕr5TUfth>XMBvQDQzߧRv袑 #S"P5J,*C/c!,iWQ5O֧kp~QH~ʈk(iE~s&EH$_?ѩ7m9bmMTb;`(O]TepGqEh.JE kxS} 19stZJ1 PŪQ*JЩ2)$CyvvUTԹ+9ItwYp=-]]3RbK*9>~UqA}N1ϕn_L/>slX9?藂^R򖢐[LʳTKQv\ڼDfm,_ rMFE,׭Y|zϒr#`o+X 1|qeu2)Sw3UqNx[[WR3u#靌 9X^( 0*➇^c2SZ;:UܕgK]G܃g?rbC!XgQ,7rӖl4ފXz.;j4^ߩa=e>+eݣ"ܝo\mCr?!uD+*GR>:R6cENT3O} J)E9]Rƥ_gFE/[sei(8,[}BX(4fʾhGX+O hl rOmʽrvzeX8UW6FAnaqAg cpɁL 8ڟ&! iBWOU?|2COruﭢ,c؉S#`i~v3tpV A|  /*FʃATPfV.:+M;QIOKŴԐWR~'b=|BSd_.-kHfZeQj-+Jbh]QMT&w읔BĶ{oiSGtI^M0,N_:n;)C!K 76ƍz:1{Sªb::T;Jn.3gcUy)!3V0Υ4~'=ǻ\*+wkL95%гO4lÚu,5lngc2-&%g,%Sp]I U-h)mH-^7e%K]k /)tҫmm6is-̸EHB!Mt і1--oppG^6b)uJHɢ;]uQ9n$0;Rq/V-N|h-A]^@pïJZjq)ƶU h IF2 {|uˏ[Տ\J솨\ &(â3G\gMaAj(B/6xs Wi1WZ,ȪWxU/K?ew(s@c 5\PMi~B!zqYCޓq<-(whh-dKX}P&Q1zf+Dg aׯ*=ؚRx͏g-G'w\,naov!n,#J2ƾOqvysv 894,aL;˱&_N+ZU'O38"MbM"'ba>rP=E:)}G %ܠZzVW"UȰ8*!!^ګoAN}-Ύ~B 'Γ7d{4QRՍ(c*R"1Ku4)g?bc/q3b H[D}s@D04 q(/t[HF9>my fG_FAlԸI_N("GP[\P}ULZސVz E5 4h-Xp#_t~)&Y~m Qe'*Ϧ1 kz.;oŴ{\X٦ITBzU0)̶s՗`51 84GcS&j$:s8'dJ5KOa%ҙLʬ*G߫UPkTȂ'#K<|ue@ $y 9sPu7)v[77ҷjyfK]fIeF#4yVZ-YJ獊VU;R'+i)Dcj5P.b7I[ t|nљӅCw|?5:,׶&ro5ɵ7rӵYЩ_ 8^NmCNīF̤&n{e'P^36!wuPP+Hsu9H/qRLu}?[iQҷu|`A:{ S>ʺaѴ3#ZS_BxR},hChr)7~Mɯ`חI7Fn&S>?oP(U4Gq9ug](ibWf6q\h)^{Q o$V5$'_VפY'pP0+lm4̿E;3(x=dRK"ٟm{2 q2=tڙFM 3n#FT Zg(Js2NBb;Lcvq0P&y{ dקo{V{3SmT۷KMח mxo\8&)7$Rܺ h\.w#؄ϲ"ŧa 1i8}*JvsKBV^5zApR|Eu-XGuⱊCRVpWr* 8ҊD ؼT%e༧rK:Ҝ) t?^vm {"Y4ITt#@9-ꀋ^QGwHNWWVE!|~$6]?}:Bqkbػ Zޢb}q[_MS|*;ѧ.-kazg9\sX+/) <'Ty}oNګs WPg/E(=N/%MwN-OR(*^_x7wWB"4<Yz+B 0)F!\r7JM.AcmLeP| ɹ| ,vo9VP'Ơ+ǧwEj)i! SOS/ 3"Q)0T羖+OZbl;똶k3{2q(igZd^Ĵ4m9UՓ8.ob侼ƝR&_Qww;tˆW66=j*IT&ѕ~f_:LJl<]AO}sΓӈW6wqH~lOlVP(; a})`R(hL%qO]OSTtPSt?qgTôԣcP9&¬XvM) Yx|ʛb>F&r%d zlS6'Lt1dPIc:X_M$ZUT7Z%T_ҁĩr;z:TiU\ տK;RQ}X_3MSk#VުJ C 㘵)YT$}hIiqdc*+Ls85l%vuP(}ۆod{qKgPR#~* D`aD#7bW[hfl]r$yN@${^E.Ee:=}ɊgF,^8EDy3bUKѧv쭦^C3ժu< 'tZΌI0>q} -<|rڶ:ZA"yh߻A`[)UqTTDkѼvLXӟBź/>𭫩A?KR*Yr(ʋV_ʴLKj)ʻ q|׬wkJ½z9a)U *=TZ+)t;V[RK!b+O`Wz__'?!,2gkK-pJZ)n_v,M `־Y~k kVhWLWv%e6[* 1$l)x'j_]Z62I{Q@~ *E34rX_s}UAz_Fk{G8;'ɻbGৼԛ/oE8VzpoZqsY.>DL`%s_m-:M2 QS(ELekeX|j ڣF3 k F ax'wÆoz;5ה$)K0,:}Ӿ*j%VUEw B,*ke1#mWRa剚+j-{ݤ( ">Q 3M<2n/m`}55(-94X%26́Zvӷeeml2юkyw'ge"Xrn^{X!nT͂"6#/p k;0AEiޱ.F_'/9tS^մ: 9b-+`wvi2QW"I >߆E=kCmnL ]x7c6%Z&ٕ_|*/=E=F:尮_wI%?M婺@݊h*s>̷[6W)CoAΠt_>7Ꚉd49z6mkxdSpz T3^h\x Yr ӷWX86؛狿|xWqϭ)%8y W25^w++Ǵ-njr."[ksD}I3jmKf7Q"k˪o l"%5&=6gsxqΟzړV4%,brf=kbcC,?Bha36PVz↍0rAf›ӎ|f9ј_X hZBu긝(8s?}]%"̶Owωu3ԼgmZ{19Du0'V8hmkBR^{.⛇pm<(B94tR$ ŵi4[Z-nY",}EZRO=4%YUUzI)ެ9vS/wS^KT-ȱie>ͣUg2㜖5IkJMԄMejYݻ;"=€JUY啩`z˦V)C* S @4iX&UaRdDHkŶ֧Q4K"j.ꤹ_3z\R=R58jyoknS)↫"SQ"נ_]/85- htGb^D!wU]GP*f [˹O9_[( dV( <r`]!$}hB=Hj*Z`8D,*av`gO5iI $x:-L BOz \͝ [zQ*@wCQ;cR?$ !aÿx͡)jRmI >F$[>\(*~|VS}/)HWau ͜9r) ;=Med>&Zi5u깯3Wi=b1*+rNK ?3 ^+[p7~$U=-;d;k'5OT:oh$~OyLtʫz"ו](tǻOPѩkMm1~t1T/s8NJmkHQSP^' 7͡/J[67UM۞N}M/JeQ:o6jQjDvy>S[xԢ"2.3Q墭-\*mnpǶ Ш jp#5Dmn)qhL 6{b2xy|oP;mnn +;aD jz뒘;Ҿ_[FTSAv'MۼWZUTr5hhy, v\sjRZt| ~ t о猥!_ӕ]WmCR4\)òECpP]僤'(\e5}v=ORb4*WFtn.m+"E=s+(^nq 5p@IpʉYd%gKtm~Jg8xCzLqWeGQ!A`Po%""" 6mUe|[l,+,kՁ#3Wl .j8eRiAzȕgw.ƝNOrD*uax?:* sY(xON &7 ->U%}TF!Ӊl,DvF?ו=zIŦ:B7f1k5 sF z Z o\(B=ב椓M4vK?Qd$:#su9w2 #pLÃq:!*KSD<6gG]'}rtbx!f_Ep5J[EMC%HQ/Z~.(,ӗ鱮צ?ʨ{e .7C6nFT,m*Zaw-l~RV%E+ı;aO:*:Zzy∴NMUoxS5J{Um)Ra4VUa)5G-jSG^CXf]fej`dSLQ%ba"rJT$ЙZ~ϯf <޺ XoG0!OEPIG*Hǰ˖0'tߛ2i bߚ d;|( N0۶IGYw1j]SZS2o*M;j/O7ǼF[ۊ#bOilMzE  jC9|iycTYHۀdvMP cfު \wc}Tv|NlcNlrK͍W'`3B#H?Oj.OIWaՄGH ,& ob~ zG nmX4sIc7~+¿_e)BljTǓ@IPBn50f%q50I] NF"8G>r'˪6peݍgdBEv] 6(}<鳨ZɿC{5XnsPv 4\W4n??oƻ/𦔍5);m"ÅGn⬞KU F) L²RuAcM_x)(ؕB 5rk?L'Mn]fsr//;xY}3XüױN}jisOɡER!I!ҩ)>g(?;I-,ZhϢZ ]29, ,3P?U;QVR)U%~i\=in#YMA/U%SB *(I(b!+$šk)'k SH 6&h+;pҍB !SKQ\XU8b$(+QF%Z]Aѡr)O!-R V S|YZ5$-S=K,~~f\Ţ"0Q b[r@I+DDQ-X/])>Uau D65DQRHߖfVt1NZM?DJ+乥TEfR Kv+ 1 KIb)B=զk7xڄ~;8y[}LR>r1W.V%Ƈ3 SuKkm(U e㑖oN}ji{OEٶ1Ms0 KYzST"K.ԘJq ¨A -]9B"9+=PaB\\W=&vr_A$20c=AOjմH"/4>\MS M."CCLFv[9~T{U~HEb  " :B ,![K^8;ԤpJFВe5±(TR5 R!ƅ4 җaUnVih % D3UMPHbPW4~7mFmYIE*% 9 z j)+}kIn(sPcTˢ *>Ŭ,+RH"jTe)M3u<3-*3&U*7)icbK'\IB %P?Hnصk9B괔!HE0S [B]uLO-$ҟ U?Ǿ,f/4%I#vC)1F)ʕB6Zz Kvd_ՂZl%NYuoj)BB<8s&pRH!ĥ٥ PQJXT0m6eOнL!""R%%S^4*/VbB5 Tf=96ifX7V>8uJ@"z>Ƈ,)沩b(?z)~*ƞ| {|gjE?c6Ν;|vlVE>4-<ӱwp[jZ2kvʄWDj޿9i%u;&\ Q4;5够 WAZ&yiӆ}yk.%eX6}HN ie%7ӑS? uKgCdu kD82R㞓>lc>=?n/&1f~Pԅ]LZvleW\U'b Ϡ+@#=Z !g/װbfLVz2t<4wɮCiU!By,r g-kQ[tb]ҞPS$+;~+F[XE{(P(.jZl)"־t-KTR1^+SS1;Ӆ d:yJKq4x'*eAC7(JgO55 .Xg//ݛYI) l: "..I5o;>U|oTl+SY<+=dQTm(BE/|\T׊ԶSљ1kyH Sf‚Fҏx$ a/Y=Ya$MwUiȇP^MqYV8Kt¨KYͫk%姓ēx%9)/0 4=yIJl4f`~r]2ϵ !?tqeuzʂ&U0!"͊1ճȧC^{ fc<~fSѣO,!1 i>}ܣŤh`݆amgQ!AX{@#5A*ˬ&-'啾5mi(RXV3]~~ʪ 9Oag5zlgDW6 ^qVǴk._S6y;vV7M}bE{/F Z*3 7hE{/bf#]S]6c)9J=vѧ&MGMvaд n%YSXzʃ?TSZPUeVR򩁴6)hN('aaP% _Vw^¬Z~*V ?*u]V7Ҏ=TlgEH(H8+YO-Fnt1|i]怛)3-ˈt!!Y%TFO|/! $I"O.%IC2THG  Ma }edBg%YN#3Ȟ7oW@~*!p`~_Fb&#=̔ JEgIa̜vI)%IiW"Td K#V;pUKXw*հ} %e.xĒHk͛XQBV`I F-kM==wL_eCs$1PR8gLYYxv}z~THlk??QvOu7CfyN}Ο>+>{kk6pPچ:GJ;ԖA:wQjŷ$z칐T)8lIyvX'4ʊF#קDùN: 0 ;|J\Rw& 5tU9EL')aT4&Ļɶ/E:Gi";nL5}H4:5)tPeEO{%x1t ؞QD2&kڋ! k EZ(>hMgl,ҔFBk$C Q۴r1OL,R ң7mJ0ۣ`*a=4Ti蠰 eykþ1*B跺ݗꡑ~{0*!R1 ;L(ly0AgO;b1ŻM< 4s)hQbΩI0k^-O-hw9f==QѪY'Ty\^:]GrGQUa$ Uyi:mp뽤@mΎd-og£]ʌ^̨3j▭ Xا-rڿRr0[N qOvR{"=6L!/ 5\~5sS%jfZmQX[Ɖ0*l{LCro} L\S){Į VM).͆^6*Pa8e6G[#R4o"޶.]_'i-R&S[qm LjGuZA>{V6}}h깪rl+ g[ad~jWeJʕ1! ~9䴆=#5 X2pZ=q׃iS¶C]ڲ՞l{cY'vi{n6:ɶw\1o[G45kiyF6N];Wn\Bd;&tSJV2V8Œ%PomNm 0s_WKJhQLhYޭIARdwN׌䰭/3ˈiE!9-W^vD|͊Ydn߻wiʶIPZ%YOѧF<R| Xlmm%06u3YGM-'"bҵΤ]R 2Hii_QǞ"#K춨 3|HWPHe{ ?5K[tb=eg,uE he <6%~ ꔦ** P׮ɩuMge3ܩ-3ҾC+PQ\B(ҲαQslKz =CF(-M40wBIz& t"#үԇRP҉9>[`,ްu5/@Q111$D&NCJ:t" $’LCVI&75l>+Vʛ^%|_ʎF\UvFvQ6 M=BZe>ƇsBjM9H/ ֊J8Rnz(#fJ\ZYDҀx)zm^Rړer9ptzpUe -PSt(tB섣vRt#Mblvm몽k jxWa}kxeY4!y븖T:J$D/{j"60.ReyVklrwYɿӞbzpNmia3JrMIVY(ؤ0$n,^*8ƥ'OGQАj۷);TԂ"EbW5DM4q vͦjFCW"cmK}.1P(kRQ|Et`'{:}p0vx*Y^DYT¥WxKuY c:!8{E8TeàzQMFj-yOfOX-c+ LEU˿V#1M:U!71~[׋Y5*#ԩ-JIb!|7V,Z֒iZUB_?CV7tT*9w~?/R=߾d(~m#Zʟcu>XܖGgm%Wqx:jb a!?_D柳އF'*H_@uނˍt-xQl B .ѿgWX_.0uعE [`~Bؾi;O8wAb&`m:iA`e %\O]a#PHgXdrzondՔsG,'[YpQM5|~_鳢-VWwAO{ ?p)+Cr󈧦2KT:-aߑ}VUwBM9íhsW#-cjiqK)?ZW"f5ڙqm=lU4{ebf͓g-!2 >jmTPQUz4`w 9PqQV JRT沪kwmP^,)BX"?Ou{}Q)G˨7wS~ TH{Mh9hIfSeH)sFiHy9*v 9^E {:#9z4?j=,g~eBҩ>IGQ%{: z|m,k35=G87E];bM#J]9r5' Òy A8PR>S?F#2BPK]MBuW{/2:Fum< #ak!3?SskoQ0!fc&M~u2+l?@.Q5~~LM6~.5sB9.F:!Wׅՠ:~0&YEQd+;/eWk%"lw>b%1oU ]?wENZR鶨Lz<[_){]ENr~ B( At̹\WZFs}Hr[7%E#U l_=OU)k˴+-n/L\ *֞ad[.-EhE*.K׻D{Y4G1Ѹ-RZԊ۸Iء;Q#Mފ~'W&UtPgƞVݦu)qʬd,zO^e圠lL4+#'-M-IFإ Y&QtL'UgyLQoA8(_:F5`U_J|P5e}_IUX[VM.ֵH1b)SW1N&W&7֬j> o `.zګ2I2ɷ0>DU"ؗciuS&OUVmk(}QwvG6uM_E3( ܄h/ƔG6+5|tJ9^FB>~ /yÀA>eIOfErUP }l,DC.E@$Ya9j XyKvDº+SBZLKjLqSOU"kr_"ENg|i5]}Y6RxB詳;ЦSI[p{h1 uIv9[lR(M1GsҩX᧎2ܖF&C*.eQK+UUym{R=/.Pޕ2 "ykzgM4/0;("TOԯ+&{wwa~YVE{)uZw)*YP9ɣ{uVхe =x46D:IkzM6gjlЬZjndձTG 5mM&]iBHQ^rp+2(7ؽQ\feVg$*OL!u7dmeZ?06&yd3|B1Ѝr4yI0t=ܼcwNREO^b9Lhu_*Y\ۥjx';B/)t3-v̜Mb7 緟v Dbա$/_>5PWENw(D"&6qd_L[f5]%#L#֮zۦ;DJ9>氿m`NcOP2!?ڴ ]STubVbnNT4M`Qk@A]മQgՋjm a4)%R ODeI6.AD$kxJvPRW1\eEX,r_KeQ욆:"֗O猤/ Q{sxk%r=K[C ؕxFrȤz3 B'QCҲxԜ;a\>WL /u*{͙ }A`' /V=WyO!Vee"׫H3[Zȷ8'r"ՕIo}GnV?ƾ&1xwQU%)¨yNg|ϳrHrYN}U: t+)$cbٕyIQQӪl6-̧X}=t5]BM Z736rƠUư7)&ݦR]OWһx E5X)D׸Cts Ȅߢq 4ʳYG )Qz +?G\DKʗ/ЖeZ8T Wk+k&mJ)l[8D")Cn3O~Dv~ond&\%n`]gy5mIeF: JTggmfًR_+O]yZӷȷI#Z%]HzK&rM׼tП4 H҈b.g$lpF@%Mĺ$,U?a, =Qݥ}Bq{Iij/_?* c:Y0j UCMiU|WʠפGeW8:oO9ad\X\U!k[aCCIz2q+ K@#pSwnC_)6@N[yCxևbQV tyj%L.R)d,jf]*_ZZpRR]#Sw-)uN I6.ߡjB4*FЊS̥bUƖ¹TLcCVӞKEE6v<͞.xsM5qkUmfR<èjͨFe=%xǹ5mkW RTO ؞Z^4,jo9箷ķLyCAb!wM1Vg)<(:}ͅ7 լHI?.W1*إö96 ,|,[6/"ktw~ʸ5|ߩ\Zth>?ճ^p.\b! ;$1*:EM6s@ COW4<zhSV`@:V:\wýZ.ZJ eG8 SD>V}Z%CnLghsr+ spy䭶{*Q4dW yO*baIj <@IٴӶZjE1Lo@O)~Q{={M 32mm-/Ujt*,wXWx^."P*i4$S- Še]zhJݑI>ZT 0eZ(Z> nZSbU\=#rq BoKrRfSjky-"h[:-0%۹!aD%ꪖ=-xtMvM&z&Ǻ?$ۋumYk CKM%CTBY&J+\~_]ykiԵb/ʊ&R_U˾6aIn Y`XѠ;:tך%ܲ)"";Na#|m?)3RrZ2*٠=vDE)IEydz2RԳS_*CENU!:%E4GOn;\_֜1ܭmJfQVt))8QT|o =&Y h!&rKyUsvPT[bYHH lT5Zܪo*z!Jnͅo zv67׿/\ےORaN!XSq!qե-q+."2FmVzjMՠ4-_^\xOmzЕM)BQEz,jTʵ\Nѕv8*TlPm@>ۉ,zt9#@ưHa7O ց,n S_ )Dr%㶬r]}`q[9|ԥu&9 WP"rb,;]E> -jg6it 7})~QzlVesjžE!C=͞Xaz2+ⵆEnz j;k-htp_]wz溶jj^EӬ.v 2 mXE,T[Z,7AАP1-t RVҊxÙ@<2%}g˔+Mߝ.1 ʉYh3.=aA2DVע>E4ZKV5'!֬~'(^{r.Y7da9tPY$Al A+sElQ."LA$qb,$'ܹƳ}ECXX) ~=b,`澝aCpa6}H"S; z */2赋l۵1ogH#.g5]ld!KYeSEY_ʞd+zTԴ)6EASo!7jJ~ȱS}˴nK< BǼQT{m]U4I 9iGAaVw+nT#DRCNJjf-Ns9GK;OZm {TM&3tiؗUK9NZ՜|vQ/sH23=iJ&-g0NRؓI4ZvT e1 )+r]ĺl>4 %]ե6 j1HN?.~*rRSKM!%4Lr1LzM. NdҔ5YItQ/霙 ^CGDl4pg;rrSpQ(>p[sk RfIyGվ+{s*\W!0HA`d3G}eןM~h-ͳrִkex]GCFVvVI4Oȹu`ΣHCD0JH*Z].-j"i o_ G2xYTs8j|+_^3H/φ|B~]% "8Nbo}~zƉa:僁L/ 9a곥z孊a}\X  Ylٹ@R_5lA^yQ^GEp2x5)!i_ ؼmzsPx(T)m#J^;TNaԸ[nO!LMn-EANt|- ghqY-u=Xb$(^DkȅUtkV/ʰ.+b<~ݘvtvUsLI9iߑBCHfQFo۵.2)U-ӸONXa$f4fﰄ[ A!*O|7a쬤F鼯 "GMv>bCˆbAo[kJ :3C$ާ k1 }O6a* xkP V&Fp>02$5[J}DM@D"Hx+n)j\ƅe]Ԗ|>[uF6 tܧ ]+XaV`<)/;~,.Jk2եkפâ3M[Qj~fU@T:7BN1=bE dF4*b|6B~H,*3"6A`UV]n2C|ZyuD#xJ-Q†^z}勓T 5mdqåwlKsM{}|j5.ʻV*eD)!7ZV"^6_u'm@yi5q2P>sn$aDE;0˵ۯ\ˮ@(n2M u7ⰡB|BJP;c@,L ȑrMZ:fa0εȻÕh U5M]2^iq=%P6&"捔xxsd{ߧ|%j4$ӜyupivyBW)ık=s~WG\qV 4-Wʄ(*ćs=Ƒ!Ӽ=\I'dW˞elNJ޴cW@fڠOU1@D: T}wry ڨ HN]X dւi6q%ъcjqvEqFBdt eZ/0sпBV{"ܕ{ɠicU-"$ HOkLpsʞg36:SFfxθEF1 f [t*6-~Oeר#ۍM?N <_wZ͝kkPoDX &IP.2=om(9$ޣI~BA u}p%"H/rGa xG #{YzGCt/^_"Ԕj俢T?BJ8ʕH%'QOZ܉hPgܐo֕M(N㼣hHsc+Ǡum;erpVVAݡK"L'%%]`PZ3N=bֵ?0m)cFUq/Ouظt=YUiM1怵xJ^pMZqU}MESO`[KrbC ֿ "$0m>u/ξ_2(.+=WAXݨZTF1HF&_mI>D :)cDNdPK8woKKJko=[G^fޕh_'m%fI6»ȥFґLB}KB#6wJM 0Y }Vsu9X .$VE-5__^;)#_Տ/m?yq/nWsWo<1;V]ˍ`f s_ʖYq-Y7䴕xi y q@w'K Gs(UFw9}cM{xɖnAaJ<*kB .7Q.3aUI UW55!')ִ̚W}kKm RSMfssm2_ٗA=COX8E&8d6& so,c4e bYҲ;Us"2>] fwZv:*9z,q>\odI(AlVBD\)y:"}^ͮ $* . wD$n\#tMѮ;n&^h7.g=11 NQF|WEs$@$‰Mx}hQIH7(س餗6)dkUS@9PE_O&3l2SN&}#,ply:T>:5 LG۷ά]IdJQ߳MJ7\T֩"LkfV({'"%UHk`WqwӋzkɉ}ɩ&=D5H)(nwIYzN vLZڊC﵅c9Եr"\o鲬{],-vlפxg3L4S-wTMt6D\@R^л TcY57vO}L/ ɸO+Y":bE%k3K֒QT[09MeׯqK2 :t˻}>zބ)5*_(XyeTgx;sw<()ƭJiZG}wtkRkC$BMsqx \4ku)P>8v\kؽs$$cӁ)ꐈc'I@i6Icu *?5 ȧ[J0\i{&W@ A>_fpFqy?BX_/&(z1PUg4'|u5Px9YQ~=.a(/žCHMh- |#LF? 5x ZrVXDkgδ91 JL[uӭz [FVE]}Yޫ{.kVIMt뱋U@@d/r%}WS܏^b,6X;Tl-ؚ(a e'_2 z h"چwAUpu6q)BڌUn3=qo[i4^Pe2kb$^*et"gM"r BvoemOUxg%u|?i^qV5ϥ2ky-±lTV͕}-)wLѧ 5r bVGz^aX[WC-h]e8 U5isP &%u.zkϓV}?RZ!?zs(b +ZJ$X"ri`= $iL{htjQW?}Q#4.K$޺0g/#UTcEenUTgX),^sAGv|V3K;2Q+6UbWDr9({hO{Omqmu3#/~8ൃm6TI Q+*1+sdJR,H*c'-HBZUh$T˦@bmY&޲njU5ݿy=Hilt-(\|I%]JS#W\ZG䊈\$F۫'$&kl|-* +mz]*uOUQPuY=wp 1nkHqt:Ԥܗcxޕ^?R%ERu֗S.2Y"9oilwkk>Z7 Db\t-y3I' o=aNNSRr9 Ul$ ԟ͉#S?$.ŋI$\! =uExriRSikFPֿ6 ughil%XVT4BQ,e`MŌ=ۭ sρ6Jd]v{ēMJшڏ&_=bYbcLݩI eضknXL{og6!{uO͸gj'B;<>s8F &ׅ(=c}sLzp1zV)7euZ;U0'8QU&ϴa4cswºhg"HG}5eImmz14R|u("~ Zuf1Tݘ]]引%MϭNBp]RZzPR1MK~ {D0ǿ)v jSԷ H2uhEZ(=uʮHO=CV>izؖahBys[F饶hJ0FPCϩgLgȼ *DV 03~V!P˾ߥTx۳I5 co"jZvpK;+"*/[\{o}Z XRIE?]'nj\'ߙ.ջOq>jROPjޙU.xZ}}ZF+ZؾIT{OmPSãLy2/9Y(m߇^7.Br!9jS= q).|ޤ(EWI~RVv'ʲܨ Ȼ\tUe]L8mzN>`̢ 2dZkW1@#R.0? eD SekU?H)^ⱌm)s.A{浪٪x%u!o DWI6Y% 4 .VrMkse-EKmH0iȭ<V.нMRTӂ-.3'R\r 6½5vI 1I46_G^SGO~!Dr*COeBshn(:d94-RؓJr*ZOcZu*i֒g eX'E#dsJB֢YEwW=jUBSNTM)fRBhߝ̶eT)E2iKس*\K{I-%3Ue*C9'2V*T!eUX+M*PkGX!!IRmm:i%!Aڝb4gɬְ Gwyɣ.[=)֫W kRR4#7jwB(SXsf:6nPg>>d=RmGN҇l8$OBirOjN5ҷ[_~S/M%]BE<|K ?U7e铥>ȡD?m<KIwO/cR]z+!Lvvqݴ(B}vȈDϵwx%^_c%L'2x,y(ر/ QTC)`?xh RsHyvGD8瘿,ծx{&Scy-<9׫J|cvd[^]>ֽ5.ϪeK'̿ jCC(ҹ~Wf}şȺо`ޟ|Ve)~ƐL˩㍘oZ/)oJuM^xWͅ|f1+_Y5tCNϹYnkmF富 suh NdZk~"^7Vj-} =@oty7eUu=sw6{f1,U+ fpwЍuկ}kL\p nhLKJ$3@fYp*TV, ܤ2-6,oegL˴^ЫE)0WV6֤įwSByW"&WfJZKrC4.#RX1U5c3<\w:^U fUqMW"Hb1,C9rXT%24 SߣQ !hriu4! kM)x0бBiGDBJE0m-z[Xܝ]5 )UQH`IwKSze6uuסKab%`n-3&Z")uױK C;4w56RQqYNUtJbz%Na4eG*UORFДѫ!./ JqiNVSE)Qq|ʯ%4! E ׾]NTwkJm ynpkZ|{Jg7z9>fabn0V(գXlJT:V <%Cef-&"f)! N- k(I&JPU١Q$Kg7RfL!E'p^*̺VbjM-sҟ %jU/Z-ej)9D:Ty`PgL[IcWiՅ!LޖYq&j P]E lHl (+JJ?%QM /P5_D,A%,cjl s|0vۧVpy5zgFbiN%_`Mi"l/Aj 5 8 )~.U[bvwQ !FS- m q'Ě^?LK-m캥PJT՚l~_5R~)HھUzj bX:%%LB)ꯡuNΥWDR_iM gT| D]oK'MT$vO&Ę4V&oop*z*wl iObQohꦈnEV .ʧi%{С+CSAG܍-Y!ե,BDaVUq~1E)e!M)Iy y P~@yyeVөBt-c.3/Zb|p.6XEMMD6rACC~IdgQ*ws&,U7k:jN1S٦ tgoAyo_J(emb*Ԝ4%\NNry6NzQgEr^*j䭰I%Uŝ\Wu\aF9V~ WV4OsȵqeP>|?|g˳[NbIzF1NK^{(][𿆪f}NU:5څ2qH֞,U4B)jU;+įZll,қ半~b',koXNuX܊W΋RQ{gB8d'9jk͑<} AH4t"QyOiXt! =0<-\%ÊFLb\jΪ[q/ c:P/YtTF6mVZHj+`2*S}g3tǨ 3yYm|*~ZMN]:N/5IJ7F! }E|Nʻ\)61Kt]Q7qƩy-f[5npN, r-oJOUTd !V+i/.[JƲNkG2-˼d4{xRMST*~ K%gJmAZ- wxeu[2 eg P{ s)XP_#񰎣vtTk,6QP 'RG O"5ٺXQz1̧^#[KνU(6h𪫫^e<٧I*!NWĊNۥ{N4궏OX)ԟ24ԘOJʢVb"V4zyOUgͪ [b칕|J#͠/&+B_Nr§$tC,{ gTǐ;͂ISW̊Z_ wuz8/Uk'-(ouH9Z߼]^Waߣ<"dYb8b?M;c}HQF9ayM&j7H7 Wyu_],%~ ʇOzgd$/Jp#[vQ[WPkٿ;{+FO8hk, PtP iiPU-ľmMs.2M6RYSbyL=t_ l=\r~Igadk- iכށgU-HWǸ)xksp .L5C~ :efW @T% @OJ@`ܛT {$׻ L{-5]PUKlwD-u h c x;t;f+黃 \1PŧTW;*c|S4ɯDiG:-Mm' S4F"®RP]L5b] Z.r4MK 4)T=E씦A#E^;gA^ siDT+F1vOXXP&?h&Ob[u zmj}#6U-M-gtjCм|>"N5IյQwAICy%oq}5`]QL'IY=VV%~kB&abmz+S2Ntl^r ̲I|<46qeL Czң_%69ꢮ0{t_QvWҜ( &D:z+46þ,nw^rTiʺڙl(]ؤcV䏲aq}4ސB@m5՗Z"ݥ}Wv"K>8醩i'^wW{7 %b/A]j9;wdߩB؝M"K[8|?1KEse1wD>7}q$۸,21ڬ ~sӖ=7\ 5/cШ[2 BsRuOj)RH(Ak&܏a#kV,3·UR P,/iI?UU\nUaYeO ʰå^aT?ePVЯhRA;QaZ7J=b^2-^:j5/vURs=!19/+l$T2oTJxn=R~Iu[} k3Ƹ'um:b_{lVJjlݗ`d%.sNcZS:)vAlEU}AICsNY<ku'Z?QP-QS} S@Y:#C;_b*`N! C'"REt y{ 'K6 FyyנH# APi,¼'m%mi4?H 'D74'nMH?pf߯<&P7C`50]O ~jKN=F1&iE(?ތL"Ͱ["_;/VNMkFMQY5ny^]@(11rST&ti?y7cb:8cxrTMJ*JwbaQQI{ϡUY9g!mpN@VAwP |ɧ%eHne@˥˼ӟAuwÊn߻Z*Lۖ嗒v6n3t$˙&'/Gf=6Dɳ <) ru }IThWĞ-Eih"iL]?J JOuq%ԥ!7vJ>,k.GД@ev'H3>C+lL:C+E$Lr)}vZN_DziKXunQ'Il=/jOhc*_qc5 -N CPen״R;rWk$'"xL)wf0&RFDq-WptvwVa-Z]p;ytSKO 5e9t|/}=7rƙSyODȤZލzk#Ĵ+1>IXxʒތYlJj+Y;V\1Pu8zOv(ȥ݊DPZDB:T>ꮢQKuSlȣV,zbWLabpYz) FŁHdjJS* (3|#KC|lr-JU=n"e`-V;l9!>lYV~wU,$SAE`K;CDۈɕIF*EzC߭l#c!(e-tzwܼ"썂 -k(h ?ނ:cE/ =&[2ŬNp]XI|@?־(A)!}R7YP` 'a#cR8yV)6($plmr1pSgE'Y ,uTK\z;Տ̆ilv:1q2էn ?VFЏAvʴ&5UfxeE вze_9zX:^tmhM>׼M)u3 Ii2R䨤%"82Ef^轄b5Kt25/XL&WyxiJNQ Z&@wT&Eҗ_F0ɻ_ɈoUL`>Ʈ>e,AF<$+,:Y_zmj./  kkWA:S׬gIK]XpZbñhCޕYqH6G!.\ZD#21$!,Z:O_ΡD|:V(YkXTbDS8;X:{}_hd"R\Ӑ_Px:V^ SF96-sXi]w}mfBR f̳=Tz.L0HқjZ+Z'sAAϾrUCG"W4ΊcmE &þumJj[6+hFV;|} s 4jnuqWieR$b~צAIe^CˊpO)N>-cbԴznU”J *s QH]ѕ_epwM 0NŪܐ_V#*a 9kCR>Nd$V7ʿKVi/acM/h2pt7ס|kswY`8H,{GŲk7_$C} izK‚%{"ˣa'h^(Ncӧ̃J<᧐u`m/Xm)RP0CvJx=]91NҦLZҩf8&E&~*BswwV zj>u=T- z6jR3 ې.)Ӑ3lz>WR4z 㳌< 淍˫!Gtu|y9 Ϲ//&]Q) ]r /m|BZBI6rZ ^:I/Uh3RP QB` 2v>W\֪?@C_[-{ jMV 6&^\ttM8(Po(xh~3;/UOm+v% Ng}l+: YF;4Hh&RF< IoPš"\J0EO!sS nZmUG}fjSאw$(,;k~wG䰬IW_* &M"pP>|>&6 DdWYj+YݥavϭCMزa\lP[«ZvIXy~ K3 #_))wCPu T~δ6MS'Jt`QVyIOdE@RQC`@ʰX[|8m2b `kcj>$hc7]]iӝ66T hDJdk|ވ&a Qp8c7R I<.;¬$Q;k.2G9\K;)(KQ툇/  ^DK FzLX1J{dG\#iyzDHup{(`y G4m<@7)F-OלT)57zv+VNwv_X ˾N%sv=#od1e ed`kE9ʲi?8Ebtێoޛm8/]TWdӴ_x<ŭuuA͢q{cMO=XT-mU>Yz0bgׯE#(bV$G#ҌcXǨOED UHyn<.IWXyyl9tV+~X+vTWIPUF ]QEdս]"9N)ha!'ϩӟ yWO4Sb謾M>e0h*zT^Y6NlVN+rz)|Mݪi[k'kOwعb0 q~h%OzruYD/i5ID&>°\(N5eh^~}.ieW%FL?~U]5uS-7-%YӺRomy|j PE_:\E4'vLW?ØvU<[Qv|%ӶEl"R-q0QQQ?>*ZF]veXI}Ji;qTN#vľ9[^ٗi;zKrH(JVX&+̌rt^_:{VUU zˮL%.2GDQi0 fٗyoխVhJiK#6O}nږ˰a^r+{.lc0+&uo:GK#'܂kZ#ӵ]$Q%QyҞ^a^-uXGUTXayiGeߴGR\k)GghGڱ_a˃ y׌7HIgcėKBpR5[UyIJ¥UUviGPaSg%xFU =%!7(4IGά*j\ 雽Q̕^a[KƼAy!qp7Be+QMFH5%?DF*I)/Пl,iM3k?@:+S*r/mԡIMa; K<{ }paCKP;v>6 1uWѡwio(j~H!n?[pmP"?,.Ybkxq ;$hȑXv֬aOQ :LnVC}=6 nB3d%U[ʨy5Ւ{ }NqSle&$m.3ќ:"7Nq_:,U-\%o"V w Az/IhSm`M y+X.- 5ItQ @;hhj'$=VGkC,pQׅ+oa@CPpޥUkOvbSqj^Ӥe↷lbrϾEPFYxӬ{*w+U 8bԕMZgEqe[ {eӆ7'(Y'!ޚ~/]SwJկoyE.+!uHV)QnW@E?(,bQb[\[pUyXRG恗_t;rjQL(?)-ן&UeqS 7Z} mQGj+:8,WmEb/ɘOx"ʣ h lD՟r  P9Q#G2)&uyU M[ՄaGI2S:/1i[eY֟/4:74' gʌBV߂z"{d3 } mρ8%œ#/j,e!BoZV5$߸;ƒb1y,&aK`hNBgu p}Ҧ¤:tA#Ah/D!mOVՌq싉q˔-Eʬ^UT!%m+CCOlkM9iTSKlZ,g VQe&4ufҐ.0=if53/3znUZE\S^jOy-SލeW qV\dbAPDʆ}%EdfTɯSv->>~EZO]2arɡ?;rhkڢL3yjqdR5B*`FclѢ n|5sf*ƭZY0qw3 >Nq:Р!cוj fAI; AsOLֺX戄lkkyKNJt١f={濬d&ee/^l/*»'>Gyu"$ábYگ>gFrf'İΡLs=+]a}Q|KƧgB#qm'0Bt^J%MqИvIi)ґjƲ!ΐ$=^БmJ`B}v-r.7+ |I~Yv|Z5q bpS)HD P\}5I%{\|&!״S0)SES8{9mmqcCC^lZx UM-St~ dR/C={M-";D13h1Jg#лRUws~-RguaK;'$a} ď!2!UogJԗ$-p׶IZU$zeݐQCBSBi WVn~8r*!bUXU/YW< gm=?ķI*8~%СdN"%rh!7[՚*)5neUҐ}ohrS]JKɴg ߭KTx瀉!$TPISN&UVΊ*QK?*j2yixk/9/pܼ<ç񥧫CzGZu2?Ɨ#7T 1ڄ(SkPЫ_'J^*i ?z(p^9eT(W_:^jJg]b/qZŤ~t(W1ΙcE>}rouvK!Z1K>#$WX[V KuenYi1iZKB%b-~tRcV[rKE))|-_s ɹ}] 1#=66Ag⓾Y۹ƈ⮝oCYO%Vi!qO vS寥f%2,\3qkzI\Q{e_jW6Rgqn^6]^q{.YeE=eÛ^xu E_=z5Rw 5*4%T<43Se\ziW'LִŸkX\xM,"2Uס7e2f,* % eqHd6ֹxk%DhkX"4 :]Gb؇LЎ9|e`wOw}k)72/!DkSYY~af,z/9C&cCdP( ua*u=q" ;vV5rk¶ئ>󴿺{NK:5r4Hu?0tP mEYPMIJjp۽L@ҼdPeiyAtUgYzRwz~q֓[t)ն(B㝾-cy4Nr@U(<g::9@%f@ʻi@a"wv%PYõ $uyTr6d-p!+>ݲ {TBۇ@I(UVGT7%=Cvb+nx>AK:N|L$%# Y?T!TСZ(.S 8*RVVWWch&أ"9K([J3vK2?MFFnuHe?}._I@06PLւaЅ&̶=7A>{/x&+n2z%ZC~q2t?#^\h{I,*k5jzi\ќg?_꬚E P7qW#Z)llj3eSAX[ǠSM&x)gYS0}>F?&a9 l YK8l(/=B,+ !}+bNV9o)<g5*) ;&ȅm,g_kUObdZ!|5hjRqSߣNYO]J* %fay|-n=OJ) (@b qm>䘷:{ LA{'- N vXtDS"_u+ j6:|6ޛ‹,ATUUl "Ȧ%a]hQdDZj?G\t2+E ٤{:BPA^* e$ ^v0_ WZ-]RUW\baVa人̰NUVfŰ뢍5SqK'1^e}+)zrS!f7 Ou:KEѾT`[j}R w^MDz+J1kZW&Q9R4S*SF߳);/'ӺK0 ]W^-zMDWALq[Kj KRԑm_ʻ, ^󜷢 N6l_3'NfإWf ñQTnOpW=1tP18H+.˨= į,6]qWpΩv"נ?jͨxF?Ry6G[QJb5O\g %TSf+ȅUYD']STINe-ܸKX.}B( zڷ9|ܬ-;@ʿ@j򴧢4p:/絓~tij] Ia26Fy+ZH7Oxc߲ۨ5SԕrA~W :-{a6'Oog}GY[=y9rPf ݗq.k5*؜/pgz4okE?K,FW`cǓݾmq t" 9.Qa2Qei `9eD`u>l@ۭ+[Q ? SYLT_^\Z1#0uY\^8D,wa,' nRT3.`Dǚ3~Rϖj,sAoj%d:4%1_V(vO99uo.h^4PݦUM>_\mmYRv,GVц"АG 15BdE|]cݎZj5Y0bkP1"ۭȡg<-ѕ] 7 #ٶͻrI}x;w*+;Eo&_b^{훼eԢ!Ơ,RA`ཫieicgzٰJ(OI{$zNjkl=mɽ}>Z;όk.6ra$LE(pkb{M$R5)i\ܒnT@"=w pIý[1%IU9J|Rp$ǖRZ⎓X1;y,)M2|#8ʡ>i(N~o>T56')X' JKw67yDS7vɴiծYD{ P|Bi3m8F 3&ײ%Amޕ|o{}EO*-S4z:%]]Ե%)JmY?_.X*xi?y.ey*" `6T+f)2:2s_oԛxS* >)ʦ!nh]u9NebU nQ$|mJtijtr):"R-%{#PU$\ҒL'iVLVݤgBp"z%E7"[4<n0[3, MBq Tta9V&SRnFYW:eu1ydaǺ^>`r( ُSd=•88]{B٤#|u ֊Ἐx3lc{lֈøb~9qw趆J7]gQӖBlaCIO_m֋$|uu>yx\p(j.%MF_-.D69}K`Vݏٱ$us˗wbH ▴TuiECm0[ϋ?橯ht'{a#CCWQh(薤|(!p^#p$L@O3v'Jy ̺ mP^Ģ_;6ER9UiCCE]ke^bq!WF]R  }>0g>9VОo퍿]gk2uI^ZpzWf:lEPFo/ŒHRU\VOS s5qi6Vyg0v51vdE(۩.69ug/eո{{ۚr߫]jPi[ݳem 4 W0 u.: (vYU \3[\X1U+%=5m,]SvNdyiruEoC`'hgbSZQG_0O^1G[\2->|r88 0ܯ-KvԤBW/uc^(h%5Qj`PCn7U<&-^]a+EmzK|Myʪ*z;nOY%M4/* )e.)~Q4WwW~] YϽQQN͎PTٹGZPmf^Y"K\X[b'XT9GzsTmqwlzJҨ4/|y洱u\G,4>MIL^w[YQoԦE]KsBx|`Z 1X>f]\E$į+s*>j' ,w-9Bi@?%9[@;U`CˋeEu9te՟Z[O o8L~cP dǘ܋|{+h|ɈC\KJsƂtn LN/2yYg74v:^msN̤zGј#H &pS\qdRlDBh','#|IPA΁x ԡM_}YRIH?qآSPrR%YTѩ SɃMVX =dYp ~t$ɌnJ4in~9 2NS"YK$B) 4Qј90',ω{AUF!ُYd?Ah~27T!JCZJq$ӓ(<&T"N b[ iw[uٳ eν=reIJx IbhRx+Nu&Q"f<ǩ l\l=jiz[GߧIʤ&SB̼EP-*ʜ7 D`jn#N"zR\Z^Š.)G;MK#%[CkL=,tҫ"Z6{feQɪruv۪|̫η,.2Wh qIWrCe\%y(r]yFEʵ/!vwtkLߋ @VtZΪȷV-|-! ~6V1wpl[Rj3v"1O~wl08uiaԗ6+-WFL^|izੲ4;eSt/Ʃ|^2J2/[7̥#.XX'lvX$ѷ$cHDɖ "S}G`V_Ү\Ͽ9I)~{BOtGZF6mZMe %]ԓ~+-~^]R:NR^ ;.UTs;-HwCzJQ=ª0IlXԕhbnwZ._nL_48L(oϠh^ZAo-U#N9eaL#ZGN0_NhPYXj;BX>WF EjMPߵzZ j'g)XQ'|4Ŝ:sp\e/yNږ2_?SP-Y\h_t(z? cFEOPZ{^nyE/YXO!@@SUšۘK5)%Rlі8,qdzO ( $"Ϗ6T f-5湫u*q@ЮHJ6)iSOGӇ(4/i6|ˏ^ԷjkzŦbSrN YT&ieT )OQ)뚹v9]EVnX$vM"!&}qE݇ck]Z!Th޻Q DV"ΓjKOME!c:Y(i5M4nkR.#h=ޚn]B)u0*j.AH>7 /*RPNPߚMfU#V$l$5*gv:+{/YvV_z#XQ -g 2G)}Ǟi)'A#`b&`*:[X]T5}rʖ/ϵ>u)o{fyg1HJ*$GժsHJׯJ#s,1YQOm=bصTXg-Ufw*o lUPib>U`[iI.4 ת/ .>vq{&:WK)xm*=Ɨ" u) ҏ.uT멥* wOϵ-mD6*3Bsy;drCAQ"Lb3%0O % ȓ?ǾcFh0M|~f-M~_NYܾa.t/p:!}jO812I,]$x*'3 ۤnj5f:+;ӸxM9[ V;)PAd u/6&A\h'ƜsȮ0a*ԈԪ,p4~`ΐhN~cdD`9 O^R5+ F2"|G_„?k 9!,b6h\e_;mti |-ADR!oL4yvԬmaigf%h=P-y+Ƈ˭{\-qjvrڻ <>waBʼyU&ٍ%y..!O`C~/{ܧ稘d} VV!4:z%({D0}uO&!4&GVkgQ3K?~چ8SL|td.OJiPg0)Tr-+C롡=@a?yMb)%z۷[.?Fpl*u>'RwEs^8.Y|H=E)9l6˜S&̮ ⠢WơuY7?wܮvizxNe> ieaMyUg zGqsx7=^Dx4j..noNRtCdRYOy]zåfV I$gPYzCv>ɠKM('SP уѸJ.NZVA{˄f5}cyDECPэ=uKB {Y1JkZ5S֚&fYr=,v/ڼUQԅ]V%wҘPNT7>d<"^;4ϕ}6KGd%8pso/@z3bK'תc ,d$ArVԲ_rmvVIHeQؽ%΢]7VQ.:&MP}qbF5j57D7E=zS*q shb_ 2!ORS:m-TJ[ڶYqXҿ]BA0 Xs2/data/s2_data_tbl_countries.rda0000644000176200001440000040323314530411473016457 0ustar liggesusersBZh91AY&SYV`%@7}֨[L3٭eJ֕Usef7wivtehq^F=J{)ZUihuݴWv'lzwu7\fm׹緋޻ޭף\{:B1VG9{TSrwCnΥ\D]{{ɽrz.׽҅]u)ק';Vrw[w;jvz޳7kIuR'éR@ A=]z=;fWC.uպǻevO'Tz׬z޺fӥ9LW{x;v˅Z CasSRlB刚&1˷T]sݗs8mݹ-57׾{6sۦg/^w;}ek93}g (nm݅vvN͉+KсagR;۝Z^;ۻ)u8єb%lf ht S@#빀)@:ݙwVkl ]SmE4ѩ1ʛ.Cff[[]tڷZlQBEU%lUm,FݶZ)*vDE0K-u-ΧLA\MzπPhR}%-lQMMr٪ujJV:R,`WjʅEiHDIMQ*m]jTZm֢mpҦ-d:TKF]v#lܫ +fXJ4hba@44Mѓ@4&OL2``##144(5D&&4`0#LL&A110&h 4AA ɦ i&# LOMM4ějzd4)2#24FT1I&LhdhL M4'ɩS2#M=F4Iɡ#4OSi=4SzDƐI"&L24i4 #1M4ɀdƓd?@hddѓLCh#dlF0Ѫ Id!F@@DW$ d3330C Wʚ7Z@LK wB.a&{N2d99l̀̀fB&0%;{iP%IT4 31.OB&%O>IrdCE2C*h ⨲C dw9("L}Z!8T+ 5UtP\ Ü̆`ff3d0̙`,fC332Z62Dp̙ ̻e*n&]Vd@)ʔʱEp(8 aU6E12n$I"CAD g-nwrJ7,^MG(T5#))$O‰oPJhKoɑF:2{kZb(M\|#`!IƥEal'cʁI0 1Mspʄ/z3eJ1AV=4J L'MW8I[FL>+*Rf)Rju/! ɆIa0d̙ L L2a!!=2@ϝ8a2aC0&d 003202f2Ha s !0 jp#!1`. &I$ N [P0 O $Np 0QX=-}CX*'|9qScM坋a8]xYSk'yoxQqNc`= 5¹6GȄ2a)C ep?0$vCqk[..0Qio7IpGQ`nX[;Äo +~K-|0p̽`FcG7Q,#j>Gt PaLUbF>IeEl#gHit?iVҝH6e&c^ Psu8w­onKӛ=A( ^8~w̖!HmG.*1+p.g&^oͯ>)̼]0[*C[YCZ6!}”f8n7NV5K 8%3Mx.8,ɂ&cu1 T:]P`f:(N PЌ]Y$$+lN l;39 fa9Ü3Ng3ɆLrs`fag9 !ăf`s93!{܇sC3 330a9g8wlίŘ1hNpth(}mp"hSD4A32˜S10L%!E !Ea$$ W @ Br$R!)WZ Vzk}pzT8Y /zîBÜpp?7KtbųMP\h]d.zs)ϚJEl{E2/D?VymFV<_g'FC31X#vG4L@Pa3ݥU[sG,@TUHh|9ČA9g[, <9m>f9dD,<S8L|Zݲ3@ǶKY o9U)_G:K>Oщ ǝջiWnsѠuؠ;@ J/1t"6,`c:*#FTx R b؏#GHhP4:~DTgd)|FEf:EZ=_t-2<|F b%6,t! acwW uo}'K9"s1'+G'Lb%)ՄƠ$v؟OLb&%mr?Kj l':)/ >B3 Eme`VoBhmγhHa A.Ąjht&7uӸ8,XiLb2*3ɗ,s*tb؎`1 և;QLd%0 s)/}1Kd%=EJ9t\pUGɼ * pBI8L;~Иe1}fY\J̏JSwF+A!$Pد|=d'_p&=EC@y*D??K bN= wJɳ?!gm~Ft{)y81`-. sAA):)>]%@]$~EoCF*SW%k*,b?Bca'#Q׾'=g\H\|Mޒ|h< 6s[f}rnq cs{+*pH ǟs5ae Z)OiM_8#* ^Dr kԉc̏$gbc#ݎ"DH&ܞC1dfB޳"s/Žw"ƙzd&| m \pmNf'"D-9VhvKmgS^T}y^Za5$2Go ))/Z_o["],{'OVÈ #YVSZKr1nUO7Z屆#mILي`luV=Qw8J9mB`F}:LB L_?D! L1L aC_bQ2h b`:m77Fԕa3(²J ަ]Vk"{=d}a4ߟ"GB"tO +>+ *2y=h(o#H+bdO/ؗN0ul,ܜd}9Ē<³,qbޑ}=|{ޥٙ[谱#kIl6L ,Ês' PM : +uH#`OnTYHw)+C]4kz$hɇ*&_ldf%M) 19T4FHNj&\nDpqϐᐚ:5XQ6A d!4$r*J A\Qi =%uAݱ4B,4IJJwF, (C&n _:6Rͽ \1ьlն4! PyĘ3 )SP?=U&C:=P?g4fONyl=ak%mJb1}p5 O}ƷpRk$"$ (_Q/pdyYZ'pv5! mjGz_ۼyEJӞt|{- dgP%@h@a|Ko:c O+A[glEYبr7TԿ^j&piBԔe 嶇 3IoN)i8N 3T](2GG9+O2"ѯBIFXRO`P1YGBp&3`d ; ֘(祏 ,q%)%M`o-Ƅ-ԒYi[BZJ5ƘySFK0rЙQ?)`l*^rd8)iL@כBq_q&k%x)AKOq W0w JUj@Hݐv~lTY &=DK'\aN!ú?ﻔP F2eϒsE},s߷5LDJȁaXw`:߮CֳÆnfkCbzG#Ŧbeۼgͧsަg"b $b.TLc3j^{Dv{?7]>^˞ FqFFI9PWxw1\ɄжVon;r䒉 T8 D/e=D>c X%V)%:,I)h4}HMsa26T[FQ>WUyZ3+qRsH5c^sTG{8}ѱ1Ǜ̼fHt׫}辸oovcbXtR.s3ƐJPF,S{IESd8򧠈lL F'˯F,ZA;Ǚט#!}vmH+U]gv60D/Et3j 67k4Z'l)B2Iax1.W>(C{fC…`W)мƼ;<zr:׳[kS ]u?|!? M ӁG 5ʒp|bpꪟSiT~>pݪ̓#)WT`(,@.YّeXZ]8^3ٚVFTr ڝrr zphKRF,D)[N*-a1 4#]R 諭5IV_ g e~z& 7b0ؕ))dY jH66^!аfe&85W=ϜیITyz;$5Iccֵs@x4Qp0BnaC4 O>lL8#bJӾE}hH!$_ˍK`lɬܘ{ e tL|7N(%7IKl<U(e<1q\'{~8BB`{z4X4)3{N!vۍC0㛶c$8)yѧMHD1JU;"YmjbW5x~& Y>,Q듈2, .4U˕j`Pb lY YC'jAZͤ_í 8A]=IEI@i0&D|nd2l;wlse Q|q/l_siRL#[hO~)xpmcF){`jx9@ ▫EDXo+I $ k*fe1U {ARoHv/]ε@IsT#-"νϯOQnkrО{6)$2)8ZE_0v&FT28ykd 7r: 7_PLSY~iCMޤ2|r5K-$d 5\ϗkvY-Cu/|LѬ\#J)ܧyr=ȭAHO-X5ۙ֋| , ]{c%؟wx,=d5+Ջ4+334%>.|.p6 MJqqz;E֔يR"h=un@UoiKwu+qj(ԑQ-\q~GxECW} g7="ɯ,Gh,EJ#F[-wcVE56d#Q?z3Q'$psf̏i: qwx9Àw`K]K$ Â#KOfʖ;/=SrL= Co ddĦf1Oxx9"SC󋴻{e7?'p_!S veʾ57=~7'|b:xbjC:Ng }S i0Ҏ,3^&A486dQ%,S}[ W8_R',(۴i WMhְ_,OBWBL TLb+B|Et?Qp'c$;]4ϡE"s%KGX³t.Dš6J~QmTywé˝+Ra}nȡ)1 g% (vz.]C#N'3fZr- .#@]Hآ9B}I9q2$`xu}63)펶F,Y̏_N lߋi~&?&nSQxd>p~ ʼlw9`ֶ̂EϪ6UTLLyHB$+}xnI$"1Ec&O 5;=<=̩8#t3fI >#ttR=⼋+żz3lI fcLWIozI'cLVnrۉu42-l]җa Y7,eBd&~ ՟Ϸ1z1Ǽ_?O졑TC{h:+Ѩ<&eFmBeZR3ls]usȨttܔ#F)[,ãt+<#Y} ?%#~.P7 ߤQ792:] !HZnӘ}^ ;1wU%EwTn `9&$;(t5&Lʎ[6%lU]ra%0 v.IR(ۚ>^ʌ翴$e dHN.{ܖ*?06{=_.^ SmF :ms0¹_rI'q Q2A6H%:*3;_ I}WǦ638Pߊ\m:A^ky-xz&1e#&"LdZ)_H!eszim0#ذE8\{Q%KA-aX݊{;nGW>*]K )1ww lzT'Pd|aLq)Hz>Kvi?2J47$BXL+CF]_=%X !6qiM{,_x=͏-LД4i14=ٰO9[u0Gxb"nssxi,EFE"nQ>a5)}(>B9!-FF.S W}IFy"jjr eD&|3.]y=M! c3Oر̴~yƥmzKYpCճc{[ pY# Ú^4`We>!&gf2r^Բba3ŝ8 [jЇMi!;9& 0fD%8s?'#Aɢe{+|}ZRqS&uʧsPUtisM6* .ɍ-#k/umoߦc_/?_~FY"H.ZS\Iԋq>[l|n;1Xį.#Hl*Z˙"$C!DlHFSi6f PYp!X%IeqQ}x+ +W3| rٵNӲ'{ӝOGPrtTCyZJQUsKl #ߏೄ>&LƋۥuz' ڷA;oc{?dC"_=F{fK"{(1=F38]ZoSm`$WQW5R^@v'sPU )ďi;G] G%О3.4w /ԎKL>K:vJf;r K]1Lm âf,οUv amEs>=U~~ F^oueRT8cNz//"?Dƕ 80zs~IyЯ):i=п)HP Q6%3w8"zRx: jq$рiL0[VES|,gl1Rz0iyͅvf?ԓ|)ˌM &F@&osndf(6]]’pR3Tg=|t"B!Gl F NVLmR>u~ kϦd+*FWTHWGD!Rv5\>ճ}w E-wɎ[P"E~ȳȼ ,,(ᑷs~jP&&way^C^\spa+Ndm֎Mٙ2ڙ??mU5Wg%{b3#ۙl*V6ެP" LTs]#kQAQ'Qc4f>G;55cɹHchKn*De(%oa*:h,VcZ/vjS_Yl ^ -,F A@ 7)8*R]$ q[yctٴ@ }rxq'#ݖhs<HI pjnLQCH1q+(PJJ)?O Ƨ&Kz(K#*Bv"9,H$G\.7cDVZl@e{s喖<H8#n~xZ/PL3HP4c4UM2a Ufcz?s+gdgQ軈NOPST] ze OW &P{0%1\5L:9 ]دt`3aa GFpSo#& mL\\]ƭzb–BSk~HyMq&0%*z.W҆'n%Ǽ|XX+U2h AxtW(v>~U|^ryBY3f`n/w0lJf p(G_z|*k}g |sw]4yfpvN\_KQe_CL!$"ZTp(z[!J S2|`tyrvhJ04Y<W?2ǓHcIeD!_0'5}/)۱>|/>Ӂ/jDsAKlbFAʎea1{Ş#ڱCI$L!wʹu `|1 m|/%u℁Jji٨RJ[R;)6viZ aTz f0=jkxN[d=-q$GIpA(gsz~xd69j;/DYsWb1vDT>0:0wu'Rqo|YYk$ԱKt/c|.!НWشwp'xwWO`!ĜLò+Nqs^{.3f:MG(j=5kcn( e]{S @e/qdG#F9lLv 㵾?NE90hٖʁZyNr'NEIi*Ubc U3#2@ Urj`]8<mOجb#;}7R,6Fl~MC(|})bT|T\n'BAFzo 6щ"?lZƮ&CB6SMDԫq|H yNknl s~Sb6Xҿҷsz"B8$JK+~%36>Cx &㻉5^ ЧS1L开.Gh<)n:]?DITiU:qD-WҊּ~(x=D%4,Ao9c6zE5eQ ȅ;O8UGfscV / 9Uyy[`c$o?b^BM.H05gibHN:ۖ x/CJzW$|TSؔE1ot*òA͎T@/8CcG4 6ar FK!5lnGT{2%=%~TӪyw)txd?uhU?;CTNX뉕pw[{BoMVВ2WZ ժ;؛t'Ms.FhU0Ca>X]<*a8IS6 n.+&Dq^؞v u-瓷J`wע}LH*&%%ۮ`K =|(LCti˪-O9_G`- (ܫ|TwrGC&l߹6{O:T+6lo~7'j4$0~ek3<9a؞m(2ϹM.L95.)F< sp%XCw:x+].Q(%ѳXyő<+jIƗ!(QEw)~.R%B5gԵ=>For).4*oW$׽ѯ;j4׭M-t!O@z+Wp]T_3rYؼP>,ⷯC66ˁ46H\˝(&92{;>Ѥ(2ZIB]챽ɣFƭ¤ '/aAF qYeߧʡAϚWC%ۧL_!=6FfIɃ ff??KQmݯҎV&%yr iԆ|[)DvD-/2Ӕn§:5hҾC<Vf 4=9flИ<]g ۚ^)ѺK,g G\+sy6N}wI=e;^ZLzD\ѻyp; T"4{"k5P6|΋fn js@1"jjst3Y0Gp #\Οxn3%#Z@Ez7b5Ir[Zc4Bb72C\ 1,]&˨1RK1qSkgED{eƍ} %AG)@߱vX>_޼j3$!Jl};`n~b@S ĴAvto6C/J,>͘%Ke뇵"QX:!^ފR-m5| w~\υdGȪ0xMPY |h$/юbU3F(pT!ZPPcse85KajrGBT IRRK)rRnQnx>w]{2Y"vh{4cU_FG2lۙ%N`c@I5'C::P팽)jHD&"u;5OY(q;6VG6̯y\lםhdݯ$jLǀs ۟@c ^bBJUCB%eCM)EE7p:6K 98iT0< Jz;5pNp<$Ͼg8G eթϕ~*sw1—:u&jSHHRG)L0CL)5쉻!Afפkxu{_뻼%%{TED)nθX)۱xЮeX.Vcb I^GS;68  I"+5L&#Bk}hb32bpLXKy5ѱ]Ÿ1g_ [6?UM%!tI[#'=cL3¿Zs/)7,,STE5L(A<$_(D#Yࢶ2v8ϜSzezM)%uxV,%2&鳇.EwMNK-4+qQX6Xը8_*P yY(_vpBOjy\+4M {ie)e T\foZl 윴x]"|\ypܵ'$-Ya9Ej}3QOd>&CH7py42mܥIY_~>#9 >.,p6K]%tΟlp1Bbn]E:tzܦݯo*|`va]oC*I 2%%4N gP/rm.>EKm  r#YAqDkQ "Y>v*zҼ6ڊC tq.EzK6f+|F᫃h(1M=1_ȟ"-?Wyv´i4B\-m Zw(|c|]AhCogɊu0y?QQKѾ̭ZJ Ho+9?8 9ԤěS Q7̝um HtT|!:5n2M/Df2"ӄPJj3̇첣.MHU?}s)T|>(BV aI^$2oq,aqwBrl 8CI/4d}f* V`ē?g<wSdg~&zw4[B1/{lgx(,TNm4`m̌5O/Ö*lePf>V y ,R PnkW0l\gŧ[C8?QxہHaOsI|w g^T{`5cy>cg>N*+% |]e(;(n2a _9rts^ͧI 8x%JI$t}Lv.wyҐK!-X|DM{1.n5N7f '=i6*?ğy;VGB(c0A=M36/=C4C+k:P}'5?bZe/G _ٿ&Erw|{cX3Q~wsxO0pY8īv9?7b!!O@|u?s_b4#m{<&~KV/&e'@#.,0nylVV<0Zfn/!Nt:<S2%qX"((s*1ξZ% z6D9<VGc cr TG{Ei%03bN 'CvtRa?xyfpq`q1E鱙ۇ9@אUwzs6Ѳ&wq"%2 ioRbbMC gSZrʝ"`kK姾W Ny$foo,ep 1QnB*A&z_ո;dnZ%2+4;3Tp [Ƹ/C5"}%Ĥ\#SgI2r^&,;(tLJb:u_Loal>ԽINDjuLd_ˏ819agFMU]mCrZ=.|Ck:J_=8gB`+6-gYg ޤoT`υ4vHCj=iWLL-{)E[̗GԠMM&C30} 0)7?K0O2xaxߒsD2,N{3gEZfvQOL,Ck[T+7~r{QU Ci*cj5e/,$@?k~0pyH?YPOd+}5nva} Q{7zߝ;[}驗048uxoHݒH "АGQmOÛ{c.H(M4>i]8\?YW/29FeE?[dP{ċJ|apJ=MZ2M(ԐIKsvY\@^OX't4T]6E? @a˸X5 %6}IV]jf\;ȏ)I!/Ïz*D,I;2GP'@3Ko0Ӣ1Q\0q _!Qx'J*KEq M٥hoqXF0\UgNӬ,a Ma1b_ 9z樲.}|XHsZ+둧:}` 3ØD”qor}:WȲ&m}8: "z=h YI)X7 ۵ѭ p .A5Mq9M(d5~(uisHkە@U#7exs`0Θt`62au1`+u AQy0LmPXWF ZYZ hP7; y/F\w/Kq=K_O$>`T۳ ap/5ĮZ8]hXzi= j6;~D/'<J?7yK_CS£~F?Ms~Ɇ\tj:şM?ɦ|t_^ğmG׸pGSfv#5*~xQ_1;;_Mmϸ$T|LG:gl/,j]B,swD-КÐci kxW3]fv}[n$6 bLG=V5BeDLSAlovĢ9 Z|{|]4R&}[;["1}4fRLT3*VB  jl7S]Cy?9T/ !͘q.?eԛr?=#Ѿl$-c=G d\>ѳW!t$tWuH[cXPqPsQr5VU2j:]XDvb;R9WҶm$vIKIJ;4ny3ēNs[(+V2?pkmP}(y:g 9Sii7СS WH#A[IiM0Ě֩wb+G #m+F~Н"(3q*&l&j8U7vfAYjJ9zC[1-(*O}R%TXt]h.z pVBOLܞht7}0[l3ջѭB+zK!4(^DZ!""H2;eSb~7tdmre nGY;êBS*$c'axʓ^:8/Yyt .r* 'V'l#_MsyJ=p= 6YT4Sp1͢&RʙC:\|uЫ"L$q.q+\1x}1j~hC%mH-of$F(Y`FhsUnks;Aah'&|1'EkFޮ{~%^x]y<7ӊ̑^7E'Ur(?y@4>KߋEUzt 5yM%akհM@F ўBN!8 xkF $WEÆq-jkZcAZ>Q4PE{%,>yɫ_ƪjiy iƆCN~6lGz[vv= bpR2r8y-x/z,~ЩT!Pp՗հt4\;7_D8mmΆice0UwX0kcfI1bc(0dU&wY(,y4rڭFDw(ֻvv[ioEUs?eSG:JO$"ԙn0Р=kxu\,Z.}(u+&>,835aaT \V%F'H1nL .a 2x#3%d#;l'8\sR Gt=(8v1o`쓛f-ͭ$3~:  0tO :g-w\q; N+s7z?<1bv7`s7hitꂁ`B(drB b  fn䳳X]7!g"}7Y@n Ȩ6,d˄;bY/_l[|>"L`;v{rqNf _j(wYjW4 J\{q>GTb2TėR,!tLuYq_xwo "{܋ #bRxsjo؆FIcw"TUNa|t/+3dP(&9?}/_ST eXrR<ɳ}cڞ.QY:f RV5;?ɵm8JV&ۍH\q~-zqh2Osy(ԛVzNeO[u^ÈĐ#6epus5E/(1p7H v; VˇhY]i:83x g~yt`"5EKƅ|$EZ^D֟RGaٮ \%NTND-PD*;b/H4[+՟c<1^pibkQK:3b$>Gxd9uc | @b~|FV*3GMvoi^4 NEqdmrSrt9"QZ]\>%ӲAO Eb$+U7WޕY woQG1vH-(~.3%F=otIpQ'0;)Z $C1BSׯ7&rTzQȳ5sA!K]^e7mRNnu'9 RPIL0  w j-&P@%Y|Ry>[{Ǜ*c1#daǀw5]ᓖb#f%êCt}yedTp\2ԙ#ccxGTe|a10 V}ߙ/3G+nqmx \M \ûμد,PlH%ҕM~A՟TL~s;]{z[?@B_p "չNkox\8ؙϦŽ!y=DEZ;oc|EeP;7 %8+R[]j:Hgw9 THY)XKgFwCxS*a0R?ÄbeHg-9  ?Єj:kh4 *|o.TљԓnE=4"^K~b&>3@F_rfZml=C5.58UϬQN52v(&R Sf0zjB^G\DtAV[(Ɣ ϱZψ9}RhT;H%Ъ?;-sr 0r1a CV"r`MV'\r)Bg1f I dǢ c2,:gܚ{S 웜B ]+&!w.l,w]ӛ3 8y3B&)(T2 .%;cNAv:o{r7S.O1(urY\_XYԅ֔^,rȘM4se2D"@7vTBGJ:t[I^oü5}*zs$I,Sr.6LqOH@@sHvL J7[i)ܟ]E>qPU{\Ha8s(`Dz<23G몏PV5?QY)* z eyӮW &5֕wЅIY?oJ~ #:pf ~Pwuh>J'R:IS*4(ރ0W#5n_ғhJ/P>ghN4B}PY5T`x$( YHZ5 |D_e >VFR;85E2)ѮڸEGSUt45Kw{,熌:igO;"]Jx)q)yXFp#rT\V0:hBwruFsL_<3/4p<(HL]ĖPr"8,Z; k|S`DT!Pg}/\1 (4CV)~jaOG|id7xjD%[PE2,Xoҹs]Zq~,hζt˽m[f\L|aM) ɝr\X=0`ֳy.3m!~2¾i3tܡQ4DdbجCBLO«1p'7*͌bS9XPxH4gw=u3quƞ1TN_ :^d,E躗]8 h>fN+k{,|=]J>.ƒX.Ʀ^vQwFڕQ1v'S_0aMˉ8%m[, vȸK| V?LScJIB} r b?%noŋYWȅ>9kAHf227Vdm:cIvC2v2$iI! ͖gslbJk .] eRi?*}p&|¨7J8/&Q5=>p/l-P(u9/!ct(Uj2\n\{RʮC bVe^Ĺ6(kyOpЊIk"m.*Rv7Z|㤹Ɖh2dŹd2P9:q#\9X$%C<<U;EsS\}(Ŏ-@%ALMq#Gۏ!#ʐtvCC87iQtiR u9~$8L{PUVٖyp8uPCe|zY㚒EGO&H R0&`Uۍ:Y=g$|s =ԽZ7BԆؐ"fҕć0PR =77wmStzI@K6v^X2Ξ'J _<]uFfH. (0gĈ*Ύ@`J[avlg2`4Jd00TdZMPؙHj  ._'%V%r0wAHǷtW KK+vJ!YD,XZf+|=7t>,3(!.A8 G$sb_ߖ6r̖0MOqh X\=hR[ģt +_ ^Ipاj;6`ƔAUJ UJY] M(De-eihw?}, R \gR>(iG[J976Al[;o0]S]˘>yq[VQ4V<@=n*2QE}`zʌpoz|8oFpԺ2mm;Yj`|&d`'f۫7rH҇\/ՈpR "+F Q+ȟ;etgbWK 9T\bLoHgf$t4L\JĸV)-MyfL941[ૢqm&n}n& ^GNw-}M~ik5t]pF`̍|q#O>; />YMzl>VgQ?MP>z|Wԍ}RNV`p_o*j#T^@( }Z>'ʮj\oو&oxfT~@t̐l~\~x!h,=Ukjk=5tM0) I;"(#bR _jaAA |5?1Xj%O)iڇFTzKI#.$  J^ SU8r;9imFbd8ЙTƅUɂSu;#ǂB\y"X9]̄&ڟGU)'${r^42Ώ0dL*a C plDP G Fa^JkB" wE/8 4§Ŵ?$OS'+HKWfH-x** 3abJ 4PtptnȚHeZ, hPVE~L0ێWOt[Q344+9g7lZ̄9ٌqt~~?N)iڔ^K^mM~ Q'56cMF 䍍@e3E"ͳAH῕%Nu崪ūV_*%ix5.ؖ֞⠩'ypaa4$ Ԕٝ~X\<*3V0E>;DnzKbEⲧ,wM!=_0}Gڰ$afMa fg V?xV$cxnis~|>1dԹL9pT}LGb8sL1GOV\x=w 1vcJNXAe{a2.m;-g3p*(GgAGܪ 4Ф`Z21\gJKR%53caZ?)i6leRFW4)j~ًlYWC&I$GjD;,ACHPwK B1/YU&LQ4^EMv9c AfCfGvdmlM&2t\P뻗akMk6:uhݸvMjGcAl 3%2<̋^+^kC4CрjW ]k:BIGe{͈4=!2xGrfS}PqMqsg= 쇓p-z\\D5BtVcti( < }ESmV!46~yxZ4 p9C!̱Iod;ȱch|K42( lo{^5~x{@S]b\V ^Fq%뭧uXϏ厂diE[/h3#%pHX .'.DIq#YsZ,u` NM9O~FD$R˧xr%<'P!jes@6U}ԒX(땚hMYL|γ:")Ԑ1c"d Mq א?jӄ2" 6jnLdzK&f{Y{(s!Ѩؗlo x?ʞGY;={w\Ym]fʖn/fpZ38wjѬ'Brk-Xv5r>vM?uUb~{ܗ# wk[.6'6mU=U"8p9g-ڸ+k=J/BĈc#),YA>> S2f&% e?/1JFwd_9X6x[ԫ8:#v>SKk@x¦P-6KmD"\ ǘzCkFGC"6J$,(.;@x1fjoElv7,S={&VJXKmW+v`j?ammoC#W3eB+C{\7J'@8ڊ$P%vdwL< ok{O Z2q9b^3n[d?f!,.,FЗxeP `_pO lGziFE!])-k)? Z zFu ^E ^X[P2S OK =R.f1$Zv@ٔ1Q5peHMqM*lW|u 03ts3f 96νMѳbՓWno/VO?ޢHuj]ѠA<niTrAǐjxHx@p[%~3Diz-3 MWEQy7eZ P]k2C#P fS`]WZc%~PȘ̐|N*ӽM"`fǒ'xo\k1 QyȆI.ߗ>7 `l>eT3Ҿ9ãˠnhS/A|=!j嘠.GR`6W!M?>ϛ[3X*R^P;Oxv౜w/5jġNq}H`޳4utBƐ#}v[s{,L<{v (Tce'Ol W1!BqTrABnq h =Ko~2 ̧8b5qDrHsFBvHA8mDFLh}4K\ݙSB˞ZaN1QXKӄ\NCK`HW(:$LS;pHetN*W@0U()Ρnv|A}Y~skM` F@-8w+}|/$ X%$" BXF!sU0L7FR`Ę6cMxu&3 l76EgS0!~:?\o/Ÿ 2&F"hYALW>: ?nk>JUB2TPB _MB/c2Aӵ +!o"w+aQHVa"B izUGj8 ": op]~s,?7e}D(MȌf nDIFv5W |\HCNR!]V1D#Fܛ"1NyЕ5+ ٲ3$ j%ӹjPA)rM GùV+de$S6tp"vDBE}o Ӕ`&t! Xtk 4(P",r]yQBhG:|'th3]FIiTiAx3bǿďaPE?>:Nr  ;U x6JљwBCS 4fj0|+D<>ngxund j(:3#(oWй*+H(X hHBNMcwG@i.u}&8ENف&UD0r س٭L}}Cqۘ驏x3aT U5 r*a궃ㄋQF4V%v=:8j78?Ts;QqojzBeD@8bxM@o˟Kt ?v]췕l]#99Ɉ20:"^(8|V< hêLFrb\k|xC_ ?o[p'UDJq֭pn 2+ w/8 وY1"kJSҰ#?yR q c|ϯmkЛV'3nlyXTN.RTR|"4 7[3UE/;5١=[7a,4WH n ?9F_G1hn:[5j7 FsS)uF՚A[7S4-Sߊv+h)9lFچÕщ+&2ujɴʌ%ZqxUgz-2lLտ.O#k:~۹Ph>!lAi b+@ 5x88zHxȽv!&l[fwɷ)DMa)G 1b] !2&]wpNL>ox[|\y- J =NY.~1~bMT|y~A_= @c[xNFLOi&_C!JNlW(N#X@tPAWx`ʼƻqttD1G:Ot4vUN9g3QXSXXZG2Ҡ8 K 0BDOjCd%| ʹe¸"ҚGDp)w]PbbDШ(]Se>iJK-7ty`aQM !

Tz^_\Q;[ccgN:qD}Fr: @ďB$ [iW3^L{5?~\9#y3?PwdidKZ # D"(HWqy@A3 uW@*Oid>gfs~!Op@8wJ/OaTZBqx6fH)WI eDicz8 ,S҃px%CDJ3Ha#G("8M#_(/@5aR.r]}B?Ta0 "8`R /wHVAF+G%!ˣ8GHFij*O $8Đ3$\N.E bG.FxN Ȓ z r Dqŋp̃/agtyN#0mmЀ?)`QBB\­L*FS9%ȝ {ZJE/X8 wz~L!0:F'wRH~-tihcx*M< CA_W(LRӦb ~:>)aca㪻'5&ᶑs`oy"!5-x%ΆΧdJX=u~;#AĐ_7hx&+7/ "@)i\m Bɝl٪o,AӡJdELAޤ&lDI3(G!ڬEAA;I[Bk"_>J5†B$.D;yJAobyc0R h (WǾ?H%bFȻo] *-EwT; B!P; :ba=s[|,EGh:k_vCm1~(@V#!H|!Q.Dx.9B/ U1 7ɽРJ*1q 4 pA$v#VAԺ7Ggwcpi9E$E^aa.?tv_Y3側b=xCwd^']u$U}Ti#'-$@[Eb,A99m(W8Ŋeָ/p6Q>&pOb#< ؅hA* ERux. 4\BrciۂzBg:#rD`dw s6]^A@8G#y-|S} L>-0y+Rot1sd/N&vHkL0Tj}*~8@$}ĽLߊs*!Cs4QثdP9;v+q+3>;CbQBP@ZPxeaBC4f9z`xGIDNIR?NP}1 JU AC%M80I-Bq|=h՘{T;p j|HG2Uz N3<.3Dt>LZ// tQ[ %h7Ȭ&itQ2|J0iP|x⚂̱멯|سZHFqHd?D7&ע| ;/(5r9  ,؍\(Sg r+A5cMOoY֞M % 3${EZ"KSJ6JXJ.Ep0yWZrj{8 zDÂtAtf \&8lOL;']Ai E*Ea%pPq.65!F׷O y(9Exa+{i׈j[=b(4kT -IoB&}b.֠EM$6ec3" D4)'-!"k[gidXDT Ϛ6V=\"#i3k}qtk_6crnHKl1lN|>9X'vXM 0z9=.\OEBs U .zU }<گ"h岏3֋-a0E5e-P< HR.=՚9. hՉ c$7^.cwLΧF)8T}9\bSJ\g S<1CP KpNҡh'Oa!ZS94 "2׽9DpL;fXL o &H } |z(=!u;"H Wp2e+UO3hC21\Jf1[aDDrUG0~.X~MwVD9Fg +UÎTQs"$F-$ FV-mBP89SrGٴAO@H2sRNln 7wO93:%$F@|]QmgyަuFLLurWvۺ.^L7b*\Za>5W P*~E^xUzbru"D<.S *1B)TmW`tM :D]0",&B\ɗO{E;@/\"o39 ;sPǝ㛣:ÀTi3 Z2 Wgp19AJ>naG1RQ"~Y4nF:^n6 T]5LZ5C,?Tw4ˤx~8|U1=ݾR3OP0_ U5JvVw⒢|?sy猋?ECȂmc%,dR T .2PhǥJ]@ &##N R[ Y,͚ CD"80THmqE& AM!O9PpkAw+;70Gزxz rD•ݦ1d&5p'L~ S6I?< %ϥ0'q-(LV@g"PggĘvP7ae~;x5Z,2D?ZbP^9uOmY3d2?K'aQ:؄^"lvo+ ̧AX@|:P4q @`T_A͖=P?XUF7ό:rV"YfyvW]!>uv`+X5Ξ/0-M}rvo Z"A_b@hOT΍aCJmӼ _At2@L8SՅZ´TPA,H@ ;"o{=nT  | > c qZ(sa.!j s؝D `/#Eߖ:ľqRY X)IIn]ua%i /": N6zED^s6'EDx6,jS2Ga{$AKsx<(ބ%V3B cld .^U.DV0AћL Dl#VAb}qa(;FO7ĉ+1a~:h:3 ]P;xv ȓP p#K9#l77w=5KVwؑL`Dgp6ك +&& V-Q/[ĴaHNA93E, UmSUB`)0bL  K t$cq-~eA=B$( F"NuBNҠZ`wo+qbwT2C;9xL*72)MH 72PI1xS7T Õ1̼j0-ob)&q- x5c$ÚA7A9QaH8@8VpN |` M V|$M$HtceSJ,/(¬It>p xcWe"#P]U\ÍzJ)wZ 3tw  -FHӲ[hQ>G^C9DqFX[s8'B[`B!,#Kv#:%U2ϔClbx>Q(PTqygӜ^ZG DWT?hD5lw pާ_vXz ]aաXʒN=@M1JU}A[[Qr n@r܄im_Zpi}360fda.#Nh`ft[3 Ni.0iCA}بWEa, R!"8D8h|D8 G\Lo7<'gu퉘BȔVyЫǿC֭7…8^,.R"$8)޶/@ĐS-^%ǺL@ ^bN[tɘ,vy Ȑ5WM:@ dj`TF`)r8 ߮xTTib#~@!oq"5eDvH0 {?l@s&Pb.x'H)3AӬ\"$|秶-"%]Y/z>H/yܓg:): X)XWMƆCJ;F[-oP^1ДF BdY; Ĉqү}*VT`Psؤ\)"k * ~SpretC¡@f|j]Y\s 0`v0$%Si hW>!WgַfomveT{ NthH,>b" cD"f=bx?FVrv[{Ri0f.PA7#Lotu¬pEZfivpEX¨2p5As[%VH%+M j#^OO A) ,`S!$,Dvl!L4(ڤ/Ml]%Ժ"b#ч0e.ڏ<[QUoh&BhB;rP"3sc.!ѼQ&Ln VQEL q@uq֡Xt`X- 6_/5O]zX"-J_[RLzU8ۼlR#fBPvh˲̆`tWpqf4l9[|Ք%8>L@!meHۛ94a kf 2`I`!0&fD@gb ߻gng i)*9RGTI,ZmKNûWd[[nF:+0{@/v.&6{k%5y #d9z!(mAl6R0W4y-BRTHHhZp)g5ܬ55y%iMA.s\`jńNywocr%$O;C I'وd_DQhvG u1vV7<k0?~qPz3$(22#,w"#?hs"pp l[1ص7amwm[ ݛ_˥숋'KY 9?v;ѷ|}l^=ri;)O-6yx}i\?(ChM"Xô%/bEBnF6Q{(Zo:8+:07 /CrKl!?m-Hע?1-C X¯m*ɺMB mq(0O%=x)2_#IwWt% e]IŊW 9CދN˴4_ RWĐ\g-5Px-kQ)7sV;mKII;&GE1Œ,}{j2o3V;q ֆ n)2=qgyL VI~ 7oP,6W{ 1;ҲeB7I´G9*˟e=u ځ?v?7Wo)4fڰ?t :`CюUhz[&X\EkJmnUh쯋dm_7zXÔVW맺7dTОeF4wv½qeTlKýBWU0Ays,>/Ckz̋Ώ4kR^gl=I30$\,8奚=۩rAÔC!FasOk'K\Hf.SCj/}Rf>K Ӥ&,#.wZK,5A.g1SuTٚtJhsN4{U54A]t0)Li qIl]tY(? mخwG5Wfzb+fU]j}̩l1/cB۷GDqurbbJarZ=- =!MXlaD0gVu&fhxeȂGq^|%>ԕ%Ld5^L*Y65bۛ20FTBOwQאPT:Qx0$kspdoR Y^8ybE49u$ m“]vpyK\ٷ0D3gxyW |gSg &c_q b&%!q[3ƏcgB,wmKLwN=T}kcM1 먇7I+e d4yi"'2G>19sn]99-~p[ m'MQi+=o_tt;/jj7֯7Y׎enn+iJV.DU-^2*we ҪjZO<#. &]-fUmR~mx)O`k}@o~[bBɊte'*mSox|u'iӻ4R%6u]yUOm79V}w6;Pa\AջBa8 lS \s^ONA1ݱ0jC?zfK}1e9 Wx 6v8xX xtMC]g;=C2"Ui ]FFzj^rD5gj ! Da4u0/v9/?a `0(3IyHYz #6r?3pǂȎːm6GjUo2FOv{U棬[$x bT_,)qIod S !LID[St9`RX). 8]ji~! dG<"9W&{ Imm`k4)+'c mcz\욘♰ZE䔙86,mCr,ڬzQ<y] MU>9!@\v֌ڡD}H:cpn]G_ *H:܆d{[yEQbpzhO`_F)!j%Mήw7ept/OMv4A7*agVߍDoz(ՐJ߯csYv2^="`k}IG\,' )ngr FWG@RWC[i4'\_ˀcj8wWCn&VMB\;5)>h7FD `ʻOs0}#Uu^#Sw g(:܁ņ{#WS~:"OoiquhM TEGhܤ<%i'@Brp{hCI!}̊$R'O]EN@ԛI/Z2]FCmunTZ&s^h$$NMnxPV 3y(\O@~D|tNfdɝE W˸b!Y]*^HRDh[0O'hz Dg5Sx'XUPЍRZު5e^T[~7[N3)n-/Of~}?BRzJ";:TA JnR$sxٜ),²ڏ G1yưVn*g7LϞn(m# GiɻtoSc*?ZJYKuŔd"#3mi^GN捏_GnWk_9ua݉}gkeG#{{ OY:X%A&Rw:[Dx,G3r+ڻ㕢o/.0lOjwf, YӢp}mpxIOo9'8sX\(/H !0P疂њJ&y0:Wk|_2~F#1qZ\B2탼fL cgwrXac+ꢯ~]btNN0!x*<0[W%BX5wlHrG(òKJ ֽ+dg,6*&9T~<^O¤w$/Rt ,ǨF&\Oqe 4U>J?7s&*~ڟ3^am+5r~n J_ g5w+2QU$fv-2Rgt#AIELʛy+ %햸s*/I_kYB|{?|v&U ̱-~gȞ-= yҫ6ܷsROŕVo*97\T\ZS/rh[!m󯗽jb'wv(%ĎӪ2wNergӘťZ;Ltŵ"+=8T֛BƍK S׻'P7W֔\@S#6~Kwbh17GZbWb-^[<:d6!W}'̄,A܂?WyS?aI?躑|:5q?R'nRXEʸɿ,KQO]R=Ge;w شAj[E{.b6c"*z>ԹmC"Qn.a ISdaofL,üj< ;EGuY *0S:; ?XX 7ކEO_5D 4R$\N+O/D*e?[zÒfe CįjסC3Rh/yF4ygGi`z >y]۶8Ǎ]^\ .o XgQE6IdErl4~p>󽔏Y*  5NOO v˚[L_W,iPbh (]5%ܬQd4Yuv'gQ#4VIHY5rP6U5'3;(xHn] rIhUwH 5.@kheC̬^5GSžA"indX"Izh gdmqg][nډ O"Nc?//ݬG=i66mYҖP~j#)/r[ī"6;ZȖHR6.|9R^+R>DV[/Kɹ#{0TcRCZm\пME)m3ϸp2U 2LCy2@\L stLdO&#ab&neS1l*%֗!@2+퍩;Gd,ikwSU(iC=#CTM 8f[y1ZSkm̶OF{!\)öL?lOKzLB]"hmy0٥',waųNl.k[IOGyej8-N7]w!_Ras\iޙyX6Wo}]?ol"`Ŭ+Ug7ZGon Fj!HsE NTN!0_Ee k4+ek}xv;Ǒn1iq3Y;r%~jm zͰ7gbB:%ps 1c'LtS3Evc߲n#tZ++jB$cXb 8l >ZxQ`Ad4G4D0FvbvF;EHത q# dZ4P^L>-K2.[tR Uf찤QNu =w>CԻ&zPvVjUr2]4 *X&oFA%H#EȢ!GjСMH$1az+ ߠ5RN:]9wOLguyEr_G b(3&{pG (VFF^ng($وZJ\!~#guRciYu+juwaO;Ϩv{xnY)͐Ybc`FH[bƀD(bQ'{`rICY FmIc(C/DGG1ǣdÔI?r\MM' ܕԬLjtH>OkGLW;4u<*#own\ƙ9wN'xB/"]ѫءU yRۙѐԖLtI }G߼d^8mؼЎ/SH^nc^#X82Y2tA%Wp%$$ tk\Ico iKQ 23,ZNնռxN$+fgq*)W ̉5VF 0[j>P0-~s)}iԞ +<5`S=ZB@{Ohl+^G&X抚Dy.i]%tN%l]BKFNtlM\3[w,'_,1;1U_jªc l8tH7|u) f3}'c cj}W֎oRASv/aj໙:ÝG=|sܴm~C~KybSd?Ѧ*fLoD=\8t؅ӱZݛ]t0"GGᖾ[96IأI7bO*CGmFv_o\! P*g}%U0rx! +S($clrCxWBfg0SXr#(`;{C(`&=gөPl+6Dܹ`ξ+2%=%G{m)[Jl+tX5"D;]c ӄ)ۉ|qWvoY(,+,qUؒ!*N^_K[|F48'mr*]BqmzTk~-2)/ Ai̱tᗣqDcCi8-|1D/A2lΓ<j8XֆZ9N5QvV"l/"%pw[3Bg:No9Jb,LmUInm.JJe~?ḚO~0)_eC K_HhyOl_l1{E"9Ypf+joI-ḵݪoOi3i{g3was(\9$mMQ7Ӥuxؽ& $L`vUw"4j,\}Ճ JG"c7999I=6:<ҐEIpJ>,b~iAL"e[j~_&}(C t(HD Ǒ`n׷g[4*哴fz}(p[jΗ֢;^ZYWWFL<ńkx mbne3=&ʸe OKp2cr2?g<&,eY@`mvD_!y&<dm4dduJYr2FH#._sRi#{nm6Nm4bd`l7#q:ϬuݏoO״u] A<8kIr9RW\S/\?zNfw?R@"O7(<7깺%oE}Wx:Ț܋{{kdb\Iz{WS!A$& hOH}-Z$y*Ge˰tIsU)ƙPi=|3{ =[{!x;/R;*跋[#q8]pG^ kP]T1 qg]{-FY sd%ovU.T՛fj(?Uo$bs [l-2!<>BN?݃UF&}ֱ?~ ;㲺a[<)oͲN|;ӫHv&Ȇl@٬d` /F' 4&Y+JzWPz^ҺR&/:jf;S| CE ƿ']8{['nN;_K+cYЖF沱 #GOnD.zwf|5&3z~m. N7î?DL "@yx1 dm}|60aĬGx^p+X 9ǹfkm'r(.4o_)LNi> ~=r2DLZe܄g(Fϟ ^1BuYuso4į̌7qr.dhS^"ĦYRU7x.~ WG/0^hf.|v g(CW;Y)2RLbWtZOK(XEszK8` p01[@c`NG]EMUڟoI5|{tGGztYh2T8zOux:~VQ8n*R3o`[?Gnf8yrQՖs|0.Rj3DY=8sQLѫ|7hÞFgYސ$G,hCr6.eC:rAP]Pf y}_ED>}t;FK7)<_{y1fOIZ6yZn>Aqϼ?5aJ9f@C^}cm?|XGh[( 1XbihY?.gqFO::Dt6MKb ՛iW $gç {iASǃ10~1/\[K1[ᡖQ1tT{cjwgz\"#.W;A)^.҉>CAI4P~zoCdf )v"@Qk=AB;ZR.v<`z>env6'ޚDt6DqN6tw6GH%OO:s9A^NRB]M ĊȻ2;9{*qgDϢ-^jD/viOʛ؛]&mݲPz Q܏c8ߨ4<5Jw$lZǞ쵾 ʙœ5̀*;bncTk:fIuW?-;`ܠ!VsH$r?%VBDlBJ\ OhD#r_ZWPR"I=WN8ԢQy^:8MAۥ~_\#z \/{>L/iD>>I{ٴAe(Sv_|Ku`H.t2‚TDb*Kb8)ΖD{DYăp9RI(ol٘mWq_М-ʞ׎ WPTͣnkhCpMg?k86wK&]{{]އH/S6Fd|*9Jc[l-dJOǿ EY6u.2$Z)7w.5ɄXK@!Xp qHBFLh؋39}}:|'lsc˕?haS }oܜ,"|$"!LHNfH3Y#NTu=>%˺.DUoU~2/f'2d-_4!c[;Cv asu]in}R&Ǥ0y~;^%S\~L}DK MyŊ5=$^IbqƸ_q\!9p=r9i_P{yl?Jx=~kCۢ ɟF]P0KvS ea//#gvp6.[}ͫQvuVwEBIu9KIX- .nc2ri.>q520dxb;^Z[tS3>k7֌5o«ߤ+ɬ 8u 롨2G-$t' = [q1i˾޸]oqťw[X0Eq)X4%4.CR#'}U€2sxOQaV+9\h*'?VFcZz$G ?IH_8J^-ޏ|L"m YZN|;RhdQE>PҔ|QFYX8T֜;y͓֚}j}5JC/'ĮmҥssW7 R2L[L5XamjbB;qX kv[vZjfvX)-B;DKF,hnāSYojabnWxF9Mꂮ:Ջy-ދ%eM'#E@q7aFF?Pq'IrLt9޳P=fqzQFu쥴ƣ|@:HI<}eKѴфbݲu$g'8k+㦷X':GeQ®)5LyFJMlC+{"N&D|ۧ&7}ʣ*@Rwپ?.旒8By>9} #z_\K种3\xIu(vu} f>'Y¿݃bi5$hI XI cni5}I >0Oh&:#PpX3ȱ:"s=t) PaF۫g>|z~[YCv:LL7lD-~|fUeed ju 32~ Xh#9tuP*Q"d4:roq/(XNb@۩1AٗÆB~IrFlZOgPDI|M 8rdyFnn̽֯MBZu11ep%;5N­dBorihU6nm5T=%u:5s!c(ʭwK zKڿ / K;_l5?^G0Hvߎs~$KHG-'\wJ/&RwpZJuh5W0Wr O}==+'E]n[hi{;3)L(זXV'?EPi{z7 ܫ}Yd:whDšUm`jhgm 94>NJN8y,0g!A}B+W5]>n[5㗝V 7UauT͔sಥ+N[Ś4׏=1-򥧓G=# a6V¾Gx(}3Wh%7v]vkq|Q 9mE & ařWlϵ(Hq3i%J.NfMgs8XZM z]RDZ*cG#Nnm57ouL8 )5n5uapEYx5hA[2̼qKuYQuğz[ޕı,&p#Ʉgq7BRl*Qp;4 E8E""  c7*9Zl;#Z ,].s$'Μ$C}rĊİGnB74&K"ME昪0՗)ʟxs Ƕ'呃oVvtW^8I?_Sg2ju"OL~Y5XzqīyO _xaBe P!p䝆6FoԄAàٸ7wjk;'' qЇ5e(IZ}-m@D0WKtǰBwRq}Mb%ˣZ! XܨaIi"0ۆ+-o/9e% A9ΩiN0%`lX [,3O@NیoPn$ORfLX4 -5ωh{t'$+$}&p罙,}! $Q X{ ~joa}>Y?=FԋcsQ(#m$MM* 83aP't֍#v\ki)W*z0F Z52~N_DC3ac&#v 6G4Av qMGRveY40݆C@DrFm %K/qwO ߮;_+p}($Jf`Q5#9'*F=Cg1Cr~b}Wmtg{.}S޸+`/ئl{*$#jb"/fW O -'PjNA7*Cm(fxkGn9 W:3Uj' ͜r _59r_I#Rd;/Hl:,ƥ"fjjuQC1t2OpX5`q97*nF8#頫-p0!CS4Vtd8}v^%E1CU;3’qYD$tb͖I^1 鷦+hMˉ9M0c:cG֯my2 0ؑl}y`lg,w5"e9bGSSHTES`ɲrsh6!IvǥXĿܶ*RP҃;OSQ| ltʌq+%6ͱƩvJqК"C*eMqec>P]M).rV3uO(=_F\Os0%VWMVuFUYJ@ g6(KX漺9Jlp'oW1`-X?*jEбw}imIsn,(>Ds9q3Ձg} A8*vso۵ SN^QyBDdJ?#1d 08Ht$*{282Y硨X.b\0GE6i C\0 xk9̅A+Ni@d>Gx[ف'9tq1&<3w߸eW7t >wС`:m/s jB}pxǔf G$~ ( jY)F/Glx_n(I86_R9Ia7 pjUmw Xҩ!`m H^#0W8'X1֤)w稱#e=6W\l_Q~7%*Lnn%"r7#g T/|$z*vpٓT.cti,Ų;vSTPGv(xQ9Dlf[B#W} |(Zb.P/O{70wl V.,m.F~0:<E*TXzpk T$̕JY32-[riI9Avo7z-VTr)/]bR|#&.ly>UkR8yWbfM$AgKi7< 7+<=1֟)˵5Vp8FalN0]':;e9h6! ۼ,ŌaBhs# ~=t 8.O}uͦ'%śLWs鵿0qEʍ7i'ÀTROW~W+^F +?ӫ{ )r+gN2?/u=*IEk,C 0))%H"P:A~8^g<]uE Ua Xyjaa^y٫9Ǻ=#uᕢ8K3e!޿0nEt/4-df0^jdpJozǴ4W~Eҗ:4*Bņ02)E݊y0|[mBA،D :JajU2W>yYg;xo#&T Cw_O̵(`FɺL^ni0Ma0M '[[-b߷Q1L Fm7lZ4q_[: oYnOD"됬EdiJOgI2%A݉,?^|wzc7i3y$k*~燈1o8>f/-qi^w7K w)͙,q`Nq8Yutr2t{@O,ރ<&[>XWPd6uc0lɲ|N/g3 zNcqHR&Q3uƼ}!W}*y5U~))QަuS wTR@75qRxd 7C)_.~ .ɩRq™P3qATIتC4 6bgcY2C}QC>xͿ]JoKu EkMUN{^3<ڑMџXͮȕ.1/@nf3|tnj\ ޮJ+JH_k.0!vׅp`UA\cgG>_p ˃&bMȹH(gQi~T׷3t1vT P_rɅ2Gm A( Ԃ@tPMs\cʯ$ȳ>A$h/"| &tC6"5N&Y@o} G2}S ra#^\Ԉ v MR<gEOxt&xK;d ݇vv$cJG=FԹ![MjqWdGO,zjw ( )\߫Lk4zakJy̐f}1 ɭ7h)#e˦UD8 wܰF$+W" >8SHorGؔ7;'tLW\Dy&b V@Ggf3ƪIo3 YwsLxvuu]z_j4s U,Ouwwn_޺wש.Iɕe{21;㏀3nQ׎yd=-"j3J,e.nN-k4ҥKh?\*#:iz"JiWC_%qv^[tUE@rO26p@DR]QiƠ@)ht @KOfК@{Z]0kLD5X~kFӔ7=+^!{-!sCok=.W ~Fw;ᒄ4qI&N%s/r۟Aa{ m ,v$Օ,iBƓY]\F(?${F:'aQix7 B-ŧ6$ǩ+vh[ Y|Y$Sɯ$ff1QgNYYb̩M1-35<ێƚϠyyb6ۛxO0T揭Qʍ=5 _?zbF=sF ͘A?w9A"ϼ9ԧ͟$5фm.P&f 98& ,6ă#wrJX| u'ka[Z)|=# K@~QY$L&z abaGWw_\Ùԃa[5#mEYo; T}2"VD,l *euN JA#B!WIY-$gwGũWȅ'(`qm\v|# ay2Z8uR+?Ch)؇YjFƩcrW<mc..tr#HX[Ό_EWŦKAIEon9|aS%,}JW=rj<ᚓ)S|譍-Ѫq VwkB<'O/tEdwcPC%$;g;YhrngA\5]ºo\#ĿѠW,dsy1i:,^@nb2}MMNك<:' .yZdW%/(MM6s+H, "<4źsngg~dpْdʱ!ax&^`sHQQ}nYD%ik9T0:w&K~犁 REj?:ItwuBri.Io/GΚԭHHb71= sxlf噺J:j2 LG$b\T'S1v<6΋y+vr~p;o>(tLɧ΃UTPܺnS(x]>~|e9*Ԥqe>g#ex]͝'ZJ)P0{^crid2k3,20Gz*t*j>KHxY~lAr4!%N3J?C[)2J&כUjQjݘ-ԘkQկIܧ LN5ѫdNՑ}.|Oʌ9+s(/w_'^TeU-X/Pml0Q0M@#0Yb' Ƭ3/WԂ51˒98x:4`r (Œ:hM"֐2(\-~$PT9v}$Ahy|*%jʼnZ \e.f&<ƤҞ'h^dqԉF`^O+WhfNpy?-y<,pzŋ'%*6(±F577PMK鈭M +7 BqcU+"QMиt,? kI(v`^3l8d,~sY~d;a|vѐ -hq Ӂ,Z z; 'w:?|vNnt~jĜfQ@oM#kϢC[(y<_OdBLK L~%Y/WM*\K"᠌F"!=e3S)i=gГ|Gվm7\cBt CYhcjek/8wiVF~7hØm+U&Ce-1tѴT̿;b-e?3QnbNmSK${K="pۏ.sM;t7ٳvЯa~ןYy6瞝+q:[@êy{_]l]?ɜ$18FhPMQ YUҳ)vm'-_VY7;TX:)6xj"լH3M&.a84hq*PQ40ڭg,[A Q /vϒ6 /VFQ;r tY/< 0mEm &gdpLe2׮7FY~3-β9VW泀JK'O~J׎vhO*O* v{ds_!G S}tfaj]tΥHQ>+֧ă؂74j12PƐqzWf(&|/`"%ez&iHJps jw}1۫;s8&F]^}i'9OKTe3>TC #XWC6Z#bvI ˏ`.YmxOOB)DBB蹜wleUo:6}O ﮮcq|LG'Iy̌x3aṖGrOgC+Q6W|0.<8<|vߘrTY IgV80TcK3ӌW2Ԋ'η^L*cI9^/#i/mVrō̑u?=u+;*!Ԅa]=(Q׶h35<] ,œwOjd^{Fi_C<:)1li9pQ=+|fNpϐj\wJM u ʢSN I%*|şH gpIY4.0J=b]RL_}'\뮪{y[7 =k}/=u&f?jIf,YJS9Z_H]P͔RG.ÉͿ[[e(Kr!nTbH8T@䐗1tU_;brX 9-5ɳ%q.,MDHb yZgvvB{r@㠱t04V nWڧ,/8!!nL/5KԄ#:p`$)Q\Q ̲q? #{ڹH% |yi٤PAR6:9+yĢbF!&#alkĶ%  UA6k^~WϨ<3; M pYNh#E<+ is cUj㈸y,ffwS&LqMQR2C|Б?)C%>\NROHp}kUt4Z>xJ6;Q  8ɐ0̙ 0[ TvCByqnQۢAۦMܠ)Y⎹71Ͻsi8؃Ca_sY_d%,ŵ3 Ln7Ga|ELxhL.'.萲ݰGuYc3r3JKH%m[ewF)vA+6=ƀ\EVjw y6/dk=$dFT_HRC\:1`l_{Ri'|zcotgGA<pb+?Մ]d D} qU" a[Q>9~+*n'٩,Zo4ݎEvB/!a$WT4<ƥ8:m7 cUuS4p٣Ҹ*~ږڑ =,HJ`Mspf>^Y^sz),dwNJ/("M ɞIl>XC礪e9#ݴm\+=Ѷ%邭%1  a7~"kPIL )e+9! Y+%vMȈ|o P"&5u,iq7|c}71 eBXsRq XhZ5rB="Bv1G:)XE7юeyFmq ~7 7F(1+IJ3kiwVy]!89uxZtg=}(i1'Qr.G*'[|!iĐמ?ya}lycLi>4T|Df#OK*(lEӵ$.!&/CqҽPW >"Qĭx44c}Eh2S {:VYf)K?_J *UqT''DCHxoPlsh{xނrj:P @LL[ky"2r,*׸>sYN5[>IIY/ƃ덱{G?T5 QzqHuN+~)Xyqv8:C,apÊ!(h@ ܼs 7?e%\GTx#W)}_2<"`Ìr@~_Ρ\9j`IJSt&HXDFt:fuzIGtI.:#Fyf.IOrZ5}j bձ7.G%ۻLT N͢vTUUpR_yeJy|k%)<9KgnwR)H7dw 7>(%"|o 6E1pp0Phc:h.iIjQZDġghb66UrO(߿&Uv,ܞX͋VyP5~2&l[|OIE0[^2 `Q6P4 t%+#Flx?Hd?׽J颍%#lRߢ ʒx)БOWGW)'P3#2 ?<̍:I" s}u\񿏃5OsFo[[G?|W|3$Dv5c)itPoxd*qK(ˡl[sد)Rthj,WzX4VIȮ ;(T̥59 MrU{!8ܔ #d^f$_8jFZvP'0t6K6"l mWΗ0  FkjcA^m*:?`#Zt2+ncY?u_1GfN _̡˩:W=5Jb x\Mi:[eijcx0K3Jm"22%Oi?ˑAdJz; :~#qp %}eBFj L:yutGZ1i8X - ^YQ BEQ;Z )4"I\a*8<9NXVXֲ[DO\4?+zPҶ(^*U0Fsf{q/R(S~/4~ sJSOtGjTZ8+^hTnF UZW^ƯOE bPX=!14Yo>Ck؊:/Q(pGb)<:ۅ,Es_cՊ5en ņ0(ZpRY{zǓzS{ޞ'ʎVsZdߟʨ§"_Ҁ%BdVM=9^IAi% 3` -+5aF| j$8CAۜ W6 `;Nqe0̹W FmR[ω,IMڳr-&o+2VZ1cM=5ے`#,|!4O`́* DUcj Tu0={2_%:®zlAF bF2t ߉"[=5tBIЦA2hX+Gn\<$h"U׼JV%s\jODZQmfoZ͂I\T2L LA;+ {jqj,G!tbh#u9VeefO[f$G8}xF#8C%Ei"~P# )s@DP09<>a7o'ngU)5D.\y5lџ`@ev3%PD3 UF@tipOaxrU /p(33yOU26eVQŌi./xLk|sZvX +P2x \^fGnpWRIec֪5V(oΪ\$0]|hRQĶxrE_9;¹"Y=4%P{X; G 9$zp$IXKheǼ !rmb5 сPs)$UqR-9A|kJ=T@B_]L<|$*"+}aAsn5%lb( 7 àdZЃ ~"GG.q s&7:>? z<*h5=tp)>ŨU,]6U5)F?Nk} t%{&w'y8t45!4S-.Z z6g4:/=lCl0 !=p.a+ ˑE[G|gng8鼁89%A|9v6'7JzW{9m'\g^*b`vY A?e0cQXЅ%l&C&L8˂)].&xW/Suj),Qؚ<ѿ1d*HgQgT=ZL]z2U&zx7ϙgnG٪~ վXȘMxlZ*WEWߚtvfeY佥Ǫ H(7 =ˈDrtkv83׫99Ng uih2'@uJX1n΍w]dݮvS^[ǽ2eCV$Yfz6s5=>aXM,]&.VjQ"L)[PT}atɣ3}Źp΢n'mz4l>m3x¾4 bXP=SvXL6Ecj?X!R?]վ<\ ;ءȑ2\򴋗{Fx1i ux,e+ jtpYޟ4hsHx{<|gN<,΃>v#eD0pt`J$pR:HEiEhe73[˘A jGho^,SRXdwXԧW ^v(| 5 ;^O1}7t(?p.9Z}[ut| rB =,/R#Pdd0́3033!&fffO5"i H%FN*9ܹkoG~rCvrdmB c1 5gN< kM ͙|R H5}&R)$,*یk+(!/hXSgdcsBMKVX AF}kau_a~;(ئчDe"e㊹ˉ A3*#Jh9 h%ޘ,ۊ16M_#\Laހ{gm;gĻ싕$ ɜybVz*Ǐ{1l2GCn)4#SM1&O9`cD5xhfhIVP=K_1MGB`J;gؔ}٭!41h _^/yE:Fj<|M^ >3"T/aq¦b*q⻒t*91xby _-b%%. 8Or4H:h(vCj!: I>W1aI2x([L;1Yi!p.1· #g' zD&J|}躼wد\! 7 AN3UZX-×@FP=?h.G4TpVΉ9i?ǴUOM `w 8d{(aĞ2x--k׺}Gӄr {bfYW玃Fstpzf[u?6Ίz coDz)kV4Gw<E3~^kj8(!ۂߩ9Аq9,:ҊA k`3Els\s"mNJp)d蕐J_i >_(%#>65 hL7Q  HM18 5lҁ8c;V,(G8| #2(( b{*92T~IPTDfLJثz)EU%PֆeRɛ$QTHy'2m38irVBwg1E z1{yG[1ހ^ΩcYnoNHgD7o*U0qǦiї$c4V|Fn$;+;_MfdQ־:# 7 | _.VUМs , ?SE.GW5;[Lɳ#&%ĖTD:*/YjOA6z=n̞о{Yk 1~?@5dL`d5Pv"ǫ_ 2l S]6 hJȃ{Z[5{̬Jg˂Gs9`D)rF}'=$$5yhGrvRm1l0k#[*=e }?-߀Zܷ=ȎͺRI7gv;AFZK{h1mex\Ox1#ohr׏%?|餓Ok?!Ow{&&?jr>ݡFDPo Crk߈@ot(.*Zĕ' wYKX#Y:. O.ͣcx=x]S$ux)LG>XE=DgWP)Duޥ_|Aɽ!}$`u)- aFesysUi>  m~Q, Cek>Lq- fu"XUbZ}ٮZ4#V#;TI7󎾜H`4E1k46R"h;Up,c e ? Jj;b ˔'Lڗf(1i2*6h-IHdV7=궞^z29m b>mZ(ЌM0džpfrGhO#wkT{5\3e׌ N^f!*ULH*cOɁ8ϚlS*XYt{ 9Dhe픋+_h"3bv!%04rk&fÒ,WTWi/6wsLqsjEulXEI̩ mE+DO%!DN;J֥]7 ϼls}̍\Q)ICK0yTuf㾄2f9?3a+]bNphk[5]:Q(z3X=$0?G 7g'yBaeN6? ly(s^M3AI!uTJTe;B?Ա \<; &x˂^Xa})+'_KvJEKMdm)&aG 6bHbsGN&+B#'1HdA!2'3 E/FYI)IyJ]53*rSkkt[~`E]}rsT1r\'%B~[hU<:|Z bW8y<;և*hM+VI7:EP? XN#9EӪB !r8MlmZZh4Ysđ;+6å{O>|6ч3ee :c^ ӏE݈>HZ. eJ*X9[fnj0V3CvQT~&-].']^}M l_b$hWfvN瞺 t~iN=>IC WN; *BQzeFjN,6'kY ֌M*Sܳ.J|_>ѮjI"?Ke"l`X!G9^%tuw\"0yAdUvLBit|Q8~Y)q<$ء%Kh,QLLRao>`hUQIQ" Vj=bh*O$}d)h+0=eh1;Zͧ܋K1HqO˜_;9z(^B#55i(,_t#aTz,K+M}~?ZRQJ朞2 ɰJ5q35SB27efo\5:SᛔHomߟ$36ARUN)Šth4A3FB'ǬsJSƌTOR&%Ȯ.ߛ\"#6Y8K\wc[+#_HK!d2"c/ =_qY6Ca 7KB̚iȼ2;/W_ap=c n UB,crE. mgB]6=eak7%;HP`RAJqjRK+jwJJkAw68ýKDRGXG.V/d  5 qzP7e("nuҀtdK4xZa>q Sze{d`31ZmF]˿<+kU*5(8mQssXohhր?82=T}0/{+z҉&/1bTSeφT}77X8IXB? $&ߎlLNuTc!GUt!Q:#L<' a/Meb/8I'Ap5#4XբAB5f?`=4. LAx?BAbu/ffѵ%'j{u>oZ!5Gfst^ez>_XF0i썹k 2=-+f9UAs [w/wT@Yb>zCޘo.Ukw"E1(j닌kHC묋* EPgP1JSzkGD*U ċܻC\֒+|Bjf>xM>ô GpoT8HsZ% 0$@߻;)3\Z0UVRYGq]3 ,x+Ow#˃df 0TVU&{GYwqkh -v¾ȉ@VԞͿʿ o De{Q iIӨy7z`zq:& %7mc|d Z*Y}dNպ/磑]׽93aM@SŸt\3Lj=SőFGL0zGՌCfyYM~WdgTu_VuQO˼Q%3כϸ֝Asb*si5mrŦTzsܢx[JI+ʭU2\MtQ/t#x9fY`W j:[ݬf#Ipeؕ?4pg{_QC/ym.Ed1]Q8Y&c>2IФ\Z6\=`i97XBb֤;^,pPw&ʹo01<3ߑSqT ru\JQb2o[qy`,؀  $ dJ.lHA^7j)6j6KJ 3{;y?%J:AȘ:?tr;}M,Lu3$wp9ӚA]"$QN vIbr~z{xNkE>-DcQPg O4΍hcr;w4IN@xm9igl9QX|4⏕xΔ|<-u? Yf9^SqF]3ҫfk{YYR+ml}u'ɺe& :.Ux+N˓)2Iޭ(cHJ{{X:)WbujY>]|u`x6Є8咾: vrQv\`eE}4rJ ^O@*#dLs ){e;BaF/],(=$0WA3mD+,w,VR䯎#xOԻтϛO1&)y|/P1\t$g*Vo& o O`W P'XOZ&nQzP__0~chЏrs)T΂*My2` A0\zZ=YPcʪ(,µ 118DgN"7࢈V!DrY~fUCf|i$}r #Wio&}7K P@:{&R fȈo]EÔ_v2Qg̾mAqs.Bb57lKޖlD̻0;+0L`eJ*x p^eWbM.g J4.Fg2Uo {&tˆ/Z6d.&1A޹A *6\_gDx0S'D:cGzX$:tWfi-{6.W>nY4W~?ZhX;Pn_Ώ4)>Wb \ &W112[@ZJ˓`v</.jA71lI:K06W CU,C|S8UL/oFy8\,>ZWR:a "^iqX4y.p#⁝w R[: j{ae('ᇧ)C}Xc7Nxxj#O{ {[缋N/TbgCivZ^L^%@cBf}jO0UjAaEXdnyS;.oco44*:`|*g wQC3xEvÐ^15~sWNNNF i(#jəo; NF&՞J*6^rL0b}`if}NJ9(._6);ý\%$Mu-_>}}^;$ GSn͛)xwi㉏^XuOo&5'umDxΛEDwHH v`!ҋ._aVgvSS HjM0JLWMX~)2JmQ8@'bsd=AtZ׺<ǎFwvk:8\Eq yzC$kyQ"i ф]\ftOMూJ}h8yn]$n޲PEH04 B$X4Ǎoҍ%;V( cū?a1<=p@N aI}|fK¨ nM-S!g2zTo[,(Y0&RxtSITsU4!`_JzCR+fɁ+Q3x;l常aM.h+OHZRL][keӧc't,&ǖ)-D&ĜU-! Le Եe/>._uMF*4\ERL" ,ɂDsHzJZ ?HםbG~B.CMb䒕3Dlsf)7_XS{x;]VK7W}0;YM߹`:八Y;7_$̓5K]Ï|Iec?ó3΍w$lеUS.oL~IJ#{,TaS_>-QHK4¨Fڌ)-E6pvME &yI4>ed¤ ࡩ5G낞ccBCO,G7Hϙ@ 8&5v a>p8"5c݌_}Lą-?R8+͍Z~cُ25șȿl{ʤy$6$3:]VܕݤS_\\KlƄtX_,48fg"dCl< .. ])D[/wF M]y<V\ 1qL:{6&4qc(>Iϋ1+"t %NNڷJ0e VꥋܣH~XR k P+sޖ! '1YC$|$'WCN_m`E.ΣxҚch&K%HJv猕">pfZ$}1'5BJ~5uKF}Q8WVIDQzUtt$lJ3\.Ij5Bډk23c0rCƂfM$[lg o3'Gr 0A=cțZQ-,%_Q~sEG_3ldY?9;*~x''EWmPQ#.HGV:u0~a g ^UjS;q7` ޒ$&  jUA [d1N%նK#q#PjkYKXM2}Y׌9[F/D\)$"5wnu]Ymo.#*Dti^6<_,9v;JI>ZbX\1p!S` (¤.ȺnU#PoeTٍl)j5o=e &35^\7g]6?b:IDmį"e4'<& w#Ԙ ݃/D U] Xv\3)gݬZ5IEM5>#sZ&jn>f{^5^fi Jn w;92a]Y@h*! ߽LG/{U%RV$Lt*(Hz@Ğf #QȆE f;ͩ$0ϋbX~Sc]Rʯn--X{cXEx 3nC= 1l-[y PfV ֐Fnw~Z=tWa[%oaU9vn¦9\ji,vs}d4M9_4XŒ c\8mzxkتԣm 348Ekzc7Gs] I7)N X4UJlX)䉸F眺 DŽrmۣNa)uy'| 5&aIYlLG(~"jq<ZB7[umPFRʅ7;,0N+,a\_ QR)'z9%%(k:zϼ-Q0mRޕ\mt7rp޼YLqt$!e:tH叀:>!4+>ࡏV/I{FG$1H ?# 1M7*~Oĵ#PaZ"c?lbn^k>0uy;cITfبa8t%jq^-ƒWڢ,TWT6ZaUwNPPWuʟ]Y3~N]ꍙFrIрb \O]sHzYfLI9u bV]ozƳrr̢]X5a xsEttD֝##ť#9k0ApҡB97nŧ_ؤ_賓YxF_jA}pCv=ˢ+Tm:cDTM.7,/\BK iО-3Z6`A42CZ>&<mȳQ,kZU;SBlꋬr `U\SZcn]sC6-r| rvj-q[R0m%8qa殽g>ׇ{ƃб# x&Q50^t!K':zrir%;Gqb!F9/%m2Z&.5aP8=\86=$q2x ɋp!#]rsqxMEa) OUy"Z(y?2O_#` Y튵X&I2TG^6A$M w  ˒`u ݂Y;;+#h<-C5 9MWMx"QxQGéANσTep%oRC9F.t -ؖBfNVs;9J{NmrX1-\VѴݏȌS1=*rYo*dז=- ѭJdyţU`dXVkwn2Noҍ#;4+GI #cir(sMPե=3 |Bm{5(2Àng"8L=(fw#1YXҕzx%uZKJ,nEs&t=ّg'V2&B53ﯛ)O{Ge)ɉEp&նy_vV~'t3gWݷ}zsT9?};GvY]9#m2Nw6**Lz1"CT.eCS:Ma7wGCRнS1p:d=0?[0$hK:bdhS$i#} 8w Fr[ߘ,{;z"CcOU lLRYJ$Kj y:EV89ӌ5̏zfb;yeMGŲV.u*2mS"JHIg ̄9La8N_ާ5$ΟWpȃ :^X&7—(d|L@p֘ŷRQ$dlaIk쒮QfHyDdƊ#O4{!՟%n溘k[#?W43)-%c>X}N$mv@P^'>OZLX tӲJ[2J|5v$ pKޢꡞ9U"XSɔK8sq!\7Sm =än)XGwb{"x/+?Kdskɹcʞ_^QSL[ԞІQ5R]&a ٸPO (sS9nPŋ6Yޙf|TB g3zy6W7ٴuHD\;r(-]#$͟r b1iXb1h &aãdKye6$@wGVf1@]ѓ5!)*TR> S|k`]d&KQ(f'LȢeNل8+.mb[ƞc8f RBbyFۋ1x:](E ޞn=mFw_2SV:܉R!@Wc.ļx+r!FgD)% 9Z}nP!mĚ&B=wSb 2&^V)LVf2z7+>LɼҧJ(!EXc.q773Ƙϒ:~c鎳CI(r<8\wwՙ0^Ò]GM%FmPgbo~yƟX(TceE+GZ*7"JN?NI@OXB;j(w>&z] EǥϪ2a'XWR MDȹ9ؔ]HQ:v$YJV;Z?z5 @V] La2ҊK~"ɣ!͝}+x~ʝA16m,=UCak9odԱ~Cq `0F}#s1iSTLW3T#>X|rOBklmClbfMޯ$;F[dmlヒK#Ɋu?%([XVʨk4ự)]B6Ho  pqNS טC=ݡ9*oN? .׃r:C^dtW{ɿP}Ffa8Nܶ0cmr&䰔rӮ57Y6oJiZ2a}foR+\AQ62Exyao’nF,`xӬV h1)8G~4,28&< ngsW!LUa2X _$uLEhWq=Q3}"$kQkjwJ|dRni0P䙌YTAB!QOcLQb|c`fےC7etNy7n:Zm T&by̌ωD|1#CE$%vH,:V'J;=bO&_GN}Uy/\41]mb9:yQC$jDv}NƮBPzbYz?Yrg{gRQoοXN\HޓFդcU1A΍F̍ٝw]68ȻR9H]áVYkF(܅[aq Y%qh;R peKRȥ SȺE@祔H+SSSB|Q8E'L=4a"F%W'] xyٔG_ P˛Fޡ/&j Wл~2-E|aJnH&ܔī1ݯ&Jcc[&P/R}wb]+RkA+*L}RXTi^._PIx"n?۽g0ⰿfHb߯vt-za InK@QUEx.7XUfHO^yIu{(bCz6}@u w^NhN I `b<HfRu5HGb+rAl 8Y(!߷1% /h᫱>a4#0=ϣG`6;:Qf5A 2.Ft%?(.q#K M4fkL_ + 5*$%0o!n])2C"9," ҕ`H|W 0 =Gʍ D0(RaD&KGRɭERFrX"Ny=yU,p䫏jIg2~Kv;.O@UP)x>#z)|õ_:E/'+ t/$y3 3lH>uoƇCH!UbSaS{Pc;P5=yK;] 2Xc_؝o/<8MrtWYJjV_gkc8gB0xXpÕ H͠e 6T;epjW +~z`Kd 4A}}dbȈu6*&vZA*j>q.diiDپ I/#*ĕ;$‹=ӟDuAbt'>|GJ<"rBͣI)%:=YƦI!O da 1ҵ0, M~dO/;ج h2LzoH8:Ph/7ZvG?d8LlfV5#G72gm YbK[}wp7֯&z8F~ԍ0;D׏a5ԕ%lYvp>6%lfOX*ǙEYK,a|dMW7'KdY8/msTExǦŏ r匄 +`Dlͱ♌{y -sr3IsMb7rZ2(H3D*TIsJ똩11e e/Rrr$~z3TFG_ cՇ%}n- ђ/" bh<;_E ?t厕89bX[ 7nw#HČ1ۄ7`-;üXek ^Fbu-whL3ZbrKsfsj+ lwP:X1V7q*qv |Qs<_L|^C =сhHyl-ȬT2rF#&V;DzŔѕ6I0Bj*Ûf1D&d9iފ;E.廙&rqꐶ(r(1s,] /oFۓ :#P_ l{ ܇I9=k9'W;x azLHQd,_AQe?e u(Ƙʰh&b/u4Q,lg)&8<ĽXw/k1_Y|h%^4Fw9"֧XWqċ}%iΛڳ<(-m(%-6LD~g * ܝa-M -(ѩ\8@|E҅Pܿ`;G[Vhd+Z_`PH.#B)+MeF=0~-࿕CV[`6ooF@qYoXd*PhK)q$3O-Tf|DwI09)~q锁PE|DMq>go!]q7yL.>"4NcMF1'}3P߱I[13GG;NI/{-5X &R;) v&Hs:? 4KFnd:*^[d6F4Xz |dz\WǵSG)lDZ2v剗 (dxȣ0)0}<4No' 0MlӠtWڣSMWVkjpq?R e&YLqrⅥ]E`jc(N2~t7Ue ?3| EZ*{xCJeZtXц!F7;1m ~Fb->>]):bֶ4&䗓'oKшb cV#Ƒw%\qQS2M 3(_4ccv9j}>MOWeSeon`cP0&pk\4ܩ,: K I`b=؛Q;~Hܼc0cԜy M4@=:Yc#eP\I=HB? >p!JR5c?qިK ihV!^,szӿ~? 1 ee]*8v֖pͬe1/"\h|}h澃g)aZ(dZaǴ$`0dDA"$5FXnĒ:&SY ȭFr) y@&^ A7;f#?hv,fq{Ji7"3[hF3QO$h~ږaj*0"Jrg309$[Ѹm!zȾ Ѭ c/)#zED!ˍwΠ@inS9oe,ӫ:0etp, 4K@͍+S#Xc eOn0b 0QT< 28wEU+FI7,>i>g֏˙c'{mߪ;b&wY)Gj&rZCҞƠFdZs#3(${Ji0dV:Ɏ#:I5[2`ǯBX5P#2P&1b po\rbc5gӲoZ^Atym1f+QˍsrcQgd2gMjI+q-9o׊TVWmW[NYA9TQț\9g&XM29StYaPHq,)#0z?`/[(s!j]/ 4&ZP"l4حq8בp\dܴ*T]P["`=$0wW-ǹ_YmWhڋ4E'Ł 2cK[WL*2uV_mX4ƾ_YJBl H2{l>`"~Zq"~GR/F+aDy%rYHri+ÁrG*hHS* }i֛(Lmf {xLm.CQ| Q|ȖИ6QjȬ+Xs ^pL}^!hʮfV!Lb-dgRNa/ՂwLf]E0FdYyޘLMNߤf{gLi,6ܽ%?\ko=JB6g,#^)g% N%;W~?rx n"LjAޱkMJ#Εyu#FM +˞sySmcvDW`v t>$0sHfLr`^nvTQ qF b d<#- HEJ϶zIbdxY DGԜ7;#-(ɱp'D3 B<:rXdrV㜴&c n 8F97x" ٣j2 ØUf9OX51-[}!_^(mfμfs7vQzgGlN} gR$'BU*FzLhm-梅 α>?±d-]C(yb۝6+T6MF\:v,uPdwV6-^b6sSfyrp G2qTgPNW~C^kJ%t5035,x}a;~1u`Z)1[GIQI kkloԌ̡9m@js/=xS큣HE85b[6N#6+e"{/΂ d\CN09$8~eɣ7v^DQ`j-ȻHi`m*Fa+M6e>\qJPpf{##Fv]&\W`嘖k䚴Y&}x||L 1l]GN'g@J܋G}:%Ρ"3k5*r((I-6@uIk/ĊlUrxFAIA+Z$Uo'B#$O /PJsTuBXsDy#R﹂V*Gj 8PwMpv)(>F$7'GZ4 b?If^=9`}buΈ Z> BDR'efdsG,VH69ElHNUF =ˍv,oYa0D>R L42Ɵѻ]j,b C,K 9OG?p'dZF;+hGb!"<ѤZ)~)2{һ:u޹8EUmdX 8gCc:)|I FOb`dg5AQLjSIz'Ʃ.c'2X3BImUǒ ⏲3ĻiBK*Imdh]~NAgq8(on_Q<퍄k^K~AR5H{)|&iS_"M GOT C~odn|g: i2Fa֜^PFL '8 S43(VMEOl1 k HhONk:lRrIiXI[QXd \$Y`t Q2z#cdNw$\'ߕ"Kk ~4F4;~̝a ia4ju$Ay۞ʒwl)vf5!$ro%I"|9%Ir ^#;Ey-3NU3W]\H^؛29TsUdq˰"dzJ[3jY }j8芚`,tqW H?U6D)Ί}ȍ8lc pF'N)gQ_HrKyzQOzF&zI‡C-l_3U=);^ 'וQ&8+S.~JoП&DpW&<7#඙G$!c/hF}R; v(=~ڇ$7iM^`mLD[$LtE;QẠ[ɘ4#Ѵ'6`noRU"xqx=pijJD`KYd2Y@T5vp=6kݎƖR uhJE8Ip ,sL"^\샚 ef!n42k(C]xm pq\,6؊Y(|P>c |׀Ve'5kNm߱tמMاa϶C2*QêhW>hum ˸=s 3ܿl*RΦ48"[,d1~ܐhk=U1Kc(2 "ML@ Cb5K'5}"ϿmyL%RVO> S2ClҘL᭄TsWc纙_abƷ,zAo CCKuO! dN=rc r1lXI'1o{McYWGab4T?El'܍Yrл[OFj"H(}}yhG*yQNJNnܱ G㐡JBP?"f&q&War]C^7 ڪqhD]Hiu}Ms\XzRTyMJ y::,u)J0QyNֆ6h&IӥUo`;n$Anoӆ O?EZ,q86RGn!Ly'=eIm ;`le8“c@pjHRTӀ%y_I)\̘'(CANm5gH{}Ί+E"Qh,xa1@^t:wqqjAɳh: cCI &429^Cش~9hWd#LiCT`dxH\}G*P\#f*DSg62Gn0ޢlH=S^^:.Y;cL5pz6FVJ5==N'#jf0hcuJ"ǵyh“8j\:}BHsSXz^gδ_ѧi2cnbմظ#MjS"#`NNt,쒭Ƒw6Ky/IdOGYI#_Ss(2~Qv2|+Jl:]ux[H,V)X܂Y*tDkdžLUDBXE>[>j+&v n"LF,\&(QzXrDCzCvss쑏mW)O[86In{mѱ6 * =6cٮy똊RB /T)cNwϙB2|_+Ie#Yv q[G^r]ŲCAu% O=; x# km" w }VCBs] )%dޗ&<J-:1Nd+`8~j2Y!z_*?/XcF-&QTtq-誾3`wZ-p}}+ezŵԩ@NʆW^vQ[4V rwdA>z91g#Hti5Q}HfY${2Nys8 bI "foh%zlӊQ4õؠJGts;\d>0gɮᬦĚ\F/7Dd;I}-*u𲚼] L:.K d\?Xpa%dL4U,㻫8\+}%3Rw*)hTbnšiOim2qTQpezHƘnxUy7풎T$,ͽ$[ngӕ5bTʲț} vo>b>"e 6~]ahltRu&5cp|47n( 䐊Ti Rd'תz EK !`m>!PtV@ȑBI9J ,aC>xgK7,|*\=$Qʦ E_DlaG/+3T$p xK_Ez3GRpl5e:ř 9a@sX'S׺щdy'_hի;"9kPHG";%5,wqjTSI~ ~!B\H+Ʒ*[! ,LcefVIUjf`hjG5;ɘtZ#vZvִV.NKE@E*x=LJ{4GN?~Y .T0gYN!38AiP2xh!zAQ1:}VܺA̽-?8TPKRZPpjGK=ځ*@4Ꙅg&`9`LJHKo෡x=a@,~95YQta8`L"E*6g`/-`I>bCX}SC܇ Ȼ ՖzՖHXK 3C\  ٤` $YnvP7ǔ8Q#c93#tg+TU2Bm zPu[Vdg 8dHHcϻD(3aiA6 r"K;>/~%1A $d;-?&p]?~M 0ЇFwH>9%=H5{d`B?o% 4~^O:sҊ,vu9#d+q ^/*ZsKDi8o¡D45sHKZcr !\]χM{/ë ?~wC2MWS3ƛU\<;E rOh:h CrpDtIyem^B;5 7s1 bWA` 0eϚه)q^tiodKY>[$V4qOe(ݝ֚J0!1~!4Nł hr$̯3k9oX )AH*^ ` EZ"&ܧZ('n$t O8G HX{f)Ь9qW>|KgY,a,pTQ oFVU/uAn@`˘^ALӦTU1P4]h*N0%7s/_epXd>- WY2`"MjC~XF 紿7JA!yMJBLg~ii% D]ˏ ܧ9*P3[,^Ҡ X(C2*ʱ8'tҀ?Ȕ>H-y`78ה[Fxa+Ǎ3@uume/۩C$:~R'ޒ?*Lr[fT)U'>\O"~`5IOZxU~PT@@fNJ`|9T&iאQ?~/,SEQG<;B;B~ӆQxo6W5 &/bzM)יYb=TxEaKEUX&"}_JLZ4P/1^\hX[Y[`ƾԣx0MAQE_w"BP4"Q{F"y~Qc' Z9$% /Լf d{5,lص{HMN2 0!fjS+L$%$z1m[+fCNC〾j?'q{/);;ǵPPbG6D0%|  Br}$:HMdPYC2k#`~0!rIH9natk{SN}R+(8_LuFXC=Z-{,k*0+|^hxLb#zSSJXm4U"N,1[| 1$.4r >^3,";!\-cIlRIA;<qfu1C +A`."4)|[SBGGC->3!{ VcsC}LU~v_V&5-ח{9l-&Gq2,t!Pef6SIkCn <3|Gd"wRSn ]6"y!IB Ѣ/W*@8dGˉ`vT N12t-L^fRO9dz en&ѩ~B?+4-R p>wl1%=:z !S+oщF{hؑ N p` tᚺ( A^X@=!Y0j#0} r*doSȨdt]jo"_x1Vns %L0X? YAJ}DH)'B (}u4G3OiF?ۃ'.v*%QP)57X`O=! %q_ZX`;>)Af.#K5żB)CrX'}= S0(xs;+Z,͌^9^CpOtJ 366yQ>,.rs$ hlq U\5DG>">HQSBnO0Ȃaa>;@TSe7mοCgEb H|wYV UNHϨ"}i}]$QBuQ#_Y2hVl1V[WH3l`mhl#ߍ^Eo=v>:P|5ꨴn_U$jı/?epa4Gгk21 SYWee Rmr'"J錃y4g;ܳ$yf*f~}H \K=PeU2A 6f#.T~D"MK8+u&s÷n-("SzH0ʢF^"g?s++ 'VfPjpk*o(R0%`ئ<,$Lp@gφFgwv ;`.@`5:?@xg7$BLIp4>9$RyZ A,#E-uEcuCV7UDIz0q8".CRt3Nt5bf* +DZU,hʮ@p& wj~uk”P{bÊu"J+%&_ s^Oj{BΓV-Jc8sJ^ZEx0b_9D`Q(8t謊 a|\~zAiSw#6Ҡ8&އ¬'3@Q-H<)³T0ȋp}| B`%GUwU!|zB~ڔຆEoJ~{ ,Tp1 Br&GpׯnY4S-0p=k`$zMgCC"E`m1@5A_$W?t7R#_$\h{gL-arDffplkz'&{V!#ZC%ex>afA@_-c$+!$١a;ao|Y>%Vp*,XF}lv;*î-JF|sQ6)#3%8 Zƞs y!16e],QZ?t1PC{( #Uktxx Fw8@C1 5ߝz2&m/ ;AW^{AO #2?t.pRH382AqjQɧnb h"K79pw ʬq ~/ ךćsFa͊ hےl+/|oSJ@R(jR.}ӱ(LN$UQOl!jA#`q쎍D ]Dz37kɟMCHqN'| PNvHIXTL6A}-q@F`(q$?pFOr_s^,hZ3AMxO xܸwԥv{( qX6HN/S#`>Z?Tx1-`h/{_`'h2ET,$S48$Rv+Fl< Y3XD!!f>+|îKbarFӉ^]*< iE$;)wsR}`5?bYh5Qpݻyu%`^c;gDrrn`ke-2_;(2ҊfgTz_2Dd/SOd9Ch|j&ɹfm $P<`b٧%DqzM>GЖBb' K>+p>y:ۧ選1H0PT Χ:G|:dcBP~M>w{CVm9_rU19iOK2Qi;I DHf~7 GlBvi[$oadRz,r:r.וzӬpZݸ \Aqǭ$$`ro2wӉ0cb(,s-aQ}BbY#T?d6 73 ' n\qJ?"ư"3Zv J +DħZj*t9S_ :2mTg+c9|_`:%E"y%S }]~Is5H joe#i׍) S.kU>,rq*7,{T`g |USJK|XtG(qg OZwk*A#*[ oMjDQ/=tx' FW6RrM9<=y{:eH;3/cN *W0ח8Bf3 r^dot*lf(&1㫖?v#,7VW1zTi(~>0Z"wHQE&L-(#H><r3k?FQџ_kLwܻ&^uGi-EV9k&Ei;_{TQOʋ>tCAY?Q ~HW,d6}'rvK:6ڞt:aښ~ Xhf =cPC⁂O}յt60h?&5 Q7y֜a 9>VWF# MmfSЎIYj;*)F'd?d w9=3!?e $}8zr瘩N_Ѩ:Q]S6솿bt`V/?D&)K6pqQBxĕ Һ^iB^6gf_В6xz FaZt0bCzYLַ!dUt.2k'P j\>DvFc7| -nYUI-e6&c)Xp'ډ*x6Φt/h( Ysj|4d]El ,;*` t֬e;?f.QsSZJ zI?8St07&tbVV/3f=2@ȳjm6=N-ۢiDo)mV]z y+kCO; n' a~[k60L+%[= 0P lCP iޫLN[W6DtX |`w6B4b}%+Ƈ{2) i#b ]iMdR W\ݲ۹*@ZAAxD%R[Â4 i7.#C}űL]`3PU㣓̌7r*S" ȸ@#zFr?`/*@bE+dD\nx3`ϵС HmQ=A ~t] [~+&a=z4}:L ]>)7i)Cݼpy1P|Mz3y]ErRzMLn-OE?֜XNz+ L..Hx˿\p٢{?Qՠq*-Ǡ !1CǴA_ڍ ({C${[fI1; y&>aFgЄ בk&۫9q1DfN7&·B GW.j z=C2k& qp췙}sE-gECJ Ź0/lGk_*f!5d&i +HUSF2)K1. !h[|W⤾MXE}FohךYa3"]g,buݬ+?-UV/aeN%v#^Zߣ<} ;:ѯ=G:k>]8"Gy|uae:?mI|lˆ3v,pe»ԓo<@8MDY } uD2O}<G2{#$uݠtM跨f,0Eʧs%Q@9P[UL$XJLtrG8S(sG6b s|B(Wq?‡-" E;Gs`>HѲ}}R'8.aZjT|[ىՅ ;> fL8\BND%{”ַ5)*A)\揜)\ʊdqLgpɁcC] 1#;QÅ16_q06,W*LNO&71/_1.بeȘ1AE{/=o;Qs2Jq*UFNPl)qcȊ&j(IwپsHV!ZO !NDSMcRTF~ j]UN-P*p87>Q#%U /JfQt̺Zw2!rxʶ>=:6SJp%)J[Vsד5Ŋ9qE6rS`hb,QCߏeHB೯QZb>sy{5Ra$8Y9<Ʒ0c ]|e΍{eS`22qmBpON6r1;tkv.Ic+CEУvNP-(}^ uSPXod?0bWj ᰾c:3)yK_\ =O f˓w ܑ9X8MKhy.V,f0yNk[׵I160h8oE\u Ԡl GrSaO@Q6huPc 2&5 5dIbw{1h_I$mYFt%a^L0ԘjLr u+Wq(QQ0\"3{mn|ޞAstAe";USVhUFD+]w}װLDw`_?0:|}v*+vtY LteVBzIg\eQ*6j_/kXO/RwKC^ ?_"ZG'}0]C$8mZkHrr`QlivB3/`[v @P̻*Q7b w\< ha%qB%AHpwU>N`a(T;sFF+Ha|bxZYC@ȪCR~B{X7 E{0aћe˥Kz[l&<}_ 4/yR7?2wBNR(h3tN Nhq sd{fFp┾9OJU_F–*RPZmɿ]LfGcpQdq=f KD5ē40T+kFNMX>Lo/nM&{<1uX{R^R1tk|L9}8M!6s<]iXl.|:;T) )K`o9AݱԒ15I65YUJťczhWE2Yq!qA ]j|yd6 XZO:d:M7pAdTަ64ml `}v;Wbpd3R 4&09EXLwc]Z|?M$I@tN_x:z.12a}n#CN*U K,TϊvX<ԗizNq ŚGt= Gt%Lizi7ijfVS^_uP%UF ܊=%ipr$ѹɳVr =bK|)3ўÞ*׋#8Q(R+~u]ܤd<ajڵ#! \#V=]mjhϣ_ U ]]d8lVVTy,h}<<qWu[D<á̢͖V?x@ 4o8S\ECa$:åPf?I;l wzjx#OΞ|<ύǬaqCN 6..ŖaVIw!1K4HpeO!rKD0O1!cͷb?@"˳&#|ix#Rp::g 7å>mpcH'A!s8'PbS$!Vb_/7Sc~ VpD}FQNgy "NjۏWvfX.G[cLJm+X6+R>DϊT |=]$ЧNч$t S2urΥp<7ۮaL[peVnOf),֬Ob%{X且ц#;b6f&f4Rh]bi1{2W}y1?54-,))E2w,Ukғ au|ꄳ&lj]yy9,ː{#(+P:~k6;2#aFjs,3]8dKjЂԺsq H{UM7V]0Fc3LPbJ↣}1;4mE]ZMZ%^4ٱ*~+HE.~-Seiε\VB>XXJ Wt2F(\닇rmeK_< ).Qo)()vD Rrq[㹩 WgČӇRI[2ƫ*otLH {=tVZ>K-ivi-T#tVCbm1mzWa -RSyʜ**O9afƸDضm,7n M99 ɇ7Vh %Cd#opHdz!àp E\}8-4tvCM;>S<j.rۜ;裻ݜ-YWǛ :Z[­tQްt!?T^9c,X*;ɼa7]F|ՐN\׎ٿ9(u(RgFi5p'#<Ĉi"rcr!F\b('( x÷`r/ɋL,dN%mr:]!'o|tgW|\*z^,;? 4_H @VJ.xl9,톚=b˙2fMA7Fy_C۴rVCxz'8 QOC!̜HM@ǠMPr{>6'(JsM e9XޜEx,jtYZ ǓI2F a3$a !;zy|6ysjN~b[OɌ&ބW5TN1[VL[> ;Ū8~7Gpcy284g0ds#Fa(R=eOONu֬E ņ\Ŀt "/ lz`첬q)P v H[.Sr^i="aK-[F^#=BnQsutHtÃ? _!i\4ɳIr%2) a62Hҕ2 `yE%^'hd 5Fo4ţ 2Zޚ&'\ w]V۩p3y&NLwܙ ӖkT^q в|-7d1 C)qә#~븄r9Ln W&BbהּQ< _5a;23 f 4aJȠFty$G=xy8gdhZ\Y* kY!IdZgC#@n|J!qrGwuϪ(%ԺC#!Y$-O6ɓau_n]7Oj-֟xlT(|ps <;zX1X+ +YoOMiR }ڒR(=koʝRK tC`SxI8K a;6ӢnxT\>XE".'Wckw*6Y,)+\-`}V(%ݝԢ+LIVf M:pC,qL=6SQXTZE.f| ~z6b cp\d413RGyM*E4|Bz j'"R>HjZ7dH > H&@t^^p–UoCAFVcc@GG7q$Fḍ..RhK~^2gkBIHKHz\/G|M#!,x6I1u2Im 5n/(S`sʿ'"l{kjF(H NIݨ$h])EulV ۨ7/z(CclQIò Đ%I=AAG^W$,4T>m'-Q\zٟ krWC1 }x*z-DMYbx&:$q31E|Y/$A ቢN*<G {H=vPQ!C}1c0՝ 2$xR”A ג&Zry1Oy8 g&/l4ݱ*-)Eiu٣\=lZy^2{^UM^^(eu˴2ԿL"v~r]`fnbۡ9;ZDkRD IA4t۳:*tC4Q`sy,ۥ[9˃IB17|Z7`elԓl LUfCtC0S0iX(,YX*Sg~cb]b5Lźa½IW䴬}y.dDz(Fy n%o,e~u?cjad 2c(nWBŎ$Q`3;C%T>5is~izg/ƄP1Lr.-> 5gݒ1m88 ;gͲ,C?8~E,L De 5˪nZnA*XmmqHm$QnqґuтVATTsR RreK~j۹e?33>R'Z_1 ~SY#Ř"s.*[fi>k02-Uvt_ #|yY㩊2@X҅2 p_Đ㘀:E@V0E|oqEUȲk2 6~3w] D`LiXGg psFBf28UioM)l~8scL,'eh2ό3˯*jH~]p?dX;'x R*[Vm] !5`֦eJџeZb21:`*KY`ZSHP ^rDݜ*6L&/άu:.q$k& NX3{65Ѓ@5b"zr1v ȪO|[h1?5DS\JLO,KPUB-e!iZrt}3nG~tL&~\Q_͊"H0% -#XqeÚ+MMդXYK/ 5]cCO/CJHU' Mizg#LMya<.Jnn~r2kIpsK oյgF(Gg?5_xLeBҶ7LMȳfCs݌m%X2kk&YU9P6!^H./oVRDX?30/b/R;',hVQG[QN;H5ʓK/fB]okQ6C8mM|I~Fh! 6)ZSAAKվŖKXHp7Q:HAm2lEJJ=ʢ` !Y?0Cڧ)Қ2^|!( ~ B} Di$s$F2Do֜R3R,f(ĉuHӰ*0r3{~g2$d#_U`^Ufմ1BLp #d WY=6 F+I1ͱōTHzRdh(3dߎq \$+==R{sw5]vGt]ǎf014q]'%t b OoYVYay-q3NuZjN~cb|GX.R/sL_ɷZem I}3,'!f+%*[ Q+/"t/H_|WWx~%w2? ⍤ةmH4ņuW"WJ,C4oP8! ֱuRM]mX/{Aj}|/e۠ o1զ;&"L* wFW96$ RJJ'.[#+L۝ k_\Q$~+˚79$ ga2: \|r~хOF"q=j =S_)̓%inܲK="w$Q7Bu9]R*\-Oe+⾱vc.'ˋp'AqJ#UG4F)=Y,f청sFu\;9R0EIJl87%&Jܭu]9DU^J F9Ϥz'þ7?.eX;qs M>?6$Bf<A+@i 䥜Ix{|ӌ$S,W~]>nL|7a2`D=4::i. aCQ?d]kr{ĨmTN./yEG$"*W z7$(T{?9tsM‰#Hn-l8SF%?Xi0Ȳ,+F3Lj@33 %e{c17%!+{DrLɶƵ1 )ړT(ݳFnUrF؊xBF\G}8tΘLeny+XDgBD"F–~aևiI}(TJa\YtE/8 {FaoMOKm9(ˋ`݉w!:^z#ں e5!9ㆯj κF4 +WtR*8.z~d$FyH㐎(JUAl'6G' hX^l wX..yhS?ᅤ1LB6r(nRK#r٪t06_ Z1?N-W, 96/rO`\JsH $p%6h jGmRz2 :@UlѮ;+d-OgK:NVxda2ScHO~"aI8J^nV<tv˸GiMտdpfGF+հv92Ɍ\5Lب^lF9#"q}(1Y>(Ugт?G -Bo\ԏx?o7d$xaX?G8sbJ?ԨѱzdɽTo6i-ցÉh3-ێ]tG&ѷr!UpɵuiʤIԶ89xԱ56$fL5eO蹑`eo ։1A1;&XĹVF&y?)~Lvse}`gekmHM! KOOs._\蓚L%V@&=ku`20jۑ^lj㠨#ǣ3a&`5?!JKY{g?5Lt2ً9a8JF-[aY͹+4]N|>it#7M8pE cdq0s VY8)`Xb Q;1[Uc(̡>Voc (Ց S;uhhNBRHە4FJ9CI7,Bx%T6@Op%lOb\chq@KaL̗(UZ̥M.mI(W c76Ejf96̼}^-Uqt۩UV3s$nRC 0ϩ-YQd`øsԃ,w`0#&z B$IɝNy5>#X1si3& ikB\eWWK+2ۢ5 wvb Ki7f]mM,g .{R$j2QBk~yE.VjcrU?Wo#)YtFf0RT` Ո==cD)CW YFcC0S9.5'C[ ug8VV,CE7GaL$۲ ]ptxDr>aφB3 !3xi{;bU7%NZ$1?W 0ɽ..&NQkviZnw'g^\;Jn(rs(C?2"mR` [ސQ%̜*N0Yyb0{~qH.!"Ȱ)*dIpř5$|DW7^϶&xh2.•`Y|>FslXF^ҋwXrzG!!É5DYq/s5qęaEuSot2ʺX*Ž}3lf*ᷜhr_`{q:f2yHF^qDT1 BvIA@lËYA:2i?3=h-h.wD8&cE-#ʒdTL AӬ?f>UI_؍vlܠRN9s\o"?>Q^& tV r tdtB>VqDڷ@kyv=Tn5+xU_pda(wGbX$@./2v"n )_ Ss\tYM>jB%_))>CC^4ޥRfn8:&)dQC+FHù5iXB[Ofih-S&3"9XB"6㠠WWz,<8wN܁0N˹"m0Jp .nKJrɗ*s39 t ,+`\p 5'Q^ػ)"^JW0؉+ԙv S.T"UP2'v T~(v~Cs䬙Ɨ,k2` S;j6ĩfmgFWpR:iA- G3<RrEs=Ig'_:}>1;bWvHkW8hlFjAt)d5B;eNxm7Yv̭e*.f I&JgHrMjEyμ#qߋnXI&nU{:Nh@yNG{82^ud.4ď&x &% `Fz8b(K3դsEܴ70gTd@{SjMj"c#lsV8=M]%owg_.:7GJd:Ӈ]Kus1M[ × Qd*náՑ?$*㈢!z21tfD>2;H . ɲ1i60dYFG8л;`SMzqy}2>mȯ1NӧB}8K5672=D'3I/$t`S L*j3R g_s$x7Crt柭D?@cq3%()l(4-DuǿpRnCOBXsZD:ԹJ?Ȓ/N:6\q_C q94:v½˯O{n \KaMFySj1.=ϋ-hlzLqi͙[E8ޝWH{ƭI."=86a !H܉#ȴ.{w*6 XV$ "r> t\s&Xf98Nˮ-n,i6w[f>wK+^c_7S|m8HGcp/nhcedF SRY|zO2LV9&ɻ'$.ԟ9M|Sv =MKKZ}Hsf\yfY<fЖM M3 xaULL/vI8؟7)1O+"E5,k[y NS0A3"f,/("|fFژs$$P5m \dP(O2iW V\x|0Ru$\)'z׵1[pG^~þwS̻yÒ@NZ ]u'p84CZu]ܒߟg;):I?t(K cОDv'Mмc,sf%t\$;[e5lĎHZlPQ*LX]-ne*KDzǖU^8.npo7ۛL+/a֌2t3c] [9.;bZN?D#Kx#@b&`I.yA&L!fAgŔc(馰?;R Hŏ<Nٕe`arܯG Ͳ[y!NnRRj)c;YowRkD]^i$ߩj=sM!lZFFs%q)S=HHl*Ӡ.bSsl9EY': /}KC{ju_5s%j=^%zW'(5Ev(\"hќQ{UN4& c.-ϠȈvDdDyM=`dqYc`4*UEYK&X d h\i19mӉ:fIdz6Ro$CB.eG`b3b6I %8Ct<;A7GOl0/\U22D[1rҸ+CKD^ xz}͎?&C|Axí֣E[HsMh{UډBYxȽG3 Z2O$NKȿ DMyZA֨fkv=ëKROm̱۪t'BF{ވ CĆ$TBZN}]'$; ̿5nsӌAwFVqWB[ؕ];g c21m)xEYG{ןGX̡D22{Kͱi@D콨/" 1taFSic$G.3 G,ܑj8 |hsEP޹CW24O1`o@ŸѾctyNik4#FYr2^A}jKzt=f1!(e̱_{gk6uC^&F2aI* b";ڍ,*)V0HܣxA#ZcZ7~ˌ-%sc\擻#Z(VW׸K]%BjB9+)2c/eWkgL̓=P Q8J]#a5E_x&dEǼb܎с!,Z0:vU8zcQÆ1x|~Lpޗ$e&T F U j$pLx0P@"p> ;Hw/*rg4_銈<Kn<^zzAF8R;tVJD}<ȗ+i1,X!/Czo]Ee8I|]9IaL?"@U0Y5C)EVD4ǣUgtLg gU>INY- s45IMoM3'#U8 JG&6,;h`\T[63(=HQ[?u? ԅRTe ҍE̠tǨ!I]aD25>P1r&$#X\xlӐ5;!zU^J|}eǵ|_>I*C(s"W$lwEZzań rUxX±:ߊc(癶DzϲHGcƩV˞c#H"0.ڿ6B}&}rzbܱ&j\ɚ[ıyxB1\ [!-[1AĬ?7яBLCcryAݹj9>y-{. :o%<*ͬȠҡ,_h.# 5kr,MC(4j=+R)9Hf̱OD]!w ^E{GBkS-e12@I&1uqsd 'kne*{ǪNpFGs -enRl5Yf%+i;" mHJ JhByS䅙S1܂@Mq.])@2O+'fk ɝ"A6-"KM'oڝ?}^ DcԵhG˿B}-"˟-y7{B4ҌS{kXdRfD-8mt #CTWW'[o+DkIfV'# i`5$žk3xN@԰R~܏D"23YX*#TmTң8f(6 rX 1.y8zaX884A NkqEp _J><*<7-?} b||߷d5~L3jLJ%f<$ItiFWx5(J,Rzt˟CΏ2q?[e ,xr"N`:{ac4k'T+ܓ1׾Z) :< F o dcDTe\7(磄.F0?7UlՔU13%īWN;&#AbL*t-U[W!g:gI4U)ԝM[WF4]lȍsF))BZ୷)A8p=h|2doE;cQkf9|`Y1ՆPiLmlz -ct^0?9FN]>Ge1'OY[S^{Pd` 9x:fdl} r!>r~:(^~-a~7j,0V~y8ֻZUwɳ)~K1 d-RV ńH(1al]GD"1Dmc+DF/ y̜Ƴwc+H'le0rAX M$|5=vzcnrG1r #S3Dߺ7;,AIcϙlz)4G=HԶT7??#"+mjEcR1*n?vk;Fj̹<09_MZz\ | %.o,zS$sEGPw$S fPs2/src/0000755000176200001440000000000014645664251011402 5ustar liggesuserss2/src/tests/0000755000176200001440000000000014530411473012531 5ustar liggesuserss2/src/tests/soname.h0000644000176200001440000000026214530411473014164 0ustar liggesusers#include "openssl/opensslv.h" #define XSTR(x) STR(x) #define STR(x) #x #ifdef SHLIB_VERSION_NUMBER echo XSTR(SHLIB_VERSION_NUMBER) #else echo XSTR(OPENSSL_SHLIB_VERSION) #endif s2/src/tests/main.c0000644000176200001440000000047214530411473013624 0ustar liggesusers#include #include #include #include #include int main() { OpenSSL_add_all_digests(); OpenSSL_add_all_algorithms(); OpenSSL_add_all_ciphers(); ERR_load_crypto_strings(); SSL_load_error_strings(); SSL_library_init(); } s2/src/RcppExports.cpp0000644000176200001440000020475614540324461014403 0ustar liggesusers// Generated by using Rcpp::compileAttributes() -> do not edit by hand // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 #include using namespace Rcpp; #ifdef RCPP_USE_GLOBAL_ROSTREAM Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); #endif // cpp_s2_init void cpp_s2_init(); RcppExport SEXP _s2_cpp_s2_init() { BEGIN_RCPP Rcpp::RNGScope rcpp_rngScope_gen; cpp_s2_init(); return R_NilValue; END_RCPP } // cpp_s2_is_collection LogicalVector cpp_s2_is_collection(List geog); RcppExport SEXP _s2_cpp_s2_is_collection(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_is_collection(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_is_valid LogicalVector cpp_s2_is_valid(List geog); RcppExport SEXP _s2_cpp_s2_is_valid(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_is_valid(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_is_valid_reason CharacterVector cpp_s2_is_valid_reason(List geog); RcppExport SEXP _s2_cpp_s2_is_valid_reason(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_is_valid_reason(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_dimension IntegerVector cpp_s2_dimension(List geog); RcppExport SEXP _s2_cpp_s2_dimension(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_dimension(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_num_points IntegerVector cpp_s2_num_points(List geog); RcppExport SEXP _s2_cpp_s2_num_points(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_num_points(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_is_empty LogicalVector cpp_s2_is_empty(List geog); RcppExport SEXP _s2_cpp_s2_is_empty(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_is_empty(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_area NumericVector cpp_s2_area(List geog); RcppExport SEXP _s2_cpp_s2_area(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_area(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_length NumericVector cpp_s2_length(List geog); RcppExport SEXP _s2_cpp_s2_length(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_length(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_perimeter NumericVector cpp_s2_perimeter(List geog); RcppExport SEXP _s2_cpp_s2_perimeter(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_perimeter(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_x NumericVector cpp_s2_x(List geog); RcppExport SEXP _s2_cpp_s2_x(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_x(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_y NumericVector cpp_s2_y(List geog); RcppExport SEXP _s2_cpp_s2_y(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_y(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_project_normalized NumericVector cpp_s2_project_normalized(List geog1, List geog2); RcppExport SEXP _s2_cpp_s2_project_normalized(SEXP geog1SEXP, SEXP geog2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_project_normalized(geog1, geog2)); return rcpp_result_gen; END_RCPP } // cpp_s2_distance NumericVector cpp_s2_distance(List geog1, List geog2); RcppExport SEXP _s2_cpp_s2_distance(SEXP geog1SEXP, SEXP geog2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_distance(geog1, geog2)); return rcpp_result_gen; END_RCPP } // cpp_s2_max_distance NumericVector cpp_s2_max_distance(List geog1, List geog2); RcppExport SEXP _s2_cpp_s2_max_distance(SEXP geog1SEXP, SEXP geog2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_max_distance(geog1, geog2)); return rcpp_result_gen; END_RCPP } // cpp_s2_bounds_cap DataFrame cpp_s2_bounds_cap(List geog); RcppExport SEXP _s2_cpp_s2_bounds_cap(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_bounds_cap(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_bounds_rect DataFrame cpp_s2_bounds_rect(List geog); RcppExport SEXP _s2_cpp_s2_bounds_rect(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_bounds_rect(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_union_normalize List cpp_s2_cell_union_normalize(List cellUnionVector); RcppExport SEXP _s2_cpp_s2_cell_union_normalize(SEXP cellUnionVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type cellUnionVector(cellUnionVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_union_normalize(cellUnionVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_union_is_na LogicalVector cpp_s2_cell_union_is_na(List cellUnionVector); RcppExport SEXP _s2_cpp_s2_cell_union_is_na(SEXP cellUnionVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type cellUnionVector(cellUnionVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_union_is_na(cellUnionVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_union_contains LogicalVector cpp_s2_cell_union_contains(List cellUnionVector1, List cellUnionVector2); RcppExport SEXP _s2_cpp_s2_cell_union_contains(SEXP cellUnionVector1SEXP, SEXP cellUnionVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type cellUnionVector1(cellUnionVector1SEXP); Rcpp::traits::input_parameter< List >::type cellUnionVector2(cellUnionVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_union_contains(cellUnionVector1, cellUnionVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_union_contains_cell LogicalVector cpp_s2_cell_union_contains_cell(List cellUnionVector, NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_union_contains_cell(SEXP cellUnionVectorSEXP, SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type cellUnionVector(cellUnionVectorSEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_union_contains_cell(cellUnionVector, cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_union_intersects LogicalVector cpp_s2_cell_union_intersects(List cellUnionVector1, List cellUnionVector2); RcppExport SEXP _s2_cpp_s2_cell_union_intersects(SEXP cellUnionVector1SEXP, SEXP cellUnionVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type cellUnionVector1(cellUnionVector1SEXP); Rcpp::traits::input_parameter< List >::type cellUnionVector2(cellUnionVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_union_intersects(cellUnionVector1, cellUnionVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_union_intersection List cpp_s2_cell_union_intersection(List cellUnionVector1, List cellUnionVector2); RcppExport SEXP _s2_cpp_s2_cell_union_intersection(SEXP cellUnionVector1SEXP, SEXP cellUnionVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type cellUnionVector1(cellUnionVector1SEXP); Rcpp::traits::input_parameter< List >::type cellUnionVector2(cellUnionVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_union_intersection(cellUnionVector1, cellUnionVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_union_union List cpp_s2_cell_union_union(List cellUnionVector1, List cellUnionVector2); RcppExport SEXP _s2_cpp_s2_cell_union_union(SEXP cellUnionVector1SEXP, SEXP cellUnionVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type cellUnionVector1(cellUnionVector1SEXP); Rcpp::traits::input_parameter< List >::type cellUnionVector2(cellUnionVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_union_union(cellUnionVector1, cellUnionVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_union_difference List cpp_s2_cell_union_difference(List cellUnionVector1, List cellUnionVector2); RcppExport SEXP _s2_cpp_s2_cell_union_difference(SEXP cellUnionVector1SEXP, SEXP cellUnionVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type cellUnionVector1(cellUnionVector1SEXP); Rcpp::traits::input_parameter< List >::type cellUnionVector2(cellUnionVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_union_difference(cellUnionVector1, cellUnionVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_geography_from_cell_union List cpp_s2_geography_from_cell_union(List cellUnionVector); RcppExport SEXP _s2_cpp_s2_geography_from_cell_union(SEXP cellUnionVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type cellUnionVector(cellUnionVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_geography_from_cell_union(cellUnionVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_covering_cell_ids List cpp_s2_covering_cell_ids(List geog, int min_level, int max_level, int max_cells, NumericVector buffer, bool interior); RcppExport SEXP _s2_cpp_s2_covering_cell_ids(SEXP geogSEXP, SEXP min_levelSEXP, SEXP max_levelSEXP, SEXP max_cellsSEXP, SEXP bufferSEXP, SEXP interiorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< int >::type min_level(min_levelSEXP); Rcpp::traits::input_parameter< int >::type max_level(max_levelSEXP); Rcpp::traits::input_parameter< int >::type max_cells(max_cellsSEXP); Rcpp::traits::input_parameter< NumericVector >::type buffer(bufferSEXP); Rcpp::traits::input_parameter< bool >::type interior(interiorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_covering_cell_ids(geog, min_level, max_level, max_cells, buffer, interior)); return rcpp_result_gen; END_RCPP } // cpp_s2_covering_cell_ids_agg List cpp_s2_covering_cell_ids_agg(List geog, int min_level, int max_level, int max_cells, double buffer, bool interior, bool naRm); RcppExport SEXP _s2_cpp_s2_covering_cell_ids_agg(SEXP geogSEXP, SEXP min_levelSEXP, SEXP max_levelSEXP, SEXP max_cellsSEXP, SEXP bufferSEXP, SEXP interiorSEXP, SEXP naRmSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< int >::type min_level(min_levelSEXP); Rcpp::traits::input_parameter< int >::type max_level(max_levelSEXP); Rcpp::traits::input_parameter< int >::type max_cells(max_cellsSEXP); Rcpp::traits::input_parameter< double >::type buffer(bufferSEXP); Rcpp::traits::input_parameter< bool >::type interior(interiorSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_covering_cell_ids_agg(geog, min_level, max_level, max_cells, buffer, interior, naRm)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_sentinel NumericVector cpp_s2_cell_sentinel(); RcppExport SEXP _s2_cpp_s2_cell_sentinel() { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_sentinel()); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_from_string NumericVector cpp_s2_cell_from_string(CharacterVector cellString); RcppExport SEXP _s2_cpp_s2_cell_from_string(SEXP cellStringSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type cellString(cellStringSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_from_string(cellString)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_from_lnglat NumericVector cpp_s2_cell_from_lnglat(List lnglat); RcppExport SEXP _s2_cpp_s2_cell_from_lnglat(SEXP lnglatSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type lnglat(lnglatSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_from_lnglat(lnglat)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_to_lnglat List cpp_s2_cell_to_lnglat(NumericVector cellId); RcppExport SEXP _s2_cpp_s2_cell_to_lnglat(SEXP cellIdSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellId(cellIdSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_to_lnglat(cellId)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_to_cell_union List cpp_s2_cell_to_cell_union(NumericVector cellId); RcppExport SEXP _s2_cpp_s2_cell_to_cell_union(SEXP cellIdSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellId(cellIdSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_to_cell_union(cellId)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_is_na LogicalVector cpp_s2_cell_is_na(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_is_na(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_is_na(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_sort NumericVector cpp_s2_cell_sort(NumericVector cellIdVector, bool decreasing); RcppExport SEXP _s2_cpp_s2_cell_sort(SEXP cellIdVectorSEXP, SEXP decreasingSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); Rcpp::traits::input_parameter< bool >::type decreasing(decreasingSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_sort(cellIdVector, decreasing)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_range NumericVector cpp_s2_cell_range(NumericVector cellIdVector, bool naRm); RcppExport SEXP _s2_cpp_s2_cell_range(SEXP cellIdVectorSEXP, SEXP naRmSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_range(cellIdVector, naRm)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_unique NumericVector cpp_s2_cell_unique(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_unique(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_unique(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_to_string CharacterVector cpp_s2_cell_to_string(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_to_string(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_to_string(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_debug_string CharacterVector cpp_s2_cell_debug_string(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_debug_string(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_debug_string(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_is_valid LogicalVector cpp_s2_cell_is_valid(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_is_valid(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_is_valid(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_center List cpp_s2_cell_center(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_center(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_center(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_polygon List cpp_s2_cell_polygon(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_polygon(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_polygon(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_vertex List cpp_s2_cell_vertex(NumericVector cellIdVector, IntegerVector k); RcppExport SEXP _s2_cpp_s2_cell_vertex(SEXP cellIdVectorSEXP, SEXP kSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); Rcpp::traits::input_parameter< IntegerVector >::type k(kSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_vertex(cellIdVector, k)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_level IntegerVector cpp_s2_cell_level(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_level(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_level(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_area NumericVector cpp_s2_cell_area(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_area(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_area(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_area_approx NumericVector cpp_s2_cell_area_approx(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_area_approx(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_area_approx(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_parent NumericVector cpp_s2_cell_parent(NumericVector cellIdVector, IntegerVector level); RcppExport SEXP _s2_cpp_s2_cell_parent(SEXP cellIdVectorSEXP, SEXP levelSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); Rcpp::traits::input_parameter< IntegerVector >::type level(levelSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_parent(cellIdVector, level)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_child NumericVector cpp_s2_cell_child(NumericVector cellIdVector, IntegerVector k); RcppExport SEXP _s2_cpp_s2_cell_child(SEXP cellIdVectorSEXP, SEXP kSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); Rcpp::traits::input_parameter< IntegerVector >::type k(kSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_child(cellIdVector, k)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_edge_neighbour NumericVector cpp_s2_cell_edge_neighbour(NumericVector cellIdVector, IntegerVector k); RcppExport SEXP _s2_cpp_s2_cell_edge_neighbour(SEXP cellIdVectorSEXP, SEXP kSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); Rcpp::traits::input_parameter< IntegerVector >::type k(kSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_edge_neighbour(cellIdVector, k)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_cummax NumericVector cpp_s2_cell_cummax(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_cummax(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_cummax(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_cummin NumericVector cpp_s2_cell_cummin(NumericVector cellIdVector); RcppExport SEXP _s2_cpp_s2_cell_cummin(SEXP cellIdVectorSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector(cellIdVectorSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_cummin(cellIdVector)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_eq LogicalVector cpp_s2_cell_eq(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_eq(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_eq(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_neq LogicalVector cpp_s2_cell_neq(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_neq(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_neq(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_lt LogicalVector cpp_s2_cell_lt(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_lt(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_lt(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_lte LogicalVector cpp_s2_cell_lte(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_lte(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_lte(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_gte LogicalVector cpp_s2_cell_gte(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_gte(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_gte(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_gt LogicalVector cpp_s2_cell_gt(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_gt(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_gt(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_contains LogicalVector cpp_s2_cell_contains(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_contains(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_contains(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_may_intersect LogicalVector cpp_s2_cell_may_intersect(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_may_intersect(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_may_intersect(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_distance NumericVector cpp_s2_cell_distance(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_distance(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_distance(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_max_distance NumericVector cpp_s2_cell_max_distance(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_max_distance(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_max_distance(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_common_ancestor_level IntegerVector cpp_s2_cell_common_ancestor_level(NumericVector cellIdVector1, NumericVector cellIdVector2); RcppExport SEXP _s2_cpp_s2_cell_common_ancestor_level(SEXP cellIdVector1SEXP, SEXP cellIdVector2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellIdVector1(cellIdVector1SEXP); Rcpp::traits::input_parameter< NumericVector >::type cellIdVector2(cellIdVector2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_common_ancestor_level(cellIdVector1, cellIdVector2)); return rcpp_result_gen; END_RCPP } // cpp_s2_cell_common_ancestor_level_agg int cpp_s2_cell_common_ancestor_level_agg(NumericVector cellId); RcppExport SEXP _s2_cpp_s2_cell_common_ancestor_level_agg(SEXP cellIdSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type cellId(cellIdSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_cell_common_ancestor_level_agg(cellId)); return rcpp_result_gen; END_RCPP } // s2_geography_full List s2_geography_full(LogicalVector x); RcppExport SEXP _s2_s2_geography_full(SEXP xSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< LogicalVector >::type x(xSEXP); rcpp_result_gen = Rcpp::wrap(s2_geography_full(x)); return rcpp_result_gen; END_RCPP } // cpp_s2_geography_is_na LogicalVector cpp_s2_geography_is_na(List geog); RcppExport SEXP _s2_cpp_s2_geography_is_na(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_geography_is_na(geog)); return rcpp_result_gen; END_RCPP } // s2_lnglat_from_s2_point List s2_lnglat_from_s2_point(List s2_point); RcppExport SEXP _s2_s2_lnglat_from_s2_point(SEXP s2_pointSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type s2_point(s2_pointSEXP); rcpp_result_gen = Rcpp::wrap(s2_lnglat_from_s2_point(s2_point)); return rcpp_result_gen; END_RCPP } // s2_point_from_s2_lnglat List s2_point_from_s2_lnglat(List s2_lnglat); RcppExport SEXP _s2_s2_point_from_s2_lnglat(SEXP s2_lnglatSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type s2_lnglat(s2_lnglatSEXP); rcpp_result_gen = Rcpp::wrap(s2_point_from_s2_lnglat(s2_lnglat)); return rcpp_result_gen; END_RCPP } // cpp_s2_closest_feature IntegerVector cpp_s2_closest_feature(List geog1, List geog2); RcppExport SEXP _s2_cpp_s2_closest_feature(SEXP geog1SEXP, SEXP geog2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_closest_feature(geog1, geog2)); return rcpp_result_gen; END_RCPP } // cpp_s2_farthest_feature IntegerVector cpp_s2_farthest_feature(List geog1, List geog2); RcppExport SEXP _s2_cpp_s2_farthest_feature(SEXP geog1SEXP, SEXP geog2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_farthest_feature(geog1, geog2)); return rcpp_result_gen; END_RCPP } // cpp_s2_closest_edges List cpp_s2_closest_edges(List geog1, List geog2, int n, double min_distance, double max_distance); RcppExport SEXP _s2_cpp_s2_closest_edges(SEXP geog1SEXP, SEXP geog2SEXP, SEXP nSEXP, SEXP min_distanceSEXP, SEXP max_distanceSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< int >::type n(nSEXP); Rcpp::traits::input_parameter< double >::type min_distance(min_distanceSEXP); Rcpp::traits::input_parameter< double >::type max_distance(max_distanceSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_closest_edges(geog1, geog2, n, min_distance, max_distance)); return rcpp_result_gen; END_RCPP } // cpp_s2_may_intersect_matrix List cpp_s2_may_intersect_matrix(List geog1, List geog2, int maxEdgesPerCell, int maxFeatureCells, List s2options); RcppExport SEXP _s2_cpp_s2_may_intersect_matrix(SEXP geog1SEXP, SEXP geog2SEXP, SEXP maxEdgesPerCellSEXP, SEXP maxFeatureCellsSEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< int >::type maxEdgesPerCell(maxEdgesPerCellSEXP); Rcpp::traits::input_parameter< int >::type maxFeatureCells(maxFeatureCellsSEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_may_intersect_matrix(geog1, geog2, maxEdgesPerCell, maxFeatureCells, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_contains_matrix List cpp_s2_contains_matrix(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_contains_matrix(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_contains_matrix(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_within_matrix List cpp_s2_within_matrix(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_within_matrix(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_within_matrix(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_intersects_matrix List cpp_s2_intersects_matrix(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_intersects_matrix(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_intersects_matrix(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_equals_matrix List cpp_s2_equals_matrix(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_equals_matrix(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_equals_matrix(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_touches_matrix List cpp_s2_touches_matrix(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_touches_matrix(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_touches_matrix(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_dwithin_matrix List cpp_s2_dwithin_matrix(List geog1, List geog2, double distance); RcppExport SEXP _s2_cpp_s2_dwithin_matrix(SEXP geog1SEXP, SEXP geog2SEXP, SEXP distanceSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< double >::type distance(distanceSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_dwithin_matrix(geog1, geog2, distance)); return rcpp_result_gen; END_RCPP } // cpp_s2_distance_matrix NumericMatrix cpp_s2_distance_matrix(List geog1, List geog2); RcppExport SEXP _s2_cpp_s2_distance_matrix(SEXP geog1SEXP, SEXP geog2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_distance_matrix(geog1, geog2)); return rcpp_result_gen; END_RCPP } // cpp_s2_max_distance_matrix NumericMatrix cpp_s2_max_distance_matrix(List geog1, List geog2); RcppExport SEXP _s2_cpp_s2_max_distance_matrix(SEXP geog1SEXP, SEXP geog2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_max_distance_matrix(geog1, geog2)); return rcpp_result_gen; END_RCPP } // cpp_s2_contains_matrix_brute_force List cpp_s2_contains_matrix_brute_force(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_contains_matrix_brute_force(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_contains_matrix_brute_force(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_within_matrix_brute_force List cpp_s2_within_matrix_brute_force(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_within_matrix_brute_force(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_within_matrix_brute_force(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_intersects_matrix_brute_force List cpp_s2_intersects_matrix_brute_force(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_intersects_matrix_brute_force(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_intersects_matrix_brute_force(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_disjoint_matrix_brute_force List cpp_s2_disjoint_matrix_brute_force(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_disjoint_matrix_brute_force(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_disjoint_matrix_brute_force(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_equals_matrix_brute_force List cpp_s2_equals_matrix_brute_force(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_equals_matrix_brute_force(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_equals_matrix_brute_force(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_dwithin_matrix_brute_force List cpp_s2_dwithin_matrix_brute_force(List geog1, List geog2, double distance); RcppExport SEXP _s2_cpp_s2_dwithin_matrix_brute_force(SEXP geog1SEXP, SEXP geog2SEXP, SEXP distanceSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< double >::type distance(distanceSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_dwithin_matrix_brute_force(geog1, geog2, distance)); return rcpp_result_gen; END_RCPP } // cpp_s2_intersects LogicalVector cpp_s2_intersects(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_intersects(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_intersects(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_equals LogicalVector cpp_s2_equals(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_equals(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_equals(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_contains LogicalVector cpp_s2_contains(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_contains(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_contains(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_touches LogicalVector cpp_s2_touches(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_touches(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_touches(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_dwithin LogicalVector cpp_s2_dwithin(List geog1, List geog2, NumericVector distance); RcppExport SEXP _s2_cpp_s2_dwithin(SEXP geog1SEXP, SEXP geog2SEXP, SEXP distanceSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< NumericVector >::type distance(distanceSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_dwithin(geog1, geog2, distance)); return rcpp_result_gen; END_RCPP } // cpp_s2_prepared_dwithin LogicalVector cpp_s2_prepared_dwithin(List geog1, List geog2, NumericVector distance); RcppExport SEXP _s2_cpp_s2_prepared_dwithin(SEXP geog1SEXP, SEXP geog2SEXP, SEXP distanceSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< NumericVector >::type distance(distanceSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_prepared_dwithin(geog1, geog2, distance)); return rcpp_result_gen; END_RCPP } // cpp_s2_intersects_box LogicalVector cpp_s2_intersects_box(List geog, NumericVector lng1, NumericVector lat1, NumericVector lng2, NumericVector lat2, IntegerVector detail, List s2options); RcppExport SEXP _s2_cpp_s2_intersects_box(SEXP geogSEXP, SEXP lng1SEXP, SEXP lat1SEXP, SEXP lng2SEXP, SEXP lat2SEXP, SEXP detailSEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< NumericVector >::type lng1(lng1SEXP); Rcpp::traits::input_parameter< NumericVector >::type lat1(lat1SEXP); Rcpp::traits::input_parameter< NumericVector >::type lng2(lng2SEXP); Rcpp::traits::input_parameter< NumericVector >::type lat2(lat2SEXP); Rcpp::traits::input_parameter< IntegerVector >::type detail(detailSEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_intersects_box(geog, lng1, lat1, lng2, lat2, detail, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_intersection List cpp_s2_intersection(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_intersection(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_intersection(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_union List cpp_s2_union(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_union(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_union(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_difference List cpp_s2_difference(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_difference(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_difference(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_sym_difference List cpp_s2_sym_difference(List geog1, List geog2, List s2options); RcppExport SEXP _s2_cpp_s2_sym_difference(SEXP geog1SEXP, SEXP geog2SEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_sym_difference(geog1, geog2, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_coverage_union_agg List cpp_s2_coverage_union_agg(List geog, List s2options, bool naRm); RcppExport SEXP _s2_cpp_s2_coverage_union_agg(SEXP geogSEXP, SEXP s2optionsSEXP, SEXP naRmSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_coverage_union_agg(geog, s2options, naRm)); return rcpp_result_gen; END_RCPP } // cpp_s2_union_agg List cpp_s2_union_agg(List geog, List s2options, bool naRm); RcppExport SEXP _s2_cpp_s2_union_agg(SEXP geogSEXP, SEXP s2optionsSEXP, SEXP naRmSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_union_agg(geog, s2options, naRm)); return rcpp_result_gen; END_RCPP } // cpp_s2_centroid_agg List cpp_s2_centroid_agg(List geog, bool naRm); RcppExport SEXP _s2_cpp_s2_centroid_agg(SEXP geogSEXP, SEXP naRmSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_centroid_agg(geog, naRm)); return rcpp_result_gen; END_RCPP } // cpp_s2_rebuild_agg List cpp_s2_rebuild_agg(List geog, List s2options, bool naRm); RcppExport SEXP _s2_cpp_s2_rebuild_agg(SEXP geogSEXP, SEXP s2optionsSEXP, SEXP naRmSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_rebuild_agg(geog, s2options, naRm)); return rcpp_result_gen; END_RCPP } // cpp_s2_closest_point List cpp_s2_closest_point(List geog1, List geog2); RcppExport SEXP _s2_cpp_s2_closest_point(SEXP geog1SEXP, SEXP geog2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_closest_point(geog1, geog2)); return rcpp_result_gen; END_RCPP } // cpp_s2_minimum_clearance_line_between List cpp_s2_minimum_clearance_line_between(List geog1, List geog2); RcppExport SEXP _s2_cpp_s2_minimum_clearance_line_between(SEXP geog1SEXP, SEXP geog2SEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog1(geog1SEXP); Rcpp::traits::input_parameter< List >::type geog2(geog2SEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_minimum_clearance_line_between(geog1, geog2)); return rcpp_result_gen; END_RCPP } // cpp_s2_centroid List cpp_s2_centroid(List geog); RcppExport SEXP _s2_cpp_s2_centroid(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_centroid(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_point_on_surface List cpp_s2_point_on_surface(List geog); RcppExport SEXP _s2_cpp_s2_point_on_surface(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_point_on_surface(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_boundary List cpp_s2_boundary(List geog); RcppExport SEXP _s2_cpp_s2_boundary(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_boundary(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_rebuild List cpp_s2_rebuild(List geog, List s2options); RcppExport SEXP _s2_cpp_s2_rebuild(SEXP geogSEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_rebuild(geog, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_unary_union List cpp_s2_unary_union(List geog, List s2options); RcppExport SEXP _s2_cpp_s2_unary_union(SEXP geogSEXP, SEXP s2optionsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< List >::type s2options(s2optionsSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_unary_union(geog, s2options)); return rcpp_result_gen; END_RCPP } // cpp_s2_interpolate_normalized List cpp_s2_interpolate_normalized(List geog, NumericVector distanceNormalized); RcppExport SEXP _s2_cpp_s2_interpolate_normalized(SEXP geogSEXP, SEXP distanceNormalizedSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< NumericVector >::type distanceNormalized(distanceNormalizedSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_interpolate_normalized(geog, distanceNormalized)); return rcpp_result_gen; END_RCPP } // cpp_s2_buffer_cells List cpp_s2_buffer_cells(List geog, NumericVector distance, int maxCells, int minLevel); RcppExport SEXP _s2_cpp_s2_buffer_cells(SEXP geogSEXP, SEXP distanceSEXP, SEXP maxCellsSEXP, SEXP minLevelSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< NumericVector >::type distance(distanceSEXP); Rcpp::traits::input_parameter< int >::type maxCells(maxCellsSEXP); Rcpp::traits::input_parameter< int >::type minLevel(minLevelSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_buffer_cells(geog, distance, maxCells, minLevel)); return rcpp_result_gen; END_RCPP } // cpp_s2_convex_hull List cpp_s2_convex_hull(List geog); RcppExport SEXP _s2_cpp_s2_convex_hull(SEXP geogSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_convex_hull(geog)); return rcpp_result_gen; END_RCPP } // cpp_s2_convex_hull_agg List cpp_s2_convex_hull_agg(List geog, bool naRm); RcppExport SEXP _s2_cpp_s2_convex_hull_agg(SEXP geogSEXP, SEXP naRmSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type geog(geogSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); rcpp_result_gen = Rcpp::wrap(cpp_s2_convex_hull_agg(geog, naRm)); return rcpp_result_gen; END_RCPP } RcppExport SEXP c_s2_geography_writer_new(SEXP, SEXP, SEXP, SEXP); RcppExport SEXP c_s2_handle_geography(SEXP, SEXP); RcppExport SEXP c_s2_handle_geography_tessellated(SEXP, SEXP); RcppExport SEXP c_s2_projection_mercator(SEXP); RcppExport SEXP c_s2_projection_orthographic(SEXP); RcppExport SEXP c_s2_projection_plate_carree(SEXP); RcppExport SEXP c_s2_trans_s2_lnglat_new(void); RcppExport SEXP c_s2_trans_s2_point_new(void); static const R_CallMethodDef CallEntries[] = { {"_s2_cpp_s2_init", (DL_FUNC) &_s2_cpp_s2_init, 0}, {"_s2_cpp_s2_is_collection", (DL_FUNC) &_s2_cpp_s2_is_collection, 1}, {"_s2_cpp_s2_is_valid", (DL_FUNC) &_s2_cpp_s2_is_valid, 1}, {"_s2_cpp_s2_is_valid_reason", (DL_FUNC) &_s2_cpp_s2_is_valid_reason, 1}, {"_s2_cpp_s2_dimension", (DL_FUNC) &_s2_cpp_s2_dimension, 1}, {"_s2_cpp_s2_num_points", (DL_FUNC) &_s2_cpp_s2_num_points, 1}, {"_s2_cpp_s2_is_empty", (DL_FUNC) &_s2_cpp_s2_is_empty, 1}, {"_s2_cpp_s2_area", (DL_FUNC) &_s2_cpp_s2_area, 1}, {"_s2_cpp_s2_length", (DL_FUNC) &_s2_cpp_s2_length, 1}, {"_s2_cpp_s2_perimeter", (DL_FUNC) &_s2_cpp_s2_perimeter, 1}, {"_s2_cpp_s2_x", (DL_FUNC) &_s2_cpp_s2_x, 1}, {"_s2_cpp_s2_y", (DL_FUNC) &_s2_cpp_s2_y, 1}, {"_s2_cpp_s2_project_normalized", (DL_FUNC) &_s2_cpp_s2_project_normalized, 2}, {"_s2_cpp_s2_distance", (DL_FUNC) &_s2_cpp_s2_distance, 2}, {"_s2_cpp_s2_max_distance", (DL_FUNC) &_s2_cpp_s2_max_distance, 2}, {"_s2_cpp_s2_bounds_cap", (DL_FUNC) &_s2_cpp_s2_bounds_cap, 1}, {"_s2_cpp_s2_bounds_rect", (DL_FUNC) &_s2_cpp_s2_bounds_rect, 1}, {"_s2_cpp_s2_cell_union_normalize", (DL_FUNC) &_s2_cpp_s2_cell_union_normalize, 1}, {"_s2_cpp_s2_cell_union_is_na", (DL_FUNC) &_s2_cpp_s2_cell_union_is_na, 1}, {"_s2_cpp_s2_cell_union_contains", (DL_FUNC) &_s2_cpp_s2_cell_union_contains, 2}, {"_s2_cpp_s2_cell_union_contains_cell", (DL_FUNC) &_s2_cpp_s2_cell_union_contains_cell, 2}, {"_s2_cpp_s2_cell_union_intersects", (DL_FUNC) &_s2_cpp_s2_cell_union_intersects, 2}, {"_s2_cpp_s2_cell_union_intersection", (DL_FUNC) &_s2_cpp_s2_cell_union_intersection, 2}, {"_s2_cpp_s2_cell_union_union", (DL_FUNC) &_s2_cpp_s2_cell_union_union, 2}, {"_s2_cpp_s2_cell_union_difference", (DL_FUNC) &_s2_cpp_s2_cell_union_difference, 2}, {"_s2_cpp_s2_geography_from_cell_union", (DL_FUNC) &_s2_cpp_s2_geography_from_cell_union, 1}, {"_s2_cpp_s2_covering_cell_ids", (DL_FUNC) &_s2_cpp_s2_covering_cell_ids, 6}, {"_s2_cpp_s2_covering_cell_ids_agg", (DL_FUNC) &_s2_cpp_s2_covering_cell_ids_agg, 7}, {"_s2_cpp_s2_cell_sentinel", (DL_FUNC) &_s2_cpp_s2_cell_sentinel, 0}, {"_s2_cpp_s2_cell_from_string", (DL_FUNC) &_s2_cpp_s2_cell_from_string, 1}, {"_s2_cpp_s2_cell_from_lnglat", (DL_FUNC) &_s2_cpp_s2_cell_from_lnglat, 1}, {"_s2_cpp_s2_cell_to_lnglat", (DL_FUNC) &_s2_cpp_s2_cell_to_lnglat, 1}, {"_s2_cpp_s2_cell_to_cell_union", (DL_FUNC) &_s2_cpp_s2_cell_to_cell_union, 1}, {"_s2_cpp_s2_cell_is_na", (DL_FUNC) &_s2_cpp_s2_cell_is_na, 1}, {"_s2_cpp_s2_cell_sort", (DL_FUNC) &_s2_cpp_s2_cell_sort, 2}, {"_s2_cpp_s2_cell_range", (DL_FUNC) &_s2_cpp_s2_cell_range, 2}, {"_s2_cpp_s2_cell_unique", (DL_FUNC) &_s2_cpp_s2_cell_unique, 1}, {"_s2_cpp_s2_cell_to_string", (DL_FUNC) &_s2_cpp_s2_cell_to_string, 1}, {"_s2_cpp_s2_cell_debug_string", (DL_FUNC) &_s2_cpp_s2_cell_debug_string, 1}, {"_s2_cpp_s2_cell_is_valid", (DL_FUNC) &_s2_cpp_s2_cell_is_valid, 1}, {"_s2_cpp_s2_cell_center", (DL_FUNC) &_s2_cpp_s2_cell_center, 1}, {"_s2_cpp_s2_cell_polygon", (DL_FUNC) &_s2_cpp_s2_cell_polygon, 1}, {"_s2_cpp_s2_cell_vertex", (DL_FUNC) &_s2_cpp_s2_cell_vertex, 2}, {"_s2_cpp_s2_cell_level", (DL_FUNC) &_s2_cpp_s2_cell_level, 1}, {"_s2_cpp_s2_cell_area", (DL_FUNC) &_s2_cpp_s2_cell_area, 1}, {"_s2_cpp_s2_cell_area_approx", (DL_FUNC) &_s2_cpp_s2_cell_area_approx, 1}, {"_s2_cpp_s2_cell_parent", (DL_FUNC) &_s2_cpp_s2_cell_parent, 2}, {"_s2_cpp_s2_cell_child", (DL_FUNC) &_s2_cpp_s2_cell_child, 2}, {"_s2_cpp_s2_cell_edge_neighbour", (DL_FUNC) &_s2_cpp_s2_cell_edge_neighbour, 2}, {"_s2_cpp_s2_cell_cummax", (DL_FUNC) &_s2_cpp_s2_cell_cummax, 1}, {"_s2_cpp_s2_cell_cummin", (DL_FUNC) &_s2_cpp_s2_cell_cummin, 1}, {"_s2_cpp_s2_cell_eq", (DL_FUNC) &_s2_cpp_s2_cell_eq, 2}, {"_s2_cpp_s2_cell_neq", (DL_FUNC) &_s2_cpp_s2_cell_neq, 2}, {"_s2_cpp_s2_cell_lt", (DL_FUNC) &_s2_cpp_s2_cell_lt, 2}, {"_s2_cpp_s2_cell_lte", (DL_FUNC) &_s2_cpp_s2_cell_lte, 2}, {"_s2_cpp_s2_cell_gte", (DL_FUNC) &_s2_cpp_s2_cell_gte, 2}, {"_s2_cpp_s2_cell_gt", (DL_FUNC) &_s2_cpp_s2_cell_gt, 2}, {"_s2_cpp_s2_cell_contains", (DL_FUNC) &_s2_cpp_s2_cell_contains, 2}, {"_s2_cpp_s2_cell_may_intersect", (DL_FUNC) &_s2_cpp_s2_cell_may_intersect, 2}, {"_s2_cpp_s2_cell_distance", (DL_FUNC) &_s2_cpp_s2_cell_distance, 2}, {"_s2_cpp_s2_cell_max_distance", (DL_FUNC) &_s2_cpp_s2_cell_max_distance, 2}, {"_s2_cpp_s2_cell_common_ancestor_level", (DL_FUNC) &_s2_cpp_s2_cell_common_ancestor_level, 2}, {"_s2_cpp_s2_cell_common_ancestor_level_agg", (DL_FUNC) &_s2_cpp_s2_cell_common_ancestor_level_agg, 1}, {"_s2_s2_geography_full", (DL_FUNC) &_s2_s2_geography_full, 1}, {"_s2_cpp_s2_geography_is_na", (DL_FUNC) &_s2_cpp_s2_geography_is_na, 1}, {"_s2_s2_lnglat_from_s2_point", (DL_FUNC) &_s2_s2_lnglat_from_s2_point, 1}, {"_s2_s2_point_from_s2_lnglat", (DL_FUNC) &_s2_s2_point_from_s2_lnglat, 1}, {"_s2_cpp_s2_closest_feature", (DL_FUNC) &_s2_cpp_s2_closest_feature, 2}, {"_s2_cpp_s2_farthest_feature", (DL_FUNC) &_s2_cpp_s2_farthest_feature, 2}, {"_s2_cpp_s2_closest_edges", (DL_FUNC) &_s2_cpp_s2_closest_edges, 5}, {"_s2_cpp_s2_may_intersect_matrix", (DL_FUNC) &_s2_cpp_s2_may_intersect_matrix, 5}, {"_s2_cpp_s2_contains_matrix", (DL_FUNC) &_s2_cpp_s2_contains_matrix, 3}, {"_s2_cpp_s2_within_matrix", (DL_FUNC) &_s2_cpp_s2_within_matrix, 3}, {"_s2_cpp_s2_intersects_matrix", (DL_FUNC) &_s2_cpp_s2_intersects_matrix, 3}, {"_s2_cpp_s2_equals_matrix", (DL_FUNC) &_s2_cpp_s2_equals_matrix, 3}, {"_s2_cpp_s2_touches_matrix", (DL_FUNC) &_s2_cpp_s2_touches_matrix, 3}, {"_s2_cpp_s2_dwithin_matrix", (DL_FUNC) &_s2_cpp_s2_dwithin_matrix, 3}, {"_s2_cpp_s2_distance_matrix", (DL_FUNC) &_s2_cpp_s2_distance_matrix, 2}, {"_s2_cpp_s2_max_distance_matrix", (DL_FUNC) &_s2_cpp_s2_max_distance_matrix, 2}, {"_s2_cpp_s2_contains_matrix_brute_force", (DL_FUNC) &_s2_cpp_s2_contains_matrix_brute_force, 3}, {"_s2_cpp_s2_within_matrix_brute_force", (DL_FUNC) &_s2_cpp_s2_within_matrix_brute_force, 3}, {"_s2_cpp_s2_intersects_matrix_brute_force", (DL_FUNC) &_s2_cpp_s2_intersects_matrix_brute_force, 3}, {"_s2_cpp_s2_disjoint_matrix_brute_force", (DL_FUNC) &_s2_cpp_s2_disjoint_matrix_brute_force, 3}, {"_s2_cpp_s2_equals_matrix_brute_force", (DL_FUNC) &_s2_cpp_s2_equals_matrix_brute_force, 3}, {"_s2_cpp_s2_dwithin_matrix_brute_force", (DL_FUNC) &_s2_cpp_s2_dwithin_matrix_brute_force, 3}, {"_s2_cpp_s2_intersects", (DL_FUNC) &_s2_cpp_s2_intersects, 3}, {"_s2_cpp_s2_equals", (DL_FUNC) &_s2_cpp_s2_equals, 3}, {"_s2_cpp_s2_contains", (DL_FUNC) &_s2_cpp_s2_contains, 3}, {"_s2_cpp_s2_touches", (DL_FUNC) &_s2_cpp_s2_touches, 3}, {"_s2_cpp_s2_dwithin", (DL_FUNC) &_s2_cpp_s2_dwithin, 3}, {"_s2_cpp_s2_prepared_dwithin", (DL_FUNC) &_s2_cpp_s2_prepared_dwithin, 3}, {"_s2_cpp_s2_intersects_box", (DL_FUNC) &_s2_cpp_s2_intersects_box, 7}, {"_s2_cpp_s2_intersection", (DL_FUNC) &_s2_cpp_s2_intersection, 3}, {"_s2_cpp_s2_union", (DL_FUNC) &_s2_cpp_s2_union, 3}, {"_s2_cpp_s2_difference", (DL_FUNC) &_s2_cpp_s2_difference, 3}, {"_s2_cpp_s2_sym_difference", (DL_FUNC) &_s2_cpp_s2_sym_difference, 3}, {"_s2_cpp_s2_coverage_union_agg", (DL_FUNC) &_s2_cpp_s2_coverage_union_agg, 3}, {"_s2_cpp_s2_union_agg", (DL_FUNC) &_s2_cpp_s2_union_agg, 3}, {"_s2_cpp_s2_centroid_agg", (DL_FUNC) &_s2_cpp_s2_centroid_agg, 2}, {"_s2_cpp_s2_rebuild_agg", (DL_FUNC) &_s2_cpp_s2_rebuild_agg, 3}, {"_s2_cpp_s2_closest_point", (DL_FUNC) &_s2_cpp_s2_closest_point, 2}, {"_s2_cpp_s2_minimum_clearance_line_between", (DL_FUNC) &_s2_cpp_s2_minimum_clearance_line_between, 2}, {"_s2_cpp_s2_centroid", (DL_FUNC) &_s2_cpp_s2_centroid, 1}, {"_s2_cpp_s2_point_on_surface", (DL_FUNC) &_s2_cpp_s2_point_on_surface, 1}, {"_s2_cpp_s2_boundary", (DL_FUNC) &_s2_cpp_s2_boundary, 1}, {"_s2_cpp_s2_rebuild", (DL_FUNC) &_s2_cpp_s2_rebuild, 2}, {"_s2_cpp_s2_unary_union", (DL_FUNC) &_s2_cpp_s2_unary_union, 2}, {"_s2_cpp_s2_interpolate_normalized", (DL_FUNC) &_s2_cpp_s2_interpolate_normalized, 2}, {"_s2_cpp_s2_buffer_cells", (DL_FUNC) &_s2_cpp_s2_buffer_cells, 4}, {"_s2_cpp_s2_convex_hull", (DL_FUNC) &_s2_cpp_s2_convex_hull, 1}, {"_s2_cpp_s2_convex_hull_agg", (DL_FUNC) &_s2_cpp_s2_convex_hull_agg, 2}, {"c_s2_geography_writer_new", (DL_FUNC) &c_s2_geography_writer_new, 4}, {"c_s2_handle_geography", (DL_FUNC) &c_s2_handle_geography, 2}, {"c_s2_handle_geography_tessellated", (DL_FUNC) &c_s2_handle_geography_tessellated, 2}, {"c_s2_projection_mercator", (DL_FUNC) &c_s2_projection_mercator, 1}, {"c_s2_projection_orthographic", (DL_FUNC) &c_s2_projection_orthographic, 1}, {"c_s2_projection_plate_carree", (DL_FUNC) &c_s2_projection_plate_carree, 1}, {"c_s2_trans_s2_lnglat_new", (DL_FUNC) &c_s2_trans_s2_lnglat_new, 0}, {"c_s2_trans_s2_point_new", (DL_FUNC) &c_s2_trans_s2_point_new, 0}, {NULL, NULL, 0} }; RcppExport void R_init_s2(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); } s2/src/s2-matrix.cpp0000644000176200001440000005055314530411473013731 0ustar liggesusers #include #include #include #include "s2/s2boolean_operation.h" #include "s2/s2closest_edge_query.h" #include "s2/s2furthest_edge_query.h" #include "s2/s2shape_index_region.h" #include "s2/s2shape_index_buffered_region.h" #include "geography-operator.h" #include "s2-options.h" #include using namespace Rcpp; template class IndexedBinaryGeographyOperator: public UnaryGeographyOperator { public: std::unique_ptr geog2_index; std::unique_ptr iterator; // max_edges_per_cell should be between 10 and 50, with lower numbers // leading to more memory usage (but potentially faster query times). Benchmarking // with binary prediates seems to indicate that values on the high end // of the spectrum do a reasonable job of efficient preselection, and that // decreasing this value does little to increase performance. IndexedBinaryGeographyOperator(int maxEdgesPerCell = 50) { MutableS2ShapeIndex::Options index_options; index_options.set_max_edges_per_cell(maxEdgesPerCell); geog2_index = absl::make_unique(index_options); } virtual void buildIndex(List geog2) { for (R_xlen_t j = 0; j < geog2.size(); j++) { checkUserInterrupt(); SEXP item2 = geog2[j]; // build index and store index IDs so that shapeIds can be // mapped back to the geog index if (item2 == R_NilValue) { Rcpp::stop("Missing `y` not allowed in binary indexed operators()"); } else { Rcpp::XPtr feature2(item2); geog2_index->Add(feature2->Geog(), j); } } iterator = absl::make_unique(geog2_index.get()); } }; // -------- closest/farthest feature ---------- // [[Rcpp::export]] IntegerVector cpp_s2_closest_feature(List geog1, List geog2) { class Op: public IndexedBinaryGeographyOperator { public: int processFeature(Rcpp::XPtr feature, R_xlen_t i) { S2ClosestEdgeQuery query(&geog2_index->ShapeIndex()); S2ClosestEdgeQuery::ShapeIndexTarget target(&feature->Index().ShapeIndex()); const auto& result = query.FindClosestEdge(&target); if (result.is_empty()) { return NA_INTEGER; } else { // convert to R index (+1) return geog2_index->value(result.shape_id()) + 1; } } }; Op op; op.buildIndex(geog2); return op.processVector(geog1); } // [[Rcpp::export]] IntegerVector cpp_s2_farthest_feature(List geog1, List geog2) { class Op: public IndexedBinaryGeographyOperator { public: int processFeature(Rcpp::XPtr feature, R_xlen_t i) { S2FurthestEdgeQuery query(&geog2_index->ShapeIndex()); S2FurthestEdgeQuery::ShapeIndexTarget target(&feature->Index().ShapeIndex()); const auto& result = query.FindFurthestEdge(&target); if (result.is_empty()) { return NA_INTEGER; } else { // convert to R index (+1) return geog2_index->value(result.shape_id()) + 1; } } }; Op op; op.buildIndex(geog2); return op.processVector(geog1); } // [[Rcpp::export]] List cpp_s2_closest_edges(List geog1, List geog2, int n, double min_distance, double max_distance) { class Op: public IndexedBinaryGeographyOperator { public: IntegerVector processFeature(Rcpp::XPtr feature, R_xlen_t i) { S2ClosestEdgeQuery query(&geog2_index->ShapeIndex()); query.mutable_options()->set_max_results(n); query.mutable_options()->set_max_distance(S1ChordAngle::Radians(max_distance)); S2ClosestEdgeQuery::ShapeIndexTarget target(&feature->Index().ShapeIndex()); const auto& result = query.FindClosestEdges(&target); // this code searches edges, which may come from the same feature std::unordered_set features; for (S2ClosestEdgeQuery::Result res : result) { if (res.distance().radians() > this->min_distance) { features.insert(geog2_index->value(res.shape_id()) + 1); } } return IntegerVector(features.begin(), features.end()); } int n; double min_distance; double max_distance; }; Op op; op.n = n; op.min_distance = min_distance; op.max_distance = max_distance; op.buildIndex(geog2); return op.processVector(geog1); } // ----------- indexed binary predicate operators ----------- class IndexedMatrixPredicateOperator: public IndexedBinaryGeographyOperator { public: // a max_cells value of 8 was suggested in the S2RegionCoverer docs as a // reasonable approximation of a geometry, although benchmarking seems to indicate that // increasing this number above 4 actually decreasses performance (using a value // of 1 dramatically decreases performance) IndexedMatrixPredicateOperator(List s2options, int maxFeatureCells = 4, int maxEdgesPerCell = 50): IndexedBinaryGeographyOperator(maxEdgesPerCell), maxFeatureCells(maxFeatureCells) { GeographyOperationOptions options(s2options); this->options = options.booleanOperationOptions(); this->coverer.mutable_options()->set_max_cells(maxFeatureCells); } void buildIndex(List geog2) { this->geog2 = geog2; IndexedBinaryGeographyOperator::buildIndex(geog2); } IntegerVector processFeature(Rcpp::XPtr feature, R_xlen_t i) { coverer.GetCovering(*feature->Geog().Region(), &cell_ids); indices_unsorted.clear(); iterator->Query(cell_ids, &indices_unsorted); // loop through features from geog2 that might intersect feature // and build a list of indices that actually intersect (based on // this->actuallyIntersects(), which might perform alternative // comparisons) indices.clear(); for (int j: indices_unsorted) { SEXP item = this->geog2[j]; XPtr feature2(item); if (this->actuallyIntersects(feature->Index(), feature2->Index(), i, j)) { // convert to R index here + 1 indices.push_back(j + 1); } } // return sorted integer vector std::sort(indices.begin(), indices.end()); return Rcpp::IntegerVector(indices.begin(), indices.end()); }; virtual bool actuallyIntersects(const s2geography::ShapeIndexGeography& index1, const s2geography::ShapeIndexGeography& index2, R_xlen_t i, R_xlen_t j) = 0; protected: List geog2; S2BooleanOperation::Options options; int maxFeatureCells; S2RegionCoverer coverer; std::vector cell_ids; std::unordered_set indices_unsorted; std::vector indices; }; // [[Rcpp::export]] List cpp_s2_may_intersect_matrix(List geog1, List geog2, int maxEdgesPerCell, int maxFeatureCells, List s2options) { class Op: public IndexedMatrixPredicateOperator { public: Op(List s2options, int maxFeatureCells, int maxEdgesPerCell): IndexedMatrixPredicateOperator(s2options, maxFeatureCells, maxEdgesPerCell) {} bool actuallyIntersects(const s2geography::ShapeIndexGeography& index1, const s2geography::ShapeIndexGeography& index2, R_xlen_t i, R_xlen_t j) { return true; }; }; Op op(s2options, maxFeatureCells, maxEdgesPerCell); op.buildIndex(geog2); return op.processVector(geog1); } // [[Rcpp::export]] List cpp_s2_contains_matrix(List geog1, List geog2, List s2options) { class Op: public IndexedMatrixPredicateOperator { public: Op(List s2options): IndexedMatrixPredicateOperator(s2options) {} bool actuallyIntersects(const s2geography::ShapeIndexGeography& index1, const s2geography::ShapeIndexGeography& index2, R_xlen_t i, R_xlen_t j) { return s2geography::s2_contains(index1, index2, this->options); }; }; Op op(s2options); op.buildIndex(geog2); return op.processVector(geog1); } // [[Rcpp::export]] List cpp_s2_within_matrix(List geog1, List geog2, List s2options) { class Op: public IndexedMatrixPredicateOperator { public: Op(List s2options): IndexedMatrixPredicateOperator(s2options) {} bool actuallyIntersects(const s2geography::ShapeIndexGeography& index1, const s2geography::ShapeIndexGeography& index2, R_xlen_t i, R_xlen_t j) { // note reversed index2, index1 return s2geography::s2_contains(index2, index1, this->options); }; }; Op op(s2options); op.buildIndex(geog2); return op.processVector(geog1); } // [[Rcpp::export]] List cpp_s2_intersects_matrix(List geog1, List geog2, List s2options) { class Op: public IndexedMatrixPredicateOperator { public: Op(List s2options): IndexedMatrixPredicateOperator(s2options) {} bool actuallyIntersects(const s2geography::ShapeIndexGeography& index1, const s2geography::ShapeIndexGeography& index2, R_xlen_t i, R_xlen_t j) { return s2geography::s2_intersects(index1, index2, this->options); }; }; Op op(s2options); op.buildIndex(geog2); return op.processVector(geog1); } // [[Rcpp::export]] List cpp_s2_equals_matrix(List geog1, List geog2, List s2options) { class Op: public IndexedMatrixPredicateOperator { public: Op(List s2options): IndexedMatrixPredicateOperator(s2options) {} bool actuallyIntersects(const s2geography::ShapeIndexGeography& index1, const s2geography::ShapeIndexGeography& index2, R_xlen_t i, R_xlen_t j) { return s2geography::s2_equals(index1, index2, this->options); }; }; Op op(s2options); op.buildIndex(geog2); return op.processVector(geog1); } // [[Rcpp::export]] List cpp_s2_touches_matrix(List geog1, List geog2, List s2options) { class Op: public IndexedMatrixPredicateOperator { public: Op(List s2options): IndexedMatrixPredicateOperator(s2options) { this->closedOptions = this->options; this->closedOptions.set_polygon_model(S2BooleanOperation::PolygonModel::CLOSED); this->closedOptions.set_polyline_model(S2BooleanOperation::PolylineModel::CLOSED); this->openOptions = this->options; this->openOptions.set_polygon_model(S2BooleanOperation::PolygonModel::OPEN); this->openOptions.set_polyline_model(S2BooleanOperation::PolylineModel::OPEN); } bool actuallyIntersects(const s2geography::ShapeIndexGeography& index1, const s2geography::ShapeIndexGeography& index2, R_xlen_t i, R_xlen_t j) { return s2geography::s2_intersects(index1, index2, this->closedOptions) && !s2geography::s2_intersects(index1, index2, this->openOptions); }; private: S2BooleanOperation::Options closedOptions; S2BooleanOperation::Options openOptions; }; Op op(s2options); op.buildIndex(geog2); return op.processVector(geog1); } // ----------- brute force binary predicate operators ------------------ class BruteForceMatrixPredicateOperator { public: std::vector geog2Indices; S2BooleanOperation::Options options; BruteForceMatrixPredicateOperator() {} BruteForceMatrixPredicateOperator(Rcpp::List s2options) { GeographyOperationOptions options(s2options); this->options = options.booleanOperationOptions(); } List processVector(Rcpp::List geog1, Rcpp::List geog2) { List output(geog1.size()); // using instead of IntegerVector because // std::vector is much faster with repeated calls to .push_back() std::vector trueIndices; for (R_xlen_t i = 0; i < geog1.size(); i++) { trueIndices.clear(); SEXP item1 = geog1[i]; if (item1 == R_NilValue) { output[i] = R_NilValue; } else { Rcpp::XPtr feature1(item1); for (size_t j = 0; j < geog2.size(); j++) { checkUserInterrupt(); SEXP item2 = geog2[j]; if (item2 == R_NilValue) { stop("Missing `y` not allowed in binary index operations"); } XPtr feature2(item2); bool result = this->processFeature(feature1, feature2, i, j); if (result) { // convert to R index here (+1) trueIndices.push_back(j + 1); } } IntegerVector itemOut(trueIndices.size()); for (size_t k = 0; k < trueIndices.size(); k++) { itemOut[k] = trueIndices[k]; } output[i] = itemOut; } } return output; } virtual bool processFeature(XPtr feature1, XPtr feature2, R_xlen_t i, R_xlen_t j) = 0; }; // [[Rcpp::export]] List cpp_s2_dwithin_matrix(List geog1, List geog2, double distance) { class Op: public IndexedBinaryGeographyOperator { public: List geog2; S2RegionCoverer coverer; std::vector cell_ids; std::unordered_set indices_unsorted; std::vector indices; S1ChordAngle distance; IntegerVector processFeature(Rcpp::XPtr feature1, R_xlen_t i) { S2ShapeIndexBufferedRegion buffered( &feature1->Index().ShapeIndex(), this->distance ); coverer.GetCovering(buffered, &cell_ids); indices_unsorted.clear(); iterator->Query(cell_ids, &indices_unsorted); S2ClosestEdgeQuery query(&feature1->Index().ShapeIndex()); indices.clear(); for (int j: indices_unsorted) { SEXP item = this->geog2[j]; XPtr feature2(item); S2ClosestEdgeQuery::ShapeIndexTarget target(&feature2->Index().ShapeIndex()); if (query.IsDistanceLessOrEqual(&target, this->distance)) { indices.push_back(j + 1); } } // return sorted integer vector std::sort(indices.begin(), indices.end()); return Rcpp::IntegerVector(indices.begin(), indices.end()); } }; Op op; op.geog2 = geog2; op.distance = S1ChordAngle::Radians(distance); op.buildIndex(geog2); return op.processVector(geog1); } // ----------- distance matrix operators ------------------- template class MatrixGeographyOperator { public: MatrixType processVector(Rcpp::List geog1, Rcpp::List geog2) { MatrixType output(geog1.size(), geog2.size()); SEXP item1; SEXP item2; for (R_xlen_t i = 0; i < geog1.size(); i++) { item1 = geog1[i]; if (item1 == R_NilValue) { for (R_xlen_t j = 0; j < geog2.size(); j++) { output(i, j) = MatrixType::get_na(); } } else { Rcpp::XPtr feature1(item1); for (R_xlen_t j = 0; j < geog2.size(); j++) { checkUserInterrupt(); item2 = geog2[j]; if (item2 == R_NilValue) { output(i, j) = MatrixType::get_na(); } else { Rcpp::XPtr feature2(item2); output(i, j) = this->processFeature(feature1, feature2, i, j); } } } } return output; } virtual ScalarType processFeature(Rcpp::XPtr feature1, Rcpp::XPtr feature2, R_xlen_t i, R_xlen_t j) = 0; }; // [[Rcpp::export]] NumericMatrix cpp_s2_distance_matrix(List geog1, List geog2) { class Op: public MatrixGeographyOperator { double processFeature(XPtr feature1, XPtr feature2, R_xlen_t i, R_xlen_t j) { S2ClosestEdgeQuery query(&feature1->Index().ShapeIndex()); S2ClosestEdgeQuery::ShapeIndexTarget target(&feature2->Index().ShapeIndex()); const auto& result = query.FindClosestEdge(&target); S1ChordAngle angle = result.distance(); double distance = angle.ToAngle().radians(); if (distance == R_PosInf) { return NA_REAL; } else { return distance; } } }; Op op; return op.processVector(geog1, geog2); } // [[Rcpp::export]] NumericMatrix cpp_s2_max_distance_matrix(List geog1, List geog2) { class Op: public MatrixGeographyOperator { double processFeature(XPtr feature1, XPtr feature2, R_xlen_t i, R_xlen_t j) { S2FurthestEdgeQuery query(&feature1->Index().ShapeIndex()); S2FurthestEdgeQuery::ShapeIndexTarget target(&feature2->Index().ShapeIndex()); const auto& result = query.FindFurthestEdge(&target); S1ChordAngle angle = result.distance(); double distance = angle.ToAngle().radians(); // returns -1 if one of the indexes is empty // NA is more consistent with the BigQuery // function, and makes way more sense if (distance < 0) { return NA_REAL; } else { return distance; } } }; Op op; return op.processVector(geog1, geog2); } // ----------- brute force binary predicate operators (for testing) ------------------ // [[Rcpp::export]] List cpp_s2_contains_matrix_brute_force(List geog1, List geog2, List s2options) { class Op: public BruteForceMatrixPredicateOperator { public: Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {} bool processFeature(XPtr feature1, XPtr feature2, R_xlen_t i, R_xlen_t j) { return s2geography::s2_contains(feature1->Index(), feature2->Index(), options); }; }; Op op(s2options); return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_within_matrix_brute_force(List geog1, List geog2, List s2options) { class Op: public BruteForceMatrixPredicateOperator { public: Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {} bool processFeature(XPtr feature1, XPtr feature2, R_xlen_t i, R_xlen_t j) { // note reversed index2, index1 return s2geography::s2_contains(feature2->Index(), feature1->Index(), options); }; }; Op op(s2options); return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_intersects_matrix_brute_force(List geog1, List geog2, List s2options) { class Op: public BruteForceMatrixPredicateOperator { public: Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {} bool processFeature(XPtr feature1, XPtr feature2, R_xlen_t i, R_xlen_t j) { return s2geography::s2_intersects(feature1->Index(), feature2->Index(), options); } }; Op op(s2options); return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_disjoint_matrix_brute_force(List geog1, List geog2, List s2options) { class Op: public BruteForceMatrixPredicateOperator { public: Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {} bool processFeature(XPtr feature1, XPtr feature2, R_xlen_t i, R_xlen_t j) { return !s2geography::s2_intersects(feature1->Index(), feature2->Index(), options); } }; Op op(s2options); return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_equals_matrix_brute_force(List geog1, List geog2, List s2options) { class Op: public BruteForceMatrixPredicateOperator { public: Op(List s2options): BruteForceMatrixPredicateOperator(s2options) {} bool processFeature(XPtr feature1, XPtr feature2, R_xlen_t i, R_xlen_t j) { return s2geography::s2_equals(feature1->Index(), feature2->Index(), options); } }; Op op(s2options); return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_dwithin_matrix_brute_force(List geog1, List geog2, double distance) { class Op: public BruteForceMatrixPredicateOperator { public: double distance; Op(double distance): distance(distance) {} bool processFeature(XPtr feature1, XPtr feature2, R_xlen_t i, R_xlen_t j) { S2ClosestEdgeQuery query(&feature2->Index().ShapeIndex()); S2ClosestEdgeQuery::ShapeIndexTarget target(&feature1->Index().ShapeIndex()); return query.IsDistanceLessOrEqual(&target, S1ChordAngle::Radians(this->distance)); }; }; Op op(distance); return op.processVector(geog1, geog2); } s2/src/s2-cell.cpp0000644000176200001440000005454414530411473013350 0ustar liggesusers #include #include #include #include #include #include "s2/s2cell_id.h" #include "s2/s2cell.h" #include "s2/s2latlng.h" #include "geography.h" #include using namespace Rcpp; static inline double reinterpret_double(uint64_t id) { double doppelganger; memcpy(&doppelganger, &id, sizeof(double)); return doppelganger; } class S2CellOperatorException: public std::runtime_error { public: S2CellOperatorException(std::string msg): std::runtime_error(msg.c_str()) {} }; template class UnaryS2CellOperator { public: VectorType processVector(Rcpp::NumericVector cellIdVector) { VectorType output(cellIdVector.size()); for (R_xlen_t i = 0; i < cellIdVector.size(); i++) { if ((i % 1000) == 0) { Rcpp::checkUserInterrupt(); } S2CellId cell(*((uint64_t*) &(cellIdVector[i]))); output[i] = this->processCell(cell, i); } return output; } virtual ScalarType processCell(S2CellId cellId, R_xlen_t i) = 0; }; // For speed, take care of recycling here (only works if there is no // additional parameter). Most binary ops don't have a parameter and some // (like Ops, and Math) make recycling harder to incorporate at the R level template class BinaryS2CellOperator { public: VectorType processVector(Rcpp::NumericVector cellIdVector1, Rcpp::NumericVector cellIdVector2) { if (cellIdVector1.size() == cellIdVector2.size()) { VectorType output(cellIdVector1.size()); for (R_xlen_t i = 0; i < cellIdVector1.size(); i++) { if ((i % 1000) == 0) { Rcpp::checkUserInterrupt(); } S2CellId cell1(*((uint64_t*) &(cellIdVector1[i]))); S2CellId cell2(*((uint64_t*) &(cellIdVector2[i]))); output[i] = this->processCell(cell1, cell2, i); } return output; } else if (cellIdVector1.size() == 1) { VectorType output(cellIdVector2.size()); for (R_xlen_t i = 0; i < cellIdVector2.size(); i++) { if ((i % 1000) == 0) { Rcpp::checkUserInterrupt(); } S2CellId cell1(*((uint64_t*) &(cellIdVector1[0]))); S2CellId cell2(*((uint64_t*) &(cellIdVector2[i]))); output[i] = this->processCell(cell1, cell2, i); } return output; } else if (cellIdVector2.size() == 1) { VectorType output(cellIdVector1.size()); for (R_xlen_t i = 0; i < cellIdVector1.size(); i++) { if ((i % 1000) == 0) { Rcpp::checkUserInterrupt(); } S2CellId cell1(*((uint64_t*) &(cellIdVector1[i]))); S2CellId cell2(*((uint64_t*) &(cellIdVector2[0]))); output[i] = this->processCell(cell1, cell2, i); } return output; } else { std::stringstream err; err << "Can't recycle vectors of size " << cellIdVector1.size() << " and " << cellIdVector2.size() << " to a common length."; stop(err.str()); } } virtual ScalarType processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) = 0; }; // [[Rcpp::export]] NumericVector cpp_s2_cell_sentinel() { NumericVector result = NumericVector::create(reinterpret_double(S2CellId::Sentinel().id())); result.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return result; } // [[Rcpp::export]] NumericVector cpp_s2_cell_from_string(CharacterVector cellString) { R_xlen_t size = cellString.size(); NumericVector cellId(size); double* ptrDouble = REAL(cellId); uint64_t* ptrCellId = (uint64_t*) ptrDouble; for (R_xlen_t i = 0; i < size; i++) { if ((i % 1000) == 0) { Rcpp::checkUserInterrupt(); } if (CharacterVector::is_na(cellString[i])) { ptrDouble[i] = NA_REAL; } else { ptrCellId[i] = S2CellId::FromToken(as(cellString[i])).id(); } } cellId.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return cellId; } // [[Rcpp::export]] NumericVector cpp_s2_cell_from_lnglat(List lnglat) { NumericVector lng = lnglat[0]; NumericVector lat = lnglat[1]; R_xlen_t size = lng.size(); NumericVector cellId(size); double* ptrDouble = REAL(cellId); uint64_t* ptrCellId = (uint64_t*) ptrDouble; for (R_xlen_t i = 0; i < size; i++) { if ((i % 1000) == 0) { Rcpp::checkUserInterrupt(); } if (R_IsNA(lng[i]) || R_IsNA(lat[i])) { ptrDouble[i] = NA_REAL; } else { S2LatLng ll = S2LatLng::FromDegrees(lat[i], lng[i]).Normalized(); ptrCellId[i] = S2CellId(ll).id(); } } cellId.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return cellId; } // [[Rcpp::export]] List cpp_s2_cell_to_lnglat(NumericVector cellId) { R_xlen_t size = cellId.size(); double* ptrDouble = REAL(cellId); uint64_t* ptrCellId = (uint64_t*) ptrDouble; NumericVector lng(size); NumericVector lat(size); for (R_xlen_t i = 0; i < size; i++) { if ((i % 1000) == 0) { Rcpp::checkUserInterrupt(); } if (R_IsNA(ptrDouble[i])) { lng[i] = NA_REAL; lat[i] = NA_REAL; } else { S2CellId cell(ptrCellId[i]); if (!cell.is_valid()) { lng[i] = NA_REAL; lat[i] = NA_REAL; } else { S2LatLng ll = S2CellId(ptrCellId[i]).ToLatLng(); lng[i] = ll.lng().degrees(); lat[i] = ll.lat().degrees(); } } } return List::create(_["x"] = lng, _["y"] = lat); } // [[Rcpp::export]] List cpp_s2_cell_to_cell_union(NumericVector cellId) { R_xlen_t size = cellId.size(); CharacterVector cls = CharacterVector::create("s2_cell", "wk_vctr"); List out(size); for (R_xlen_t i = 0; i < size; i++) { if ((i % 1000) == 0) { Rcpp::checkUserInterrupt(); } if (R_IsNA(cellId[i])) { out[i] = R_NilValue; } else { NumericVector item = NumericVector::create(cellId[i]); item.attr("class") = cls; out[i] = item; } } out.attr("class") = CharacterVector::create("s2_cell_union", "wk_vctr"); return out; } // [[Rcpp::export]] LogicalVector cpp_s2_cell_is_na(NumericVector cellIdVector) { LogicalVector out(cellIdVector.size()); for (R_xlen_t i = 0; i < cellIdVector.size(); i++) { out[i] = R_IsNA(cellIdVector[i]); } return out; } // [[Rcpp::export]] NumericVector cpp_s2_cell_sort(NumericVector cellIdVector, bool decreasing) { NumericVector out = clone(cellIdVector); uint64_t* data = (uint64_t*) REAL(out); if (decreasing) { std::sort(data, data + out.size(), std::greater()); } else { std::sort(data, data + out.size()); } out.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return out; } // [[Rcpp::export]] NumericVector cpp_s2_cell_range(NumericVector cellIdVector, bool naRm) { uint64_t* data = (uint64_t*) REAL(cellIdVector); uint64_t zero = 0; uint64_t big = ~zero; auto dataRange = std::pair(big, zero); // without NA handling this is just // dataRange = std::minmax_element(data, data + cellIdVector.size()); for (R_xlen_t i = 0; i < cellIdVector.size(); i++) { if (R_IsNA(cellIdVector[i]) && !naRm) { dataRange.first = data[i]; dataRange.second = data[i]; break; } if (!R_IsNA(cellIdVector[i]) && (data[i] < dataRange.first)) { dataRange.first = data[i]; } if (!R_IsNA(cellIdVector[i]) && (data[i] > dataRange.second)) { dataRange.second = data[i]; } } if (dataRange.first > dataRange.second) { NumericVector out = NumericVector::create(NA_REAL, NA_REAL); out.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return out; } else { NumericVector out = NumericVector::create( reinterpret_double(dataRange.first), reinterpret_double(dataRange.second) ); out.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return out; } } // [[Rcpp::export]] NumericVector cpp_s2_cell_unique(NumericVector cellIdVector) { std::set uniqueValues; uint64_t value; for (R_xlen_t i = 0; i < cellIdVector.size(); i++) { memcpy(&value, &(cellIdVector[i]), sizeof(uint64_t)); uniqueValues.insert(value); } NumericVector out(uniqueValues.size()); R_xlen_t i = 0; for (uint64_t value : uniqueValues) { out[i++] = reinterpret_double(value); } out.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return out; } // [[Rcpp::export]] CharacterVector cpp_s2_cell_to_string(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { String processCell(S2CellId cellId, R_xlen_t i) { if (R_IsNA(reinterpret_double(cellId.id()))) { return NA_STRING; } else { return cellId.ToToken(); } } }; Op op; return op.processVector(cellIdVector); } // [[Rcpp::export]] CharacterVector cpp_s2_cell_debug_string(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { String processCell(S2CellId cellId, R_xlen_t i) { if (R_IsNA(reinterpret_double(cellId.id()))) { return NA_STRING; } else { return cellId.ToString(); } } }; Op op; return op.processVector(cellIdVector); } // [[Rcpp::export]] LogicalVector cpp_s2_cell_is_valid(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { int processCell(S2CellId cellId, R_xlen_t i) { return cellId.is_valid(); } }; Op op; return op.processVector(cellIdVector); } // [[Rcpp::export]] List cpp_s2_cell_center(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { SEXP processCell(S2CellId cellId, R_xlen_t i) { if (cellId.is_valid()) { return RGeography::MakeXPtr(RGeography::MakePoint(cellId.ToPoint())); } else { return R_NilValue; } } }; Op op; List result = op.processVector(cellIdVector); result.attr("class") = CharacterVector::create("s2_geography", "wk_vctr"); return result; } // [[Rcpp::export]] List cpp_s2_cell_polygon(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { SEXP processCell(S2CellId cellId, R_xlen_t i) { if (cellId.is_valid()) { auto poly = absl::make_unique(S2Cell(cellId)); return RGeography::MakeXPtr(RGeography::MakePolygon(std::move(poly))); } else { return R_NilValue; } } }; Op op; List result = op.processVector(cellIdVector); result.attr("class") = CharacterVector::create("s2_geography", "wk_vctr"); return result; } // [[Rcpp::export]] List cpp_s2_cell_vertex(NumericVector cellIdVector, IntegerVector k) { class Op: public UnaryS2CellOperator { SEXP processCell(S2CellId cellId, R_xlen_t i) { if (cellId.is_valid() && (this->k[i] >= 0)) { return RGeography::MakeXPtr(RGeography::MakePoint(S2Cell(cellId).GetVertex(this->k[i]))); } else { return R_NilValue; } } public: IntegerVector k; }; Op op; op.k = k; List result = op.processVector(cellIdVector); result.attr("class") = CharacterVector::create("s2_geography", "wk_vctr"); return result; } // [[Rcpp::export]] IntegerVector cpp_s2_cell_level(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { int processCell(S2CellId cellId, R_xlen_t i) { if (cellId.is_valid()) { return cellId.level(); } else { return NA_INTEGER; } } }; Op op; return op.processVector(cellIdVector); } // [[Rcpp::export]] NumericVector cpp_s2_cell_area(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { double processCell(S2CellId cellId, R_xlen_t i) { if (cellId.is_valid()) { return S2Cell(cellId).ExactArea(); } else { return NA_REAL; } } }; Op op; return op.processVector(cellIdVector); } // [[Rcpp::export]] NumericVector cpp_s2_cell_area_approx(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { double processCell(S2CellId cellId, R_xlen_t i) { if (cellId.is_valid()) { return S2Cell(cellId).ApproxArea(); } else { return NA_REAL; } } }; Op op; return op.processVector(cellIdVector); } // [[Rcpp::export]] NumericVector cpp_s2_cell_parent(NumericVector cellIdVector, IntegerVector level) { class Op: public UnaryS2CellOperator { double processCell(S2CellId cellId, R_xlen_t i) { int leveli = this->level[i]; // allow negative numbers to relate to current level if (leveli < 0) { leveli = cellId.level() + leveli; } if (cellId.is_valid() && (leveli >= 0) && (leveli <= cellId.level())) { return reinterpret_double(cellId.parent(leveli).id()); } else { return NA_REAL; } } public: IntegerVector level; }; Op op; op.level = level; NumericVector result = op.processVector(cellIdVector); result.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return result; } // [[Rcpp::export]] NumericVector cpp_s2_cell_child(NumericVector cellIdVector, IntegerVector k) { class Op: public UnaryS2CellOperator { double processCell(S2CellId cellId, R_xlen_t i) { int ki = this->k[i]; if (cellId.is_valid() && (ki >=0) && (ki <= 3)) { return reinterpret_double(cellId.child(ki).id()); } else { return NA_REAL; } } public: IntegerVector k; }; Op op; op.k = k; NumericVector result = op.processVector(cellIdVector); result.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return result; } // [[Rcpp::export]] NumericVector cpp_s2_cell_edge_neighbour(NumericVector cellIdVector, IntegerVector k) { class Op: public UnaryS2CellOperator { double processCell(S2CellId cellId, R_xlen_t i) { int ki = this->k[i]; if (cellId.is_valid() && (ki >=0) && (ki <= 3)) { S2CellId neighbours[4]; cellId.GetEdgeNeighbors(neighbours); return reinterpret_double(neighbours[ki].id()); } else { return NA_REAL; } } public: IntegerVector k; }; Op op; op.k = k; NumericVector result = op.processVector(cellIdVector); result.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return result; } // Ops for Ops, Math, Summary generics // [[Rcpp::export]] NumericVector cpp_s2_cell_cummax(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { public: Op(): current(reinterpret_double(0)), currentId(0) {} double processCell(S2CellId cellId, R_xlen_t i) { double doubleVal = reinterpret_double(cellId.id()); if (R_IsNA(this->current) || R_IsNA(doubleVal)) { this->current = NA_REAL; this->currentId = cellId.id(); return NA_REAL; } else if (cellId.id() > this->currentId) { this->currentId = cellId.id(); this->current = doubleVal; return doubleVal; } else { return this->current; } } double current; uint64_t currentId; }; Op op; NumericVector result = op.processVector(cellIdVector); result.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return result; } // [[Rcpp::export]] NumericVector cpp_s2_cell_cummin(NumericVector cellIdVector) { class Op: public UnaryS2CellOperator { public: Op(): current(reinterpret_double(UINT64_MAX)), currentId(UINT64_MAX) {} double processCell(S2CellId cellId, R_xlen_t i) { double doubleVal = reinterpret_double(cellId.id()); if (R_IsNA(this->current) || R_IsNA(doubleVal)) { this->current = NA_REAL; this->currentId = cellId.id(); return NA_REAL; } else if (cellId.id() < this->currentId) { this->currentId = cellId.id(); this->current = doubleVal; return doubleVal; } else { return this->current; } } double current; uint64_t currentId; }; Op op; NumericVector result = op.processVector(cellIdVector); result.attr("class") = CharacterVector::create("s2_cell", "wk_vctr"); return result; } // These are unique in that invalid cells (e.g., Sentinel) // should not return NA; but NA_real_ should // [[Rcpp::export]] LogicalVector cpp_s2_cell_eq(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { int processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (R_IsNA(reinterpret_double(cellId1.id())) || R_IsNA(reinterpret_double(cellId2.id()))) { return NA_LOGICAL; } else { return cellId1.id() == cellId2.id(); } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] LogicalVector cpp_s2_cell_neq(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { int processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (R_IsNA(reinterpret_double(cellId1.id())) || R_IsNA(reinterpret_double(cellId2.id()))) { return NA_LOGICAL; } else { return cellId1.id() != cellId2.id(); } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] LogicalVector cpp_s2_cell_lt(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { int processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (R_IsNA(reinterpret_double(cellId1.id())) || R_IsNA(reinterpret_double(cellId2.id()))) { return NA_LOGICAL; } else { return cellId1.id() < cellId2.id(); } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] LogicalVector cpp_s2_cell_lte(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { int processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (R_IsNA(reinterpret_double(cellId1.id())) || R_IsNA(reinterpret_double(cellId2.id()))) { return NA_LOGICAL; } else { return cellId1.id() <= cellId2.id(); } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] LogicalVector cpp_s2_cell_gte(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { int processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (R_IsNA(reinterpret_double(cellId1.id())) || R_IsNA(reinterpret_double(cellId2.id()))) { return NA_LOGICAL; } else { return cellId1.id() >= cellId2.id(); } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] LogicalVector cpp_s2_cell_gt(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { int processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (R_IsNA(reinterpret_double(cellId1.id())) || R_IsNA(reinterpret_double(cellId2.id()))) { return NA_LOGICAL; } else { return cellId1.id() > cellId2.id(); } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] LogicalVector cpp_s2_cell_contains(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { int processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (cellId1.is_valid() && cellId2.is_valid()) { return cellId1.contains(cellId2); } else { return NA_LOGICAL; } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] LogicalVector cpp_s2_cell_may_intersect(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { int processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (cellId1.is_valid() && cellId2.is_valid()) { return S2Cell(cellId1).MayIntersect(S2Cell(cellId2)); } else { return NA_LOGICAL; } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] NumericVector cpp_s2_cell_distance(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { double processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (cellId1.is_valid() && cellId2.is_valid()) { return S2Cell(cellId1).GetDistance(S2Cell(cellId2)).radians(); } else { return NA_REAL; } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] NumericVector cpp_s2_cell_max_distance(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { double processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (cellId1.is_valid() && cellId2.is_valid()) { return S2Cell(cellId1).GetMaxDistance(S2Cell(cellId2)).radians(); } else { return NA_REAL; } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] IntegerVector cpp_s2_cell_common_ancestor_level(NumericVector cellIdVector1, NumericVector cellIdVector2) { class Op: public BinaryS2CellOperator { int processCell(S2CellId cellId1, S2CellId cellId2, R_xlen_t i) { if (cellId1.is_valid() && cellId2.is_valid()) { return cellId1.GetCommonAncestorLevel(cellId2); } else { return NA_INTEGER; } } }; Op op; return op.processVector(cellIdVector1, cellIdVector2); } // [[Rcpp::export]] int cpp_s2_cell_common_ancestor_level_agg(NumericVector cellId) { R_xlen_t size = cellId.size(); if (size == 0) { return NA_INTEGER; } double* ptrDouble = REAL(cellId); uint64_t* ptrCellId = (uint64_t*) ptrDouble; S2CellId cellIdCommon(ptrCellId[0]); for (R_xlen_t i = 1; i < size; i++) { if ((i % 1000) == 0) { Rcpp::checkUserInterrupt(); // # nocov } int commonLevel = cellIdCommon.GetCommonAncestorLevel(S2CellId(ptrCellId[i])); if (commonLevel == -1) { return -1; } cellIdCommon = cellIdCommon.parent(commonLevel); } return cellIdCommon.level(); } s2/src/s2-options.h0000644000176200001440000002776614530411473013577 0ustar liggesusers #ifndef S2_OPTIONS_H #define S2_OPTIONS_H #include #include #include "s2/s2boolean_operation.h" #include "s2/s2builderutil_snap_functions.h" #include "s2/s2builderutil_s2polygon_layer.h" #include "s2/s2builderutil_s2polyline_vector_layer.h" #include "s2/s2builderutil_s2point_vector_layer.h" #include "s2geography.h" // This class wraps several concepts in the S2BooleanOperation, // and S2Layer, parameterized such that these can be specified from R class GeographyOperationOptions { public: int polygonModel; int polylineModel; Rcpp::List snap; double snapRadius; int duplicatePointEdges; int duplicatePolylineEdges; int duplicatePolygonEdges; int polylineEdgeType; int polygonEdgeType; int validatePolyline; int validatePolygon; int polylineType; int polylineSiblingPairs; int simplifyEdgeChains; int splitCrossingEdges; int idempotent; int dimensions; enum Dimension { POINT = 1, POLYLINE = 2, POLYGON = 4 }; // Wraps options for the three layer types class LayerOptions { public: s2builderutil::S2PointVectorLayer::Options pointLayerOptions; s2builderutil::S2PolylineVectorLayer::Options polylineLayerOptions; s2builderutil::S2PolygonLayer::Options polygonLayerOptions; int dimensions; }; // deaults: use S2 defaults GeographyOperationOptions(): polygonModel(-1), polylineModel(-1), snapRadius(-1) { this->snap.attr("class") = "snap_identity"; } // create from s2_options() object GeographyOperationOptions(Rcpp::List s2options): GeographyOperationOptions() { if (!Rf_inherits(s2options, "s2_options")) { Rcpp::stop("`options` must be created using s2_options()"); } // if these items are of an incorrect type (e.g., list() instead of int) // the default errors are very difficult to diagnose. try { int model = s2options["model"]; this->polylineModel = model; this->polygonModel = model; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `model`: " << e.what(); Rcpp::stop(err.str()); } try { this->snap = s2options["snap"]; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `snap`: " << e.what(); Rcpp::stop(err.str()); } try { this->snapRadius = s2options["snap_radius"]; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `snap_radius`: " << e.what(); Rcpp::stop(err.str()); } try { int duplicateEdges = s2options["duplicate_edges"]; this->duplicatePointEdges = duplicateEdges; this->duplicatePolylineEdges = duplicateEdges; this->duplicatePolygonEdges = duplicateEdges; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `duplicate_edges`: " << e.what(); Rcpp::stop(err.str()); } try { int edgeType = s2options["edge_type"]; this->polylineEdgeType = edgeType; this->polygonEdgeType = edgeType; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `edge_type`: " << e.what(); Rcpp::stop(err.str()); } try { int validate = s2options["validate"]; this->validatePolyline = validate; this->validatePolygon = validate; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `duplicate_edges`: " << e.what(); Rcpp::stop(err.str()); } try { this->polylineType = s2options["polyline_type"]; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `polyline_type`: " << e.what(); Rcpp::stop(err.str()); } try { this->polylineSiblingPairs = s2options["polyline_sibling_pairs"]; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `polyline_sibling_pairs`: " << e.what(); Rcpp::stop(err.str()); } try { this->simplifyEdgeChains = s2options["simplify_edge_chains"]; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `simplify_edge_chains`: " << e.what(); Rcpp::stop(err.str()); } try { this->splitCrossingEdges = s2options["split_crossing_edges"]; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `split_crossing_edges`: " << e.what(); Rcpp::stop(err.str()); } try { this->idempotent = s2options["idempotent"]; } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `idempotent`: " << e.what(); Rcpp::stop(err.str()); } try { this->dimensions = 0; Rcpp::IntegerVector dim = s2options["dimensions"]; for (int i = 0; i < dim.size(); i++) { switch (dim[i]) { case 1: this->dimensions |= Dimension::POINT; break; case 2: this->dimensions |= Dimension::POLYLINE; break; case 3: this->dimensions |= Dimension::POLYGON; break; } } } catch (std::exception& e) { std::stringstream err; err << "Error setting s2_options() `dimensions`: " << e.what(); Rcpp::stop(err.str()); } } // build options for passing this to the S2BooleanOperation S2BooleanOperation::Options booleanOperationOptions() { S2BooleanOperation::Options options; if (this->polygonModel >= 0) { options.set_polygon_model(getPolygonModel(this->polygonModel)); } if (this->polylineModel >= 0) { options.set_polyline_model(getPolylineModel(this->polylineModel)); } this->setSnapFunction(options); return options; } // options for new GlobalOptions API s2geography::GlobalOptions geographyOptions() { s2geography::GlobalOptions options; options.boolean_operation = booleanOperationOptions(); options.builder = builderOptions(); LayerOptions layer_options = layerOptions(); options.point_layer = layer_options.pointLayerOptions; options.polyline_layer = layer_options.polylineLayerOptions; options.polygon_layer = layer_options.polygonLayerOptions; if (!(layer_options.dimensions & Dimension::POINT)) { options.point_layer_action = s2geography::GlobalOptions::OUTPUT_ACTION_IGNORE; } if (!(layer_options.dimensions & Dimension::POLYLINE)) { options.polyline_layer_action = s2geography::GlobalOptions::OUTPUT_ACTION_IGNORE; } if (!(layer_options.dimensions & Dimension::POLYGON)) { options.polygon_layer_action = s2geography::GlobalOptions::OUTPUT_ACTION_IGNORE; } return options; } // build options for S2Builder S2Builder::Options builderOptions() { S2Builder::Options options; options.set_simplify_edge_chains(this->simplifyEdgeChains); options.set_split_crossing_edges(this->splitCrossingEdges); options.set_idempotent(this->idempotent); this->setSnapFunction(options); return options; } // build options for point, polyline, and polygon layers LayerOptions layerOptions() { LayerOptions out; // point layer out.pointLayerOptions.set_duplicate_edges(getDuplicateEdges(this->duplicatePointEdges)); // polyline layer out.polylineLayerOptions.set_duplicate_edges(getDuplicateEdges(this->duplicatePolylineEdges)); out.polylineLayerOptions.set_edge_type(getEdgeType(this->polylineEdgeType)); out.polylineLayerOptions.set_polyline_type(getPolylineType(this->polylineType)); out.polylineLayerOptions.set_sibling_pairs(getSiblingPairs(this->polylineSiblingPairs)); out.polylineLayerOptions.set_validate(this->validatePolyline); // always disable debugging where possible out.polylineLayerOptions.set_s2debug_override(S2Debug::DISABLE); // polygon layer out.polygonLayerOptions.set_edge_type(getEdgeType(this->polygonEdgeType)); out.polygonLayerOptions.set_validate(this->validatePolygon); // dimensions out.dimensions = this->dimensions; return out; } template void setSnapFunction(OptionsType& options) { // S2Builder::SnapFunction is abstract and can't be returned // hence the templated function if (Rf_inherits(this->snap, "snap_identity")) { s2builderutil::IdentitySnapFunction snapFunction; if (this->snapRadius > 0) { snapFunction.set_snap_radius(S1Angle::Radians(this->snapRadius)); } options.set_snap_function(snapFunction); } else if (Rf_inherits(this->snap, "snap_level")) { int snapLevel = this->snap["level"]; s2builderutil::S2CellIdSnapFunction snapFunction(snapLevel); if (this->snapRadius > 0) { snapFunction.set_snap_radius(S1Angle::Radians(this->snapRadius)); } options.set_snap_function(snapFunction); } else if (Rf_inherits(this->snap, "snap_precision")) { int exponent = snap["exponent"]; s2builderutil::IntLatLngSnapFunction snapFunction(exponent); if (this->snapRadius > 0) { snapFunction.set_snap_radius(S1Angle::Radians(this->snapRadius)); } options.set_snap_function(snapFunction); } else if (Rf_inherits(this->snap, "snap_distance")) { double distance = snap["distance"]; double snapLevel = s2builderutil::S2CellIdSnapFunction::LevelForMaxSnapRadius( S1Angle::Radians(distance) ); s2builderutil::S2CellIdSnapFunction snapFunction(snapLevel); if (this->snapRadius > 0) { snapFunction.set_snap_radius(S1Angle::Radians(this->snapRadius)); } options.set_snap_function(snapFunction); } else { Rcpp::stop("`snap` must be specified using s2_snap_*()"); } } static S2BooleanOperation::PolygonModel getPolygonModel(int model) { switch (model) { case 1: return S2BooleanOperation::PolygonModel::OPEN; case 2: return S2BooleanOperation::PolygonModel::SEMI_OPEN; case 3: return S2BooleanOperation::PolygonModel::CLOSED; default: std::stringstream err; err << "Invalid value for polygon model: " << model; Rcpp::stop(err.str()); } } static S2BooleanOperation::PolylineModel getPolylineModel(int model) { switch (model) { case 1: return S2BooleanOperation::PolylineModel::OPEN; case 2: return S2BooleanOperation::PolylineModel::SEMI_OPEN; case 3: return S2BooleanOperation::PolylineModel::CLOSED; default: std::stringstream err; err << "Invalid value for polyline model: " << model; Rcpp::stop(err.str()); } } static S2Builder::GraphOptions::DuplicateEdges getDuplicateEdges(int value) { switch (value) { case 0: return S2Builder::GraphOptions::DuplicateEdges::MERGE; case 1: return S2Builder::GraphOptions::DuplicateEdges::KEEP; default: std::stringstream err; err << "Invalid value for duplicate edges: " << value; Rcpp::stop(err.str()); } } static S2Builder::GraphOptions::EdgeType getEdgeType(int value) { switch (value) { case 1: return S2Builder::GraphOptions::EdgeType::DIRECTED; case 2: return S2Builder::GraphOptions::EdgeType::UNDIRECTED; default: std::stringstream err; err << "Invalid value for edge type: " << value; Rcpp::stop(err.str()); } } static S2Builder::GraphOptions::SiblingPairs getSiblingPairs(int value) { switch (value) { case 1: return S2Builder::GraphOptions::SiblingPairs::DISCARD; case 2: return S2Builder::GraphOptions::SiblingPairs::KEEP; default: std::stringstream err; err << "Invalid value for sibling pairs: " << value; Rcpp::stop(err.str()); } } static S2Builder::Graph::PolylineType getPolylineType(int value) { switch (value) { case 1: return S2Builder::Graph::PolylineType::PATH; case 2: return S2Builder::Graph::PolylineType::WALK; default: std::stringstream err; err << "Invalid value for polylie type: " << value; Rcpp::stop(err.str()); } } }; #endif s2/src/Makevars.win0000644000176200001440000001705714530411473013671 0ustar liggesusersVERSION = 1.1.1k PKG_CPPFLAGS = -DS2_USE_EXACTFLOAT -D_USE_MATH_DEFINES -DNDEBUG -DIS_LITTLE_ENDIAN -DOMIT_STRPTIME -I../windows/openssl-$(VERSION)/include -I../src PKG_LIBS = -Ls2 -ls2static -L../windows/openssl-$(VERSION)/lib${R_ARCH}${CRT} -lssl -lcrypto -lws2_32 -lgdi32 -lcrypt32 CXX_STD = CXX11 STATLIB = s2/libs2static.a ABSL_LIBS = absl/base/internal/cycleclock.o \ absl/base/internal/low_level_alloc.o \ absl/base/internal/raw_logging.o \ absl/base/internal/scoped_set_env.o \ absl/base/internal/spinlock_wait.o \ absl/base/internal/spinlock.o \ absl/base/internal/strerror.o \ absl/base/internal/sysinfo.o \ absl/base/internal/thread_identity.o \ absl/base/internal/throw_delegate.o \ absl/base/internal/unscaledcycleclock.o \ absl/base/log_severity.o \ absl/container/internal/hashtablez_sampler_force_weak_definition.o \ absl/container/internal/hashtablez_sampler.o \ absl/container/internal/raw_hash_set.o \ absl/debugging/failure_signal_handler.o \ absl/debugging/internal/address_is_readable.o \ absl/debugging/internal/demangle.o \ absl/debugging/internal/elf_mem_image.o \ absl/debugging/internal/examine_stack.o \ absl/debugging/internal/stack_consumption.o \ absl/debugging/internal/vdso_support.o \ absl/debugging/leak_check.o \ absl/debugging/stacktrace.o \ absl/debugging/symbolize.o \ absl/numeric/int128.o \ absl/profiling/internal/exponential_biased.o \ absl/profiling/internal/periodic_sampler.o \ absl/strings/ascii.o \ absl/strings/charconv.o \ absl/strings/cord_analysis.o \ absl/strings/cord_buffer.o \ absl/strings/cord.o \ absl/strings/escaping.o \ absl/strings/internal/charconv_bigint.o \ absl/strings/internal/charconv_parse.o \ absl/strings/internal/cord_internal.o \ absl/strings/internal/cord_rep_btree_navigator.o \ absl/strings/internal/cord_rep_btree_reader.o \ absl/strings/internal/cord_rep_btree.o \ absl/strings/internal/cord_rep_consume.o \ absl/strings/internal/cord_rep_crc.o \ absl/strings/internal/cord_rep_ring.o \ absl/strings/internal/cordz_functions.o \ absl/strings/internal/cordz_handle.o \ absl/strings/internal/cordz_info.o \ absl/strings/internal/cordz_sample_token.o \ absl/strings/internal/escaping.o \ absl/strings/internal/memutil.o \ absl/strings/internal/ostringstream.o \ absl/strings/internal/pow10_helper.o \ absl/strings/internal/str_format/arg.o \ absl/strings/internal/str_format/bind.o \ absl/strings/internal/str_format/extension.o \ absl/strings/internal/str_format/float_conversion.o \ absl/strings/internal/str_format/output.o \ absl/strings/internal/str_format/parser.o \ absl/strings/internal/utf8.o \ absl/strings/match.o \ absl/strings/numbers.o \ absl/strings/str_cat.o \ absl/strings/str_replace.o \ absl/strings/str_split.o \ absl/strings/string_view.o \ absl/strings/substitute.o \ absl/synchronization/barrier.o \ absl/synchronization/blocking_counter.o \ absl/synchronization/internal/create_thread_identity.o \ absl/synchronization/internal/graphcycles.o \ absl/synchronization/internal/per_thread_sem.o \ absl/synchronization/internal/waiter.o \ absl/synchronization/mutex.o \ absl/synchronization/notification.o \ absl/time/civil_time.o \ absl/time/clock.o \ absl/time/duration.o \ absl/time/format.o \ absl/time/internal/cctz/src/civil_time_detail.o \ absl/time/internal/cctz/src/time_zone_fixed.o \ absl/time/internal/cctz/src/time_zone_format.o \ absl/time/internal/cctz/src/time_zone_if.o \ absl/time/internal/cctz/src/time_zone_impl.o \ absl/time/internal/cctz/src/time_zone_info.o \ absl/time/internal/cctz/src/time_zone_libc.o \ absl/time/internal/cctz/src/time_zone_lookup.o \ absl/time/internal/cctz/src/time_zone_posix.o \ absl/time/internal/cctz/src/zone_info_source.o \ absl/time/time.o \ absl/types/bad_any_cast.o \ absl/types/bad_optional_access.o \ absl/types/bad_variant_access.o S2LIBS = $(ABSL_LIBS) \ s2geography/linear-referencing.o \ s2geography/distance.o \ s2geography/accessors.o \ s2geography/accessors-geog.o \ s2geography/build.o \ s2geography/coverings.o \ s2geography/geography.o \ s2geography/predicates.o \ s2/base/stringprintf.o \ s2/base/strtoint.o \ s2/encoded_s2cell_id_vector.o \ s2/encoded_s2point_vector.o \ s2/encoded_s2shape_index.o \ s2/encoded_string_vector.o \ s2/id_set_lexicon.o \ s2/mutable_s2shape_index.o \ s2/r2rect.o \ s2/s1angle.o \ s2/s1chord_angle.o \ s2/s1interval.o \ s2/s2boolean_operation.o \ s2/s2builder_graph.o \ s2/s2builder.o \ s2/s2builderutil_closed_set_normalizer.o \ s2/s2builderutil_find_polygon_degeneracies.o \ s2/s2builderutil_lax_polygon_layer.o \ s2/s2builderutil_s2point_vector_layer.o \ s2/s2builderutil_s2polygon_layer.o \ s2/s2builderutil_s2polyline_layer.o \ s2/s2builderutil_s2polyline_vector_layer.o \ s2/s2builderutil_snap_functions.o \ s2/s2builderutil_testing.o \ s2/s2cap.o \ s2/s2cell_id.o \ s2/s2cell_index.o \ s2/s2cell_union.o \ s2/s2cell.o \ s2/s2centroids.o \ s2/s2closest_cell_query.o \ s2/s2closest_edge_query.o \ s2/s2closest_point_query.o \ s2/s2contains_vertex_query.o \ s2/s2convex_hull_query.o \ s2/s2coords.o \ s2/s2crossing_edge_query.o \ s2/s2debug.o \ s2/s2earth.o \ s2/s2edge_clipping.o \ s2/s2edge_crosser.o \ s2/s2edge_crossings.o \ s2/s2edge_distances.o \ s2/s2edge_tessellator.o \ s2/s2error.o \ s2/s2furthest_edge_query.o \ s2/s2latlng_rect_bounder.o \ s2/s2latlng_rect.o \ s2/s2latlng.o \ s2/s2lax_loop_shape.o \ s2/s2lax_polygon_shape.o \ s2/s2lax_polyline_shape.o \ s2/s2loop_measures.o \ s2/s2loop.o \ s2/s2max_distance_targets.o \ s2/s2measures.o \ s2/s2metrics.o \ s2/s2min_distance_targets.o \ s2/s2padded_cell.o \ s2/s2point_compression.o \ s2/s2point_region.o \ s2/s2pointutil.o \ s2/s2polygon.o \ s2/s2polyline_alignment.o \ s2/s2polyline_measures.o \ s2/s2polyline_simplifier.o \ s2/s2polyline.o \ s2/s2predicates.o \ s2/s2projections.o \ s2/s2r2rect.o \ s2/s2region_coverer.o \ s2/s2region_intersection.o \ s2/s2region_term_indexer.o \ s2/s2region_union.o \ s2/s2region.o \ s2/s2shape_index_buffered_region.o \ s2/s2shape_index_measures.o \ s2/s2shape_index.o \ s2/s2shape_measures.o \ s2/s2shapeutil_build_polygon_boundaries.o \ s2/s2shapeutil_coding.o \ s2/s2shapeutil_contains_brute_force.o \ s2/s2shapeutil_edge_iterator.o \ s2/s2shapeutil_get_reference_point.o \ s2/s2shapeutil_range_iterator.o \ s2/s2shapeutil_visit_crossing_edge_pairs.o \ s2/s2testing.o \ s2/s2text_format.o \ s2/s2wedge_relations.o \ s2/strings/ostringstream.o \ s2/strings/serialize.o \ s2/util/bits/bit-interleave.o \ s2/util/bits/bits.o \ s2/util/coding/coder.o \ s2/util/coding/varint.o \ s2/util/math/exactfloat/exactfloat.o \ s2/util/math/mathutil.o \ s2/util/units/length-units.o $(SHLIB): $(STATLIB) $(STATLIB): $(S2LIBS) $(S2LIBS): winlibs #all: clean winlibs: mkdir -p ../inst "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" $(VERSION) clean: rm -f $(SHLIB) $(STATLIB) $(OBJECTS) $(S2LIBS) .PHONY: all winlibs clean s2/src/init.cpp0000644000176200001440000000101714530411473013035 0ustar liggesusers #include "s2/s2debug.h" #include using namespace Rcpp; // [[Rcpp::export]] void cpp_s2_init() { // It's important to set this flag, as users might have "debug" flags // for their build environment, and there are some checks that will terminate // R instead of throw an exception if this value is set to true. // When possible, we also disable debug checks on a per-operation basis // if there is another way to do so (e.g., constructing S2Loop and S2Polygon objects). FLAGS_s2debug = false; // # nocov } s2/src/s2-transformers.cpp0000644000176200001440000002721314530411473015147 0ustar liggesusers #include "s2/s2shape_index_buffered_region.h" #include "s2/s2region_coverer.h" #include "s2-options.h" #include "geography-operator.h" #include using namespace Rcpp; class BooleanOperationOp: public BinaryGeographyOperator { public: BooleanOperationOp(S2BooleanOperation::OpType opType, List s2options): opType(opType) { GeographyOperationOptions options(s2options); this->geography_options = options.geographyOptions(); } SEXP processFeature(XPtr feature1, XPtr feature2, R_xlen_t i) { std::unique_ptr geog_out = s2geography::s2_boolean_operation( feature1->Index(), feature2->Index(), this->opType, this->geography_options); return RGeography::MakeXPtr(std::move(geog_out)); } private: S2BooleanOperation::OpType opType; s2geography::GlobalOptions geography_options; }; // [[Rcpp::export]] List cpp_s2_intersection(List geog1, List geog2, List s2options) { BooleanOperationOp op(S2BooleanOperation::OpType::INTERSECTION, s2options); return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_union(List geog1, List geog2, List s2options) { BooleanOperationOp op(S2BooleanOperation::OpType::UNION, s2options); return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_difference(List geog1, List geog2, List s2options) { BooleanOperationOp op(S2BooleanOperation::OpType::DIFFERENCE, s2options); return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_sym_difference(List geog1, List geog2, List s2options) { BooleanOperationOp op(S2BooleanOperation::OpType::SYMMETRIC_DIFFERENCE, s2options); return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_coverage_union_agg(List geog, List s2options, bool naRm) { GeographyOperationOptions options(s2options); s2geography::S2CoverageUnionAggregator agg(options.geographyOptions()); SEXP item; for (R_xlen_t i = 0; i < geog.size(); i++) { item = geog[i]; if (item == R_NilValue && !naRm) { return List::create(R_NilValue); } if (item != R_NilValue) { Rcpp::XPtr feature(item); agg.Add(feature->Geog()); } } std::unique_ptr geog_out = agg.Finalize(); return List::create(RGeography::MakeXPtr(std::move(geog_out))); } // [[Rcpp::export]] List cpp_s2_union_agg(List geog, List s2options, bool naRm) { GeographyOperationOptions options(s2options); s2geography::S2UnionAggregator agg(options.geographyOptions()); SEXP item; for (R_xlen_t i = 0; i < geog.size(); i++) { item = geog[i]; if (item == R_NilValue && !naRm) { return List::create(R_NilValue); } if (item != R_NilValue) { Rcpp::XPtr feature(item); agg.Add(feature->Geog()); } } std::unique_ptr geog_out = agg.Finalize(); return List::create(RGeography::MakeXPtr(std::move(geog_out))); } // [[Rcpp::export]] List cpp_s2_centroid_agg(List geog, bool naRm) { s2geography::CentroidAggregator agg; SEXP item; for (R_xlen_t i = 0; i < geog.size(); i++) { item = geog[i]; if (item == R_NilValue && !naRm) { return List::create(R_NilValue); } if (item != R_NilValue) { Rcpp::XPtr feature(item); agg.Add(feature->Geog()); } } S2Point centroid = agg.Finalize(); List output(1); if (centroid.Norm2() == 0) { output[0] = RGeography::MakeXPtr(RGeography::MakePoint()); } else { output[0] = RGeography::MakeXPtr(RGeography::MakePoint(centroid)); } return output; } // [[Rcpp::export]] List cpp_s2_rebuild_agg(List geog, List s2options, bool naRm) { GeographyOperationOptions options(s2options); s2geography::RebuildAggregator agg(options.geographyOptions()); std::vector> geographies; SEXP item; for (R_xlen_t i = 0; i < geog.size(); i++) { item = geog[i]; if (item == R_NilValue && !naRm) { return List::create(R_NilValue); } if (item != R_NilValue) { Rcpp::XPtr feature(item); agg.Add(feature->Geog()); } } auto geog_out = agg.Finalize(); return List::create(RGeography::MakeXPtr(std::move(geog_out))); } // [[Rcpp::export]] List cpp_s2_closest_point(List geog1, List geog2) { class Op: public BinaryGeographyOperator { SEXP processFeature(XPtr feature1, XPtr feature2, R_xlen_t i) { S2Point pt = s2geography::s2_closest_point(feature1->Index(), feature2->Index()); if (pt.Norm2() == 0) { return RGeography::MakeXPtr(RGeography::MakePoint()); } else { return RGeography::MakeXPtr(RGeography::MakePoint(pt)); } } }; Op op; return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_minimum_clearance_line_between(List geog1, List geog2) { class Op: public BinaryGeographyOperator { SEXP processFeature(XPtr feature1, XPtr feature2, R_xlen_t i) { std::pair pts = s2geography::s2_minimum_clearance_line_between( feature1->Index(), feature2->Index() ); if (pts.first.Norm2() == 0) { return RGeography::MakeXPtr(RGeography::MakePoint()); } std::vector vertices(2); vertices[0] = pts.first; vertices[1] = pts.second; if (pts.first == pts.second) { return RGeography::MakeXPtr(RGeography::MakePoint(std::move(vertices))); } else { std::vector vertices(2); vertices[0] = pts.first; vertices[1] = pts.second; std::unique_ptr polyline = absl::make_unique(); polyline->Init(vertices); return RGeography::MakeXPtr(RGeography::MakePolyline(std::move(polyline))); } } }; Op op; return op.processVector(geog1, geog2); } // [[Rcpp::export]] List cpp_s2_centroid(List geog) { class Op: public UnaryGeographyOperator { SEXP processFeature(XPtr feature, R_xlen_t i) { S2Point centroid = s2geography::s2_centroid(feature->Geog()); if (centroid.Norm2() == 0) { return RGeography::MakeXPtr(RGeography::MakePoint()); } else { return RGeography::MakeXPtr(RGeography::MakePoint(centroid.Normalize())); } } }; Op op; return op.processVector(geog); } // [[Rcpp::export]] List cpp_s2_point_on_surface(List geog) { class Op: public UnaryGeographyOperator { public: S2RegionCoverer coverer; SEXP processFeature(XPtr feature, R_xlen_t i) { S2Point result = s2geography::s2_point_on_surface(feature->Geog(), coverer); if (result.Norm2() == 0) { return RGeography::MakeXPtr(RGeography::MakePoint()); } else { return RGeography::MakeXPtr(RGeography::MakePoint(result)); } } }; Op op; return op.processVector(geog); } // [[Rcpp::export]] List cpp_s2_boundary(List geog) { class Op: public UnaryGeographyOperator { SEXP processFeature(XPtr feature, R_xlen_t i) { std::unique_ptr result = s2geography::s2_boundary(feature->Geog()); return RGeography::MakeXPtr(std::move(result)); } }; Op op; return op.processVector(geog); } // [[Rcpp::export]] List cpp_s2_rebuild(List geog, List s2options) { class Op: public UnaryGeographyOperator { public: Op(List s2options) { GeographyOperationOptions options(s2options); this->options = options.geographyOptions(); } SEXP processFeature(XPtr feature, R_xlen_t i) { std::unique_ptr ptr = s2geography::s2_rebuild( feature->Geog(), this->options ); return RGeography::MakeXPtr(std::move(ptr)); } private: s2geography::GlobalOptions options; }; Op op(s2options); return op.processVector(geog); } // [[Rcpp::export]] List cpp_s2_unary_union(List geog, List s2options) { class Op: public UnaryGeographyOperator { public: Op(List s2options) { GeographyOperationOptions options(s2options); this->geographyOptions = options.geographyOptions(); } SEXP processFeature(XPtr feature, R_xlen_t i) { std::unique_ptr geog_out = s2geography::s2_unary_union(feature->Index(), this->geographyOptions); return RGeography::MakeXPtr(std::move(geog_out)); } private: S2BooleanOperation::Options options; GeographyOperationOptions::LayerOptions layerOptions; s2geography::GlobalOptions geographyOptions; }; Op op(s2options); return op.processVector(geog); } // [[Rcpp::export]] List cpp_s2_interpolate_normalized(List geog, NumericVector distanceNormalized) { class Op: public UnaryGeographyOperator { public: NumericVector distanceNormalized; Op(NumericVector distanceNormalized): distanceNormalized(distanceNormalized) {} SEXP processFeature(XPtr feature, R_xlen_t i) { if (NumericVector::is_na(this->distanceNormalized[i])) { return R_NilValue; } if (s2geography::s2_is_empty(feature->Geog())) { return RGeography::MakeXPtr(RGeography::MakePoint()); } if (s2geography::s2_is_collection(feature->Geog())) { throw GeographyOperatorException("`x` must be a simple geography"); } else if (feature->Geog().dimension() != 1) { throw GeographyOperatorException("`x` must be a polyline"); } S2Point point = s2geography::s2_interpolate_normalized(feature->Geog(), this->distanceNormalized[i]); if (point.Norm2() == 0) { return RGeography::MakeXPtr(RGeography::MakePoint()); } else { return RGeography::MakeXPtr(RGeography::MakePoint(point)); } } }; Op op(distanceNormalized); return op.processVector(geog); } // [[Rcpp::export]] List cpp_s2_buffer_cells(List geog, NumericVector distance, int maxCells, int minLevel) { class Op: public UnaryGeographyOperator { public: NumericVector distance; S2RegionCoverer coverer; Op(NumericVector distance, int maxCells, int minLevel): distance(distance) { this->coverer.mutable_options()->set_max_cells(maxCells); if (minLevel > 0) { this->coverer.mutable_options()->set_min_level(minLevel); } } SEXP processFeature(XPtr feature, R_xlen_t i) { S2ShapeIndexBufferedRegion region; region.Init(&feature->Index().ShapeIndex(), S1ChordAngle::Radians(this->distance[i])); S2CellUnion cellUnion; cellUnion = coverer.GetCovering(region); std::unique_ptr polygon = absl::make_unique(); polygon->InitToCellUnionBorder(cellUnion); return RGeography::MakeXPtr(RGeography::MakePolygon(std::move(polygon))); } }; Op op(distance, maxCells, minLevel); return op.processVector(geog); } // [[Rcpp::export]] List cpp_s2_convex_hull(List geog) { class Op: public UnaryGeographyOperator { SEXP processFeature(XPtr feature, R_xlen_t i) { std::unique_ptr geog_out = s2geography::s2_convex_hull(feature->Geog()); return RGeography::MakeXPtr(std::move(geog_out)); } }; Op op; return op.processVector(geog); } // [[Rcpp::export]] List cpp_s2_convex_hull_agg(List geog, bool naRm) { s2geography::S2ConvexHullAggregator agg; SEXP item; for (R_xlen_t i = 0; i < geog.size(); i++) { item = geog[i]; if (item == R_NilValue && !naRm) { return List::create(R_NilValue); } if (item != R_NilValue) { XPtr feature(item); agg.Add(feature->Geog()); } } return List::create(RGeography::MakeXPtr(agg.Finalize())); } s2/src/geography.h0000644000176200001440000000606714530411473013536 0ustar liggesusers #ifndef GEOGRAPHY_H #define GEOGRAPHY_H #include #include "s2geography.h" class RGeography { public: RGeography(std::unique_ptr geog): geog_(std::move(geog)), index_(nullptr) {} const s2geography::Geography& Geog() const { return *geog_; } const s2geography::ShapeIndexGeography& Index() { if (!index_) { this->index_ = absl::make_unique(*geog_); } return *index_; } // For an unknown reason, returning a SEXP from MakeXPtr results in // rchk reporting a memory protection error. Until this is sorted, return a // Rcpp::XPtr<>() (even though this might be slower) static Rcpp::XPtr MakeXPtr(std::unique_ptr geog) { return Rcpp::XPtr(new RGeography(std::move(geog))); } static Rcpp::XPtr MakeXPtr(std::unique_ptr geog) { return Rcpp::XPtr(geog.release()); } static std::unique_ptr MakePoint() { return absl::make_unique(absl::make_unique()); } static std::unique_ptr MakePoint(S2Point point) { return absl::make_unique(absl::make_unique(point)); } static std::unique_ptr MakePoint(std::vector points) { return absl::make_unique(absl::make_unique(std::move(points))); } static std::unique_ptr MakePolyline() { return absl::make_unique(absl::make_unique()); } static std::unique_ptr MakePolyline(std::unique_ptr polyline) { return absl::make_unique(absl::make_unique(std::move(polyline))); } static std::unique_ptr MakePolyline(std::vector> polylines) { return absl::make_unique(absl::make_unique(std::move(polylines))); } static std::unique_ptr MakePolygon() { return absl::make_unique(absl::make_unique()); } static std::unique_ptr MakePolygon(std::unique_ptr polygon) { return absl::make_unique(absl::make_unique(std::move(polygon))); } static std::unique_ptr MakeCollection() { return absl::make_unique(absl::make_unique()); } static std::unique_ptr MakeCollection(std::vector> features) { return absl::make_unique(absl::make_unique(std::move(features))); } private: std::unique_ptr geog_; std::unique_ptr index_; static void finalize_xptr(SEXP xptr) { RGeography* geog = reinterpret_cast(R_ExternalPtrAddr(xptr)); if (geog != nullptr) { delete geog; } } }; #endif s2/src/cpp-compat.cpp0000644000176200001440000000176214530411473014144 0ustar liggesusers #include "cpp-compat.h" #include #include using namespace Rcpp; #include // va_ stuff void cpp_compat_printf(const char* fmt, ...) { va_list args; va_start(args, fmt); Rprintf(fmt, args); va_end(args); } void cpp_compat_abort() { throw std::runtime_error("abort() called"); } void cpp_compat_exit(int code) { throw std::runtime_error("exit() called"); } int cpp_compat_random() { // trying to match what random() would return // www.gnu.org/software/libc/manual/html_node/BSD-Random.html#BSD-Random // the RNG state is correctly managed for functions that use // Rcpp::export...other functions will require management of the RNGScope return unif_rand() * INT_MAX; } void cpp_compat_srandom(int seed) { // pretty sure this should not have any effect // it gets called on load here with the initiation // of the Random class in s2testing, so it can't // error out } std::ostream& cpp_compat_cerr = Rcerr; std::ostream& cpp_compat_cout = Rcout; s2/src/Makevars.ucrt0000644000176200001440000001670614645664251014064 0ustar liggesusersPKG_CPPFLAGS = -DS2_USE_EXACTFLOAT -D_USE_MATH_DEFINES -DNDEBUG -DIS_LITTLE_ENDIAN -DOMIT_STRPTIME -I../src ifeq (,$(shell pkg-config --version 2>/dev/null)) PKG_LIBS = -Ls2 -ls2static -lssl -lcrypto -lz -lws2_32 -lgdi32 -lcrypt32 else PKG_LIBS = -Ls2 -ls2static $(shell pkg-config --libs openssl) endif CXX_STD = CXX14 STATLIB = s2/libs2static.a ABSL_LIBS = absl/base/internal/cycleclock.o \ absl/base/internal/low_level_alloc.o \ absl/base/internal/raw_logging.o \ absl/base/internal/scoped_set_env.o \ absl/base/internal/spinlock_wait.o \ absl/base/internal/spinlock.o \ absl/base/internal/strerror.o \ absl/base/internal/sysinfo.o \ absl/base/internal/thread_identity.o \ absl/base/internal/throw_delegate.o \ absl/base/internal/unscaledcycleclock.o \ absl/base/log_severity.o \ absl/container/internal/hashtablez_sampler_force_weak_definition.o \ absl/container/internal/hashtablez_sampler.o \ absl/container/internal/raw_hash_set.o \ absl/debugging/failure_signal_handler.o \ absl/debugging/internal/address_is_readable.o \ absl/debugging/internal/demangle.o \ absl/debugging/internal/elf_mem_image.o \ absl/debugging/internal/examine_stack.o \ absl/debugging/internal/stack_consumption.o \ absl/debugging/internal/vdso_support.o \ absl/debugging/leak_check.o \ absl/debugging/stacktrace.o \ absl/debugging/symbolize.o \ absl/numeric/int128.o \ absl/profiling/internal/exponential_biased.o \ absl/profiling/internal/periodic_sampler.o \ absl/strings/ascii.o \ absl/strings/charconv.o \ absl/strings/cord_analysis.o \ absl/strings/cord_buffer.o \ absl/strings/cord.o \ absl/strings/escaping.o \ absl/strings/internal/charconv_bigint.o \ absl/strings/internal/charconv_parse.o \ absl/strings/internal/cord_internal.o \ absl/strings/internal/cord_rep_btree_navigator.o \ absl/strings/internal/cord_rep_btree_reader.o \ absl/strings/internal/cord_rep_btree.o \ absl/strings/internal/cord_rep_consume.o \ absl/strings/internal/cord_rep_crc.o \ absl/strings/internal/cord_rep_ring.o \ absl/strings/internal/cordz_functions.o \ absl/strings/internal/cordz_handle.o \ absl/strings/internal/cordz_info.o \ absl/strings/internal/cordz_sample_token.o \ absl/strings/internal/escaping.o \ absl/strings/internal/memutil.o \ absl/strings/internal/ostringstream.o \ absl/strings/internal/pow10_helper.o \ absl/strings/internal/str_format/arg.o \ absl/strings/internal/str_format/bind.o \ absl/strings/internal/str_format/extension.o \ absl/strings/internal/str_format/float_conversion.o \ absl/strings/internal/str_format/output.o \ absl/strings/internal/str_format/parser.o \ absl/strings/internal/utf8.o \ absl/strings/match.o \ absl/strings/numbers.o \ absl/strings/str_cat.o \ absl/strings/str_replace.o \ absl/strings/str_split.o \ absl/strings/string_view.o \ absl/strings/substitute.o \ absl/synchronization/barrier.o \ absl/synchronization/blocking_counter.o \ absl/synchronization/internal/create_thread_identity.o \ absl/synchronization/internal/graphcycles.o \ absl/synchronization/internal/per_thread_sem.o \ absl/synchronization/internal/waiter.o \ absl/synchronization/mutex.o \ absl/synchronization/notification.o \ absl/time/civil_time.o \ absl/time/clock.o \ absl/time/duration.o \ absl/time/format.o \ absl/time/internal/cctz/src/civil_time_detail.o \ absl/time/internal/cctz/src/time_zone_fixed.o \ absl/time/internal/cctz/src/time_zone_format.o \ absl/time/internal/cctz/src/time_zone_if.o \ absl/time/internal/cctz/src/time_zone_impl.o \ absl/time/internal/cctz/src/time_zone_info.o \ absl/time/internal/cctz/src/time_zone_libc.o \ absl/time/internal/cctz/src/time_zone_lookup.o \ absl/time/internal/cctz/src/time_zone_posix.o \ absl/time/internal/cctz/src/zone_info_source.o \ absl/time/time.o \ absl/types/bad_any_cast.o \ absl/types/bad_optional_access.o \ absl/types/bad_variant_access.o S2LIBS = $(ABSL_LIBS) \ s2geography/linear-referencing.o \ s2geography/distance.o \ s2geography/accessors.o \ s2geography/accessors-geog.o \ s2geography/build.o \ s2geography/coverings.o \ s2geography/geography.o \ s2geography/predicates.o \ s2/base/stringprintf.o \ s2/base/strtoint.o \ s2/encoded_s2cell_id_vector.o \ s2/encoded_s2point_vector.o \ s2/encoded_s2shape_index.o \ s2/encoded_string_vector.o \ s2/id_set_lexicon.o \ s2/mutable_s2shape_index.o \ s2/r2rect.o \ s2/s1angle.o \ s2/s1chord_angle.o \ s2/s1interval.o \ s2/s2boolean_operation.o \ s2/s2builder_graph.o \ s2/s2builder.o \ s2/s2builderutil_closed_set_normalizer.o \ s2/s2builderutil_find_polygon_degeneracies.o \ s2/s2builderutil_lax_polygon_layer.o \ s2/s2builderutil_s2point_vector_layer.o \ s2/s2builderutil_s2polygon_layer.o \ s2/s2builderutil_s2polyline_layer.o \ s2/s2builderutil_s2polyline_vector_layer.o \ s2/s2builderutil_snap_functions.o \ s2/s2builderutil_testing.o \ s2/s2cap.o \ s2/s2cell_id.o \ s2/s2cell_index.o \ s2/s2cell_union.o \ s2/s2cell.o \ s2/s2centroids.o \ s2/s2closest_cell_query.o \ s2/s2closest_edge_query.o \ s2/s2closest_point_query.o \ s2/s2contains_vertex_query.o \ s2/s2convex_hull_query.o \ s2/s2coords.o \ s2/s2crossing_edge_query.o \ s2/s2debug.o \ s2/s2earth.o \ s2/s2edge_clipping.o \ s2/s2edge_crosser.o \ s2/s2edge_crossings.o \ s2/s2edge_distances.o \ s2/s2edge_tessellator.o \ s2/s2error.o \ s2/s2furthest_edge_query.o \ s2/s2latlng_rect_bounder.o \ s2/s2latlng_rect.o \ s2/s2latlng.o \ s2/s2lax_loop_shape.o \ s2/s2lax_polygon_shape.o \ s2/s2lax_polyline_shape.o \ s2/s2loop_measures.o \ s2/s2loop.o \ s2/s2max_distance_targets.o \ s2/s2measures.o \ s2/s2metrics.o \ s2/s2min_distance_targets.o \ s2/s2padded_cell.o \ s2/s2point_compression.o \ s2/s2point_region.o \ s2/s2pointutil.o \ s2/s2polygon.o \ s2/s2polyline_alignment.o \ s2/s2polyline_measures.o \ s2/s2polyline_simplifier.o \ s2/s2polyline.o \ s2/s2predicates.o \ s2/s2projections.o \ s2/s2r2rect.o \ s2/s2region_coverer.o \ s2/s2region_intersection.o \ s2/s2region_term_indexer.o \ s2/s2region_union.o \ s2/s2region.o \ s2/s2shape_index_buffered_region.o \ s2/s2shape_index_measures.o \ s2/s2shape_index.o \ s2/s2shape_measures.o \ s2/s2shapeutil_build_polygon_boundaries.o \ s2/s2shapeutil_coding.o \ s2/s2shapeutil_contains_brute_force.o \ s2/s2shapeutil_edge_iterator.o \ s2/s2shapeutil_get_reference_point.o \ s2/s2shapeutil_range_iterator.o \ s2/s2shapeutil_visit_crossing_edge_pairs.o \ s2/s2testing.o \ s2/s2text_format.o \ s2/s2wedge_relations.o \ s2/strings/ostringstream.o \ s2/strings/serialize.o \ s2/util/bits/bit-interleave.o \ s2/util/bits/bits.o \ s2/util/coding/coder.o \ s2/util/coding/varint.o \ s2/util/math/exactfloat/exactfloat.o \ s2/util/math/mathutil.o \ s2/util/units/length-units.o $(SHLIB): $(STATLIB) $(STATLIB): $(S2LIBS) #all: clean clean: rm -f $(SHLIB) $(STATLIB) $(OBJECTS) $(S2LIBS) .PHONY: all clean s2/src/s2/0000755000176200001440000000000014645746012011722 5ustar liggesuserss2/src/s2/s2closest_point_query.h0000644000176200001440000004300014530411473016440 0ustar liggesusers// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // See S2ClosestPointQuery (defined below) for an overview. #ifndef S2_S2CLOSEST_POINT_QUERY_H_ #define S2_S2CLOSEST_POINT_QUERY_H_ #include #include "s2/base/logging.h" #include "s2/s1angle.h" #include "s2/s1chord_angle.h" #include "s2/s2closest_point_query_base.h" #include "s2/s2min_distance_targets.h" #include "s2/s2point_index.h" // Options that control the set of points returned. Note that by default // *all* points are returned, so you will always want to set either the // max_results() option or the max_distance() option (or both). // // This class is also available as S2ClosestPointQuery::Options. // (It is defined here to avoid depending on the "Data" template argument.) class S2ClosestPointQueryOptions : public S2ClosestPointQueryBaseOptions { public: using Distance = S2MinDistance; using Base = S2ClosestPointQueryBaseOptions; // See S2ClosestPointQueryBaseOptions for the full set of options. // Specifies that only points whose distance to the target is less than // "max_distance" should be returned. // // Note that points whose distance is exactly equal to "max_distance" are // not returned. Normally this doesn't matter, because distances are not // computed exactly in the first place, but if such points are needed then // see set_inclusive_max_distance() below. // // DEFAULT: Distance::Infinity() void set_max_distance(S1ChordAngle max_distance); // Like set_max_distance(), except that points whose distance is exactly // equal to "max_distance" are also returned. Equivalent to calling // set_max_distance(max_distance.Successor()). void set_inclusive_max_distance(S1ChordAngle max_distance); // Like set_inclusive_max_distance(), except that "max_distance" is also // increased by the maximum error in the distance calculation. This ensures // that all points whose true distance is less than or equal to // "max_distance" will be returned (along with some points whose true // distance is slightly greater). // // Algorithms that need to do exact distance comparisons can use this // option to find a set of candidate points that can then be filtered // further (e.g., using s2pred::CompareDistance). void set_conservative_max_distance(S1ChordAngle max_distance); // Versions of set_max_distance that take an S1Angle argument. (Note that // these functions require a conversion, and that the S1ChordAngle versions // are preferred.) void set_max_distance(S1Angle max_distance); void set_inclusive_max_distance(S1Angle max_distance); void set_conservative_max_distance(S1Angle max_distance); // See S2ClosestPointQueryBaseOptions for documentation. using Base::set_max_error; // S1Chordangle version void set_max_error(S1Angle max_error); // S1Angle version // Inherited options (see s2closest_point_query_base.h for details): using Base::set_max_results; using Base::set_region; using Base::set_use_brute_force; }; // S2ClosestPointQueryTarget represents the geometry to which the distance is // measured. There are subtypes for measuring the distance to a point, an // edge, an S2Cell, or an S2ShapeIndex (an arbitrary collection of geometry). using S2ClosestPointQueryTarget = S2MinDistanceTarget; // Target subtype that computes the closest distance to a point. // // This class is also available as S2ClosestPointQuery::PointTarget. // (It is defined here to avoid depending on the "Data" template argument.) class S2ClosestPointQueryPointTarget final : public S2MinDistancePointTarget { public: explicit S2ClosestPointQueryPointTarget(const S2Point& point); int max_brute_force_index_size() const override; }; // Target subtype that computes the closest distance to an edge. // // This class is also available as S2ClosestPointQuery::EdgeTarget. // (It is defined here to avoid depending on the "Data" template argument.) class S2ClosestPointQueryEdgeTarget final : public S2MinDistanceEdgeTarget { public: explicit S2ClosestPointQueryEdgeTarget(const S2Point& a, const S2Point& b); int max_brute_force_index_size() const override; }; // Target subtype that computes the closest distance to an S2Cell // (including the interior of the cell). // // This class is also available as S2ClosestPointQuery::CellTarget. // (It is defined here to avoid depending on the "Data" template argument.) class S2ClosestPointQueryCellTarget final : public S2MinDistanceCellTarget { public: explicit S2ClosestPointQueryCellTarget(const S2Cell& cell); int max_brute_force_index_size() const override; }; // Target subtype that computes the closest distance to an S2ShapeIndex // (an arbitrary collection of points, polylines, and/or polygons). // // By default, distances are measured to the boundary and interior of // polygons in the S2ShapeIndex rather than to polygon boundaries only. // If you wish to change this behavior, you may call // // target.set_include_interiors(false); // // (see S2MinDistanceShapeIndexTarget for details). // // This class is also available as S2ClosestPointQuery::ShapeIndexTarget. // (It is defined here to avoid depending on the "Data" template argument.) class S2ClosestPointQueryShapeIndexTarget final : public S2MinDistanceShapeIndexTarget { public: explicit S2ClosestPointQueryShapeIndexTarget(const S2ShapeIndex* index); int max_brute_force_index_size() const override; }; // Given a set of points stored in an S2PointIndex, S2ClosestPointQuery // provides methods that find the closest point(s) to a given query point // or query edge. Example usage: // // void Test(const vector& index_points, // const vector& target_points) { // // The template argument allows auxiliary data to be attached to each // // point (in this case, the array index). // S2PointIndex index; // for (int i = 0; i < index_points.size(); ++i) { // index.Add(index_points[i], i); // } // S2ClosestPointQuery query(&index); // query.mutable_options()->set_max_results(5); // for (const S2Point& target_point : target_points) { // S2ClosestPointQueryPointTarget target(target_point); // for (const auto& result : query.FindClosestPoints(&target)) { // // The Result class contains the following methods: // // distance() is the distance to the target. // // point() is the indexed point. // // data() is the auxiliary data. // DoSomething(target_point, result); // } // } // } // // You can find either the k closest points, or all points within a given // radius, or both (i.e., the k closest points up to a given maximum radius). // E.g. to find all the points within 5 kilometers, call // // query.mutable_options()->set_max_distance( // S2Earth::ToAngle(util::units::Kilometers(5))); // // By default *all* points are returned, so you should always specify either // max_results() or max_distance() or both. There is also a FindClosestPoint() // convenience method that returns only the closest point. // // You can restrict the results to an arbitrary S2Region, for example: // // S2LatLngRect rect(...); // query.mutable_options()->set_region(&rect); // Does *not* take ownership. // // To find the closest points to a query edge rather than a point, use: // // S2ClosestPointQueryEdgeTarget target(v0, v1); // query.FindClosestPoints(&target); // // Similarly you can find the closest points to an S2Cell by using an // S2ClosestPointQuery::CellTarget, and you can find the closest points to an // arbitrary collection of points, polylines, and polygons by using an // S2ClosestPointQuery::ShapeIndexTarget. // // The implementation is designed to be fast for both small and large // point sets. template class S2ClosestPointQuery { public: // See S2ClosestPointQueryBase for full documentation. using Index = S2PointIndex; using PointData = typename Index::PointData; // S2MinDistance is a thin wrapper around S1ChordAngle that implements the // Distance concept required by S2ClosestPointQueryBase. using Distance = S2MinDistance; using Base = S2ClosestPointQueryBase; // Each "Result" object represents a closest point. Here are its main // methods (see S2ClosestPointQueryBase::Result for details): // // // The distance from the target to this point. // S1ChordAngle distance() const; // // // The point itself. // const S2Point& point() const; // // // The client-specified data associated with this point. // const Data& data() const; using Result = typename Base::Result; using Options = S2ClosestPointQueryOptions; // The available target types (see definitions above). using Target = S2ClosestPointQueryTarget; using PointTarget = S2ClosestPointQueryPointTarget; using EdgeTarget = S2ClosestPointQueryEdgeTarget; using CellTarget = S2ClosestPointQueryCellTarget; using ShapeIndexTarget = S2ClosestPointQueryShapeIndexTarget; // Convenience constructor that calls Init(). Options may be specified here // or changed at any time using the mutable_options() accessor method. explicit S2ClosestPointQuery(const Index* index, const Options& options = Options()); // Default constructor; requires Init() to be called. S2ClosestPointQuery(); ~S2ClosestPointQuery(); // Initializes the query. Options may be specified here or changed at any // time using the mutable_options() accessor method. // // REQUIRES: "index" must persist for the lifetime of this object. // REQUIRES: ReInit() must be called if "index" is modified. void Init(const Index* index, const Options& options = Options()); // Reinitializes the query. This method must be called whenever the // underlying index is modified. void ReInit(); // Returns a reference to the underlying S2PointIndex. const Index& index() const; // Returns the query options. Options can be modifed between queries. const Options& options() const; Options* mutable_options(); // Returns the closest points to the given target that satisfy the current // options. This method may be called multiple times. std::vector FindClosestPoints(Target* target); // This version can be more efficient when this method is called many times, // since it does not require allocating a new vector on each call. void FindClosestPoints(Target* target, std::vector* results); //////////////////////// Convenience Methods //////////////////////// // Returns the closest point to the target. If no point satisfies the search // criteria, then a Result object with distance() == Infinity() and // is_empty() == true is returned. Result FindClosestPoint(Target* target); // Returns the minimum distance to the target. If the index or target is // empty, returns S1ChordAngle::Infinity(). // // Use IsDistanceLess() if you only want to compare the distance against a // threshold value, since it is often much faster. S1ChordAngle GetDistance(Target* target); // Returns true if the distance to "target" is less than "limit". // // This method is usually much faster than GetDistance(), since it is much // less work to determine whether the minimum distance is above or below a // threshold than it is to calculate the actual minimum distance. bool IsDistanceLess(Target* target, S1ChordAngle limit); // Like IsDistanceLess(), but also returns true if the distance to "target" // is exactly equal to "limit". bool IsDistanceLessOrEqual(Target* target, S1ChordAngle limit); // Like IsDistanceLessOrEqual(), except that "limit" is increased by the // maximum error in the distance calculation. This ensures that this // function returns true whenever the true, exact distance is less than // or equal to "limit". // // For example, suppose that we want to test whether two geometries might // intersect each other after they are snapped together using S2Builder // (using the IdentitySnapFunction with a given "snap_radius"). Since // S2Builder uses exact distance predicates (s2predicates.h), we need to // measure the distance between the two geometries conservatively. If the // distance is definitely greater than "snap_radius", then the geometries // are guaranteed to not intersect after snapping. bool IsConservativeDistanceLessOrEqual(Target* target, S1ChordAngle limit); private: Options options_; Base base_; }; ////////////////// Implementation details follow //////////////////// inline void S2ClosestPointQueryOptions::set_max_distance( S1ChordAngle max_distance) { Base::set_max_distance(Distance(max_distance)); } inline void S2ClosestPointQueryOptions::set_max_distance(S1Angle max_distance) { Base::set_max_distance(Distance(max_distance)); } inline void S2ClosestPointQueryOptions::set_inclusive_max_distance( S1ChordAngle max_distance) { set_max_distance(max_distance.Successor()); } inline void S2ClosestPointQueryOptions::set_inclusive_max_distance( S1Angle max_distance) { set_inclusive_max_distance(S1ChordAngle(max_distance)); } inline void S2ClosestPointQueryOptions::set_max_error(S1Angle max_error) { Base::set_max_error(S1ChordAngle(max_error)); } inline S2ClosestPointQueryPointTarget::S2ClosestPointQueryPointTarget( const S2Point& point) : S2MinDistancePointTarget(point) { } inline S2ClosestPointQueryEdgeTarget::S2ClosestPointQueryEdgeTarget( const S2Point& a, const S2Point& b) : S2MinDistanceEdgeTarget(a, b) { } inline S2ClosestPointQueryCellTarget::S2ClosestPointQueryCellTarget( const S2Cell& cell) : S2MinDistanceCellTarget(cell) { } inline S2ClosestPointQueryShapeIndexTarget::S2ClosestPointQueryShapeIndexTarget( const S2ShapeIndex* index) : S2MinDistanceShapeIndexTarget(index) { } template inline S2ClosestPointQuery::S2ClosestPointQuery(const Index* index, const Options& options) { Init(index, options); } template S2ClosestPointQuery::S2ClosestPointQuery() { // Prevent inline constructor bloat by defining here. } template S2ClosestPointQuery::~S2ClosestPointQuery() { // Prevent inline destructor bloat by defining here. } template void S2ClosestPointQuery::Init(const Index* index, const Options& options) { options_ = options; base_.Init(index); } template inline void S2ClosestPointQuery::ReInit() { base_.ReInit(); } template inline const S2PointIndex& S2ClosestPointQuery::index() const { return base_.index(); } template inline const S2ClosestPointQueryOptions& S2ClosestPointQuery::options() const { return options_; } template inline S2ClosestPointQueryOptions* S2ClosestPointQuery::mutable_options() { return &options_; } template inline std::vector::Result> S2ClosestPointQuery::FindClosestPoints(Target* target) { return base_.FindClosestPoints(target, options_); } template inline void S2ClosestPointQuery::FindClosestPoints( Target* target, std::vector* results) { base_.FindClosestPoints(target, options_, results); } template inline typename S2ClosestPointQuery::Result S2ClosestPointQuery::FindClosestPoint(Target* target) { static_assert(sizeof(Options) <= 32, "Consider not copying Options here"); Options tmp_options = options_; tmp_options.set_max_results(1); return base_.FindClosestPoint(target, tmp_options); } template inline S1ChordAngle S2ClosestPointQuery::GetDistance(Target* target) { return FindClosestPoint(target).distance(); } template bool S2ClosestPointQuery::IsDistanceLess( Target* target, S1ChordAngle limit) { static_assert(sizeof(Options) <= 32, "Consider not copying Options here"); Options tmp_options = options_; tmp_options.set_max_results(1); tmp_options.set_max_distance(limit); tmp_options.set_max_error(S1ChordAngle::Straight()); return !base_.FindClosestPoint(target, tmp_options).is_empty(); } template bool S2ClosestPointQuery::IsDistanceLessOrEqual( Target* target, S1ChordAngle limit) { static_assert(sizeof(Options) <= 32, "Consider not copying Options here"); Options tmp_options = options_; tmp_options.set_max_results(1); tmp_options.set_inclusive_max_distance(limit); tmp_options.set_max_error(S1ChordAngle::Straight()); return !base_.FindClosestPoint(target, tmp_options).is_empty(); } template bool S2ClosestPointQuery::IsConservativeDistanceLessOrEqual( Target* target, S1ChordAngle limit) { static_assert(sizeof(Options) <= 32, "Consider not copying Options here"); Options tmp_options = options_; tmp_options.set_max_results(1); tmp_options.set_conservative_max_distance(limit); tmp_options.set_max_error(S1ChordAngle::Straight()); return !base_.FindClosestPoint(target, tmp_options).is_empty(); } #endif // S2_S2CLOSEST_POINT_QUERY_H_ s2/src/s2/s2max_distance_targets.cc0000644000176200001440000002345714530411473016672 0ustar liggesusers// Copyright Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "s2/s2max_distance_targets.h" #include #include "absl/memory/memory.h" #include "s2/s1angle.h" #include "s2/s2cap.h" #include "s2/s2cell.h" #include "s2/s2edge_distances.h" #include "s2/s2furthest_edge_query.h" #include "s2/s2shape_index_region.h" #include "s2/s2text_format.h" ////////////////// Point Target //////////////////// // This method returns an S2Cap that bounds the antipode of the target. (This // is the set of points whose S2MaxDistance to the target is // S2MaxDistance::Zero().) S2Cap S2MaxDistancePointTarget::GetCapBound() { return S2Cap(-point_, S1ChordAngle::Zero()); } bool S2MaxDistancePointTarget::UpdateMinDistance( const S2Point& p, S2MaxDistance* min_dist) { return min_dist->UpdateMin(S2MaxDistance(S1ChordAngle(p, point_))); } bool S2MaxDistancePointTarget::UpdateMinDistance( const S2Point& v0, const S2Point& v1, S2MaxDistance* min_dist) { S1ChordAngle dist(*min_dist); if (S2::UpdateMaxDistance(point_, v0, v1, &dist)) { min_dist->UpdateMin(S2MaxDistance(dist)); return true; } return false; } bool S2MaxDistancePointTarget::UpdateMinDistance( const S2Cell& cell, S2MaxDistance* min_dist) { return min_dist->UpdateMin(S2MaxDistance(cell.GetMaxDistance(point_))); } bool S2MaxDistancePointTarget::VisitContainingShapes( const S2ShapeIndex& index, const ShapeVisitor& visitor) { // For furthest points, we visit the polygons whose interior contains the // antipode of the target point. (These are the polygons whose // S2MaxDistance to the target is S2MaxDistance::Zero().) return MakeS2ContainsPointQuery(&index).VisitContainingShapes( -point_, [this, &visitor](S2Shape* shape) { return visitor(shape, point_); }); } ////////////////// Edge Target //////////////////// // This method returns an S2Cap that bounds the antipode of the target. (This // is the set of points whose S2MaxDistance to the target is // S2MaxDistance::Zero().) S2Cap S2MaxDistanceEdgeTarget::GetCapBound() { // The following computes a radius equal to half the edge length in an // efficient and numerically stable way. double d2 = S1ChordAngle(a_, b_).length2(); double r2 = (0.5 * d2) / (1 + sqrt(1 - 0.25 * d2)); return S2Cap(-(a_ + b_).Normalize(), S1ChordAngle::FromLength2(r2)); } bool S2MaxDistanceEdgeTarget::UpdateMinDistance( const S2Point& p, S2MaxDistance* min_dist) { S1ChordAngle dist(*min_dist); if (S2::UpdateMaxDistance(p, a_, b_, &dist)) { min_dist->UpdateMin(S2MaxDistance(dist)); return true; } return false; } bool S2MaxDistanceEdgeTarget::UpdateMinDistance( const S2Point& v0, const S2Point& v1, S2MaxDistance* min_dist) { S1ChordAngle dist(*min_dist); if (S2::UpdateEdgePairMaxDistance(a_, b_, v0, v1, &dist)) { min_dist->UpdateMin(S2MaxDistance(dist)); return true; } return false; } bool S2MaxDistanceEdgeTarget::UpdateMinDistance( const S2Cell& cell, S2MaxDistance* min_dist) { return min_dist->UpdateMin(S2MaxDistance(cell.GetMaxDistance(a_, b_))); } bool S2MaxDistanceEdgeTarget::VisitContainingShapes( const S2ShapeIndex& index, const ShapeVisitor& visitor) { // We only need to test one edge point. That is because the method *must* // visit a polygon if it fully contains the target, and *is allowed* to // visit a polygon if it intersects the target. If the tested vertex is not // contained, we know the full edge is not contained; if the tested vertex is // contained, then the edge either is fully contained (must be visited) or it // intersects (is allowed to be visited). We visit the center of the edge so // that edge AB gives identical results to BA. S2MaxDistancePointTarget target((a_ + b_).Normalize()); return target.VisitContainingShapes(index, visitor); } ////////////////// Cell Target //////////////////// // This method returns an S2Cap that bounds the antipode of the target. (This // is the set of points whose S2MaxDistance to the target is // S2MaxDistance::Zero().) S2Cap S2MaxDistanceCellTarget::GetCapBound() { S2Cap cap = cell_.GetCapBound(); return S2Cap(-cap.center(), cap.radius()); } bool S2MaxDistanceCellTarget::UpdateMinDistance( const S2Point& p, S2MaxDistance* min_dist) { return min_dist->UpdateMin(S2MaxDistance(cell_.GetMaxDistance(p))); } bool S2MaxDistanceCellTarget::UpdateMinDistance( const S2Point& v0, const S2Point& v1, S2MaxDistance* min_dist) { return min_dist->UpdateMin(S2MaxDistance(cell_.GetMaxDistance(v0, v1))); } bool S2MaxDistanceCellTarget::UpdateMinDistance( const S2Cell& cell, S2MaxDistance* min_dist) { return min_dist->UpdateMin(S2MaxDistance(cell_.GetMaxDistance(cell))); } bool S2MaxDistanceCellTarget::VisitContainingShapes( const S2ShapeIndex& index, const ShapeVisitor& visitor) { // We only need to check one point here - cell center is simplest. // See comment at S2MaxDistanceEdgeTarget::VisitContainingShapes. S2MaxDistancePointTarget target(cell_.GetCenter()); return target.VisitContainingShapes(index, visitor); } ////////////////// Index Target //////////////////// S2MaxDistanceShapeIndexTarget::S2MaxDistanceShapeIndexTarget( const S2ShapeIndex* index) : index_(index), query_(absl::make_unique(index)) { } S2MaxDistanceShapeIndexTarget::~S2MaxDistanceShapeIndexTarget() { } bool S2MaxDistanceShapeIndexTarget::include_interiors() const { return query_->options().include_interiors(); } void S2MaxDistanceShapeIndexTarget::set_include_interiors( bool include_interiors) { query_->mutable_options()->set_include_interiors(include_interiors); } bool S2MaxDistanceShapeIndexTarget::use_brute_force() const { return query_->options().use_brute_force(); } void S2MaxDistanceShapeIndexTarget::set_use_brute_force( bool use_brute_force) { query_->mutable_options()->set_use_brute_force(use_brute_force); } bool S2MaxDistanceShapeIndexTarget::set_max_error( const S1ChordAngle& max_error) { query_->mutable_options()->set_max_error(max_error); return true; // Indicates that we may return suboptimal results. } // This method returns an S2Cap that bounds the antipode of the target. (This // is the set of points whose S2MaxDistance to the target is // S2MaxDistance::Zero().) S2Cap S2MaxDistanceShapeIndexTarget::GetCapBound() { S2Cap cap = MakeS2ShapeIndexRegion(index_).GetCapBound(); return S2Cap(-cap.center(), cap.radius()); } bool S2MaxDistanceShapeIndexTarget::UpdateMinDistance( const S2Point& p, S2MaxDistance* min_dist) { query_->mutable_options()->set_min_distance(S1ChordAngle(*min_dist)); S2FurthestEdgeQuery::PointTarget target(p); S2FurthestEdgeQuery::Result r = query_->FindFurthestEdge(&target); if (r.shape_id() < 0) { return false; } *min_dist = S2MaxDistance(r.distance()); return true; } bool S2MaxDistanceShapeIndexTarget::UpdateMinDistance( const S2Point& v0, const S2Point& v1, S2MaxDistance* min_dist) { query_->mutable_options()->set_min_distance(S1ChordAngle(*min_dist)); S2FurthestEdgeQuery::EdgeTarget target(v0, v1); S2FurthestEdgeQuery::Result r = query_->FindFurthestEdge(&target); if (r.shape_id() < 0) return false; *min_dist = S2MaxDistance(r.distance()); return true; } bool S2MaxDistanceShapeIndexTarget::UpdateMinDistance( const S2Cell& cell, S2MaxDistance* min_dist) { query_->mutable_options()->set_min_distance(S1ChordAngle(*min_dist)); S2FurthestEdgeQuery::CellTarget target(cell); S2FurthestEdgeQuery::Result r = query_->FindFurthestEdge(&target); if (r.shape_id() < 0) return false; *min_dist = S2MaxDistance(r.distance()); return true; } // For target types consisting of multiple connected components (such as // S2MaxDistanceShapeIndexTarget), this method should return the // polygons containing the antipodal reflection of *any* connected // component. (It is sufficient to test containment of one vertex per // connected component, since the API allows us to also return any polygon // whose boundary has S2MaxDistance::Zero() to the target.) bool S2MaxDistanceShapeIndexTarget::VisitContainingShapes( const S2ShapeIndex& query_index, const ShapeVisitor& visitor) { // It is sufficient to find the set of chain starts in the target index // (i.e., one vertex per connected component of edges) that are contained by // the query index, except for one special case to handle full polygons. // // TODO(ericv): Do this by merge-joining the two S2ShapeIndexes, and share // the code with S2BooleanOperation. for (S2Shape* shape : *index_) { if (shape == nullptr) continue; int num_chains = shape->num_chains(); // Shapes that don't have any edges require a special case (below). bool tested_point = false; for (int c = 0; c < num_chains; ++c) { S2Shape::Chain chain = shape->chain(c); if (chain.length == 0) continue; tested_point = true; S2MaxDistancePointTarget target(shape->chain_edge(c, 0).v0); if (!target.VisitContainingShapes(query_index, visitor)) { return false; } } if (!tested_point) { // Special case to handle full polygons. S2Shape::ReferencePoint ref = shape->GetReferencePoint(); if (!ref.contained) continue; S2MaxDistancePointTarget target(ref.point); if (!target.VisitContainingShapes(query_index, visitor)) { return false; } } } return true; } s2/src/s2/s2shapeutil_get_reference_point.h0000644000176200001440000000407114530411473020417 0ustar liggesusers// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2SHAPEUTIL_GET_REFERENCE_POINT_H_ #define S2_S2SHAPEUTIL_GET_REFERENCE_POINT_H_ #include "s2/s2shape_index.h" namespace s2shapeutil { // This is a helper function for implementing S2Shape::GetReferencePoint(). // // Given a shape consisting of closed polygonal loops, the interior of the // shape is defined as the region to the left of all edges (which must be // oriented consistently). This function then chooses an arbitrary point and // returns true if that point is contained by the shape. // // Unlike S2Loop and S2Polygon, this method allows duplicate vertices and // edges, which requires some extra care with definitions. The rule that we // apply is that an edge and its reverse edge "cancel" each other: the result // is the same as if that edge pair were not present. Therefore shapes that // consist only of degenerate loop(s) are either empty or full; by convention, // the shape is considered full if and only if it contains an empty loop (see // S2LaxPolygonShape for details). // // Determining whether a loop on the sphere contains a point is harder than // the corresponding problem in 2D plane geometry. It cannot be implemented // just by counting edge crossings because there is no such thing as a "point // at infinity" that is guaranteed to be outside the loop. S2Shape::ReferencePoint GetReferencePoint(const S2Shape& shape); } // namespace s2shapeutil #endif // S2_S2SHAPEUTIL_GET_REFERENCE_POINT_H_ s2/src/s2/s2wedge_relations.h0000644000176200001440000000472014530411473015507 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // Defines functions for determining the relationship between two angles // ("wedges") that share a common vertex. #ifndef S2_S2WEDGE_RELATIONS_H_ #define S2_S2WEDGE_RELATIONS_H_ #include "s2/s2point.h" namespace S2 { // Given an edge chain (x0, x1, x2), the wedge at x1 is the region to the // left of the edges. More precisely, it is the set of all rays from x1x0 // (inclusive) to x1x2 (exclusive) in the *clockwise* direction. // // The following functions compare two *non-empty* wedges that share the // same middle vertex: A=(a0, ab1, a2) and B=(b0, ab1, b2). // Detailed relation from one wedge A to another wedge B. enum WedgeRelation { WEDGE_EQUALS, // A and B are equal. WEDGE_PROPERLY_CONTAINS, // A is a strict superset of B. WEDGE_IS_PROPERLY_CONTAINED, // A is a strict subset of B. WEDGE_PROPERLY_OVERLAPS, // A-B, B-A, and A intersect B are non-empty. WEDGE_IS_DISJOINT, // A and B are disjoint. }; // Returns the relation from wedge A to B. // REQUIRES: A and B are non-empty. WedgeRelation GetWedgeRelation( const S2Point& a0, const S2Point& ab1, const S2Point& a2, const S2Point& b0, const S2Point& b2); // Returns true if wedge A contains wedge B. Equivalent to but faster than // GetWedgeRelation() == WEDGE_PROPERLY_CONTAINS || WEDGE_EQUALS. // REQUIRES: A and B are non-empty. bool WedgeContains(const S2Point& a0, const S2Point& ab1, const S2Point& a2, const S2Point& b0, const S2Point& b2); // Returns true if wedge A intersects wedge B. Equivalent to but faster // than GetWedgeRelation() != WEDGE_IS_DISJOINT. // REQUIRES: A and B are non-empty. bool WedgeIntersects(const S2Point& a0, const S2Point& ab1, const S2Point& a2, const S2Point& b0, const S2Point& b2); } // namespace S2 #endif // S2_S2WEDGE_RELATIONS_H_ s2/src/s2/s2cell.h0000644000176200001440000002307214530411473013254 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2CELL_H_ #define S2_S2CELL_H_ #include "s2/base/integral_types.h" #include "s2/base/logging.h" #include "s2/_fp_contract_off.h" #include "s2/r2rect.h" #include "s2/s1chord_angle.h" #include "s2/s2cell_id.h" #include "s2/s2region.h" #include "s2/util/math/vector.h" class Decoder; class Encoder; class S2Cap; class S2LatLng; class S2LatLngRect; // An S2Cell is an S2Region object that represents a cell. Unlike S2CellIds, // it supports efficient containment and intersection tests. However, it is // also a more expensive representation (currently 48 bytes rather than 8). // This class is intended to be copied by value as desired. It uses // the default copy constructor and assignment operator, however it is // not a "plain old datatype" (POD) because it has virtual functions. class S2Cell final : public S2Region { public: // The default constructor is required in order to use freelists. // Cells should otherwise always be constructed explicitly. S2Cell() {} // An S2Cell always corresponds to a particular S2CellId. The other // constructors are just convenience methods. explicit S2Cell(S2CellId id); // Convenience constructors. The S2LatLng must be normalized. explicit S2Cell(const S2Point& p) : S2Cell(S2CellId(p)) {} explicit S2Cell(const S2LatLng& ll) : S2Cell(S2CellId(ll)) {} // Returns the cell corresponding to the given S2 cube face. static S2Cell FromFace(int face) { return S2Cell(S2CellId::FromFace(face)); } // Returns a cell given its face (range 0..5), Hilbert curve position within // that face (an unsigned integer with S2CellId::kPosBits bits), and level // (range 0..kMaxLevel). The given position will be modified to correspond // to the Hilbert curve position at the center of the returned cell. This // is a static function rather than a constructor in order to indicate what // the arguments represent. static S2Cell FromFacePosLevel(int face, uint64 pos, int level) { return S2Cell(S2CellId::FromFacePosLevel(face, pos, level)); } S2CellId id() const { return id_; } int face() const { return face_; } int level() const { return level_; } int orientation() const { return orientation_; } bool is_leaf() const { return level_ == S2CellId::kMaxLevel; } // These are equivalent to the S2CellId methods, but have a more efficient // implementation since the level has been precomputed. int GetSizeIJ() const; double GetSizeST() const; // Returns the k-th vertex of the cell (k = 0,1,2,3). Vertices are returned // in CCW order (lower left, lower right, upper right, upper left in the UV // plane). The points returned by GetVertexRaw are not normalized. // For convenience, the argument is reduced modulo 4 to the range [0..3]. S2Point GetVertex(int k) const { return GetVertexRaw(k).Normalize(); } S2Point GetVertexRaw(int k) const; // Returns the inward-facing normal of the great circle passing through the // edge from vertex k to vertex k+1 (mod 4). The normals returned by // GetEdgeRaw are not necessarily unit length. For convenience, the // argument is reduced modulo 4 to the range [0..3]. S2Point GetEdge(int k) const { return GetEdgeRaw(k).Normalize(); } S2Point GetEdgeRaw(int k) const; // If this is not a leaf cell, sets children[0..3] to the four children of // this cell (in traversal order) and return true. Otherwise returns false. // This method is equivalent to the following: // // for (pos=0, id=child_begin(); id != child_end(); id = id.next(), ++pos) // children[pos] = S2Cell(id); // // except that it is more than two times faster. bool Subdivide(S2Cell children[4]) const; // Returns the direction vector corresponding to the center in (s,t)-space of // the given cell. This is the point at which the cell is divided into four // subcells; it is not necessarily the centroid of the cell in (u,v)-space // or (x,y,z)-space. The point returned by GetCenterRaw is not necessarily // unit length. S2Point GetCenter() const { return GetCenterRaw().Normalize(); } S2Point GetCenterRaw() const; // Returns the average area for cells at the given level. static double AverageArea(int level); // Returns the average area of cells at this level in steradians. This is // accurate to within a factor of 1.7 (for S2_QUADRATIC_PROJECTION) and is // extremely cheap to compute. double AverageArea() const { return AverageArea(level_); } // Returns the approximate area of this cell in steradians. This method is // accurate to within 3% percent for all cell sizes and accurate to within // 0.1% for cells at level 5 or higher (i.e. squares 350km to a side or // smaller on the Earth's surface). It is moderately cheap to compute. double ApproxArea() const; // Returns the area of this cell as accurately as possible. This method is // more expensive but it is accurate to 6 digits of precision even for leaf // cells (whose area is approximately 1e-18). double ExactArea() const; // Returns the bounds of this cell in (u,v)-space. R2Rect GetBoundUV() const { return uv_; } // Returns the distance from the cell to the given point. Returns zero if // the point is inside the cell. S1ChordAngle GetDistance(const S2Point& target) const; // Return the distance from the cell boundary to the given point. S1ChordAngle GetBoundaryDistance(const S2Point& target) const; // Returns the maximum distance from the cell (including its interior) to the // given point. S1ChordAngle GetMaxDistance(const S2Point& target) const; // Returns the minimum distance from the cell to the given edge AB. Returns // zero if the edge intersects the cell interior. S1ChordAngle GetDistance(const S2Point& a, const S2Point& b) const; // Returns the maximum distance from the cell (including its interior) to the // given edge AB. S1ChordAngle GetMaxDistance(const S2Point& a, const S2Point& b) const; // Returns the distance from the cell to the given cell. Returns zero if // one cell contains the other. S1ChordAngle GetDistance(const S2Cell& target) const; // Returns the maximum distance from the cell (including its interior) to the // given target cell. S1ChordAngle GetMaxDistance(const S2Cell& target) const; //////////////////////////////////////////////////////////////////////// // S2Region interface (see s2region.h for details): S2Cell* Clone() const override; S2Cap GetCapBound() const override; S2LatLngRect GetRectBound() const override; bool Contains(const S2Cell& cell) const override; bool MayIntersect(const S2Cell& cell) const override; // Returns true if the cell contains the given point "p". Note that unlike // S2Loop/S2Polygon, S2Cells are considered to be closed sets. This means // that points along an S2Cell edge (or at a vertex) belong to the adjacent // cell(s) as well. // // If instead you want every point to be contained by exactly one S2Cell, // you will need to convert the S2Cells to S2Loops (which implement point // containment this way). // // The point "p" does not need to be normalized. bool Contains(const S2Point& p) const override; // Appends a serialized representation of the S2Cell to "encoder". // // REQUIRES: "encoder" uses the default constructor, so that its buffer // can be enlarged as necessary by calling Ensure(int). void Encode(Encoder* const encoder) const; // Decodes an S2Cell encoded with Encode(). Returns true on success. bool Decode(Decoder* const decoder); private: // Returns the latitude or longitude of the cell vertex given by (i,j), // where "i" and "j" are either 0 or 1. double GetLatitude(int i, int j) const; double GetLongitude(int i, int j) const; S1ChordAngle VertexChordDist(const S2Point& p, int i, int j) const; bool UEdgeIsClosest(const S2Point& target, int v_end) const; bool VEdgeIsClosest(const S2Point& target, int u_end) const; // Returns the distance from the given point to the interior of the cell if // "to_interior" is true, and to the boundary of the cell otherwise. S1ChordAngle GetDistanceInternal(const S2Point& target_xyz, bool to_interior) const; // This structure occupies 44 bytes plus one pointer for the vtable. int8 face_; int8 level_; int8 orientation_; S2CellId id_; R2Rect uv_; }; inline bool operator==(const S2Cell& x, const S2Cell& y) { return x.id() == y.id(); } inline bool operator!=(const S2Cell& x, const S2Cell& y) { return x.id() != y.id(); } inline bool operator<(const S2Cell& x, const S2Cell& y) { return x.id() < y.id(); } inline bool operator>(const S2Cell& x, const S2Cell& y) { return x.id() > y.id(); } inline bool operator<=(const S2Cell& x, const S2Cell& y) { return x.id() <= y.id(); } inline bool operator>=(const S2Cell& x, const S2Cell& y) { return x.id() >= y.id(); } inline int S2Cell::GetSizeIJ() const { return S2CellId::GetSizeIJ(level()); } inline double S2Cell::GetSizeST() const { return S2CellId::GetSizeST(level()); } #endif // S2_S2CELL_H_ s2/src/s2/s2region_intersection.h0000644000176200001440000000522314530411473016404 0ustar liggesusers// Copyright 2006 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef S2_S2REGION_INTERSECTION_H_ #define S2_S2REGION_INTERSECTION_H_ #include #include #include "s2/base/logging.h" #include "s2/_fp_contract_off.h" #include "s2/s2region.h" #include "absl/base/macros.h" class Decoder; class Encoder; class S2Cap; class S2Cell; class S2LatLngRect; // An S2RegionIntersection represents the intersection of a set of regions. // It is convenient for computing a covering of the intersection of a set of // regions. class S2RegionIntersection final : public S2Region { public: // Creates an empty intersection that should be initialized by calling Init(). // Note: an intersection of no regions covers the entire sphere. S2RegionIntersection() = default; // Create a region representing the intersection of the given regions. explicit S2RegionIntersection(std::vector> regions); ~S2RegionIntersection() override = default; // Initialize region by taking ownership of the given regions. void Init(std::vector> regions); // Releases ownership of the regions of this intersection and returns them, // leaving this region empty. std::vector> Release(); // Accessor methods. int num_regions() const { return regions_.size(); } const S2Region* region(int i) const { return regions_[i].get(); } //////////////////////////////////////////////////////////////////////// // S2Region interface (see s2region.h for details): S2RegionIntersection* Clone() const override; S2Cap GetCapBound() const override; S2LatLngRect GetRectBound() const override; bool Contains(const S2Point& p) const override; bool Contains(const S2Cell& cell) const override; bool MayIntersect(const S2Cell& cell) const override; private: // Internal copy constructor used only by Clone() that makes a deep copy of // its argument. S2RegionIntersection(const S2RegionIntersection& src); std::vector> regions_; void operator=(const S2RegionIntersection&) = delete; }; #endif // S2_S2REGION_INTERSECTION_H_ s2/src/s2/id_set_lexicon.h0000644000176200001440000001527314530411473015064 0ustar liggesusers// Copyright 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_ID_SET_LEXICON_H_ #define S2_ID_SET_LEXICON_H_ #include #include #include "s2/base/integral_types.h" #include "s2/base/logging.h" #include "s2/sequence_lexicon.h" // IdSetLexicon is a class for compactly representing sets of non-negative // integers such as array indices ("id sets"). It is especially suitable when // either (1) there are many duplicate sets, or (2) there are many singleton // or empty sets. See also ValueLexicon and SequenceLexicon. // // Each distinct id set is mapped to a 32-bit integer. Empty and singleton // sets take up no additional space whatsoever; the set itself is represented // by the unique id assigned to the set. Sets of size 2 or more occupy about // 11 bytes per set plus 4 bytes per element (as compared to 24 bytes per set // plus 4 bytes per element for std::vector). Duplicate sets are // automatically eliminated. Note also that id sets are referred to using // 32-bit integers rather than 64-bit pointers. // // This class is especially useful in conjunction with ValueLexicon. For // example, suppose that you want to label objects with a set of strings. You // could use a ValueLexicon to map the strings to "label ids" (32-bit // integers), and then use IdSetLexicon to map each set of labels to a "label // set id". Each reference to that label set then takes up only 4 bytes. // // Example usage: // // ValueLexicon labels_; // IdSetLexicon label_sets_; // // int32 GetLabelSet(const vector& label_strings) { // vector label_ids; // for (const auto& str : label_strings) { // label_ids.push_back(labels_.Add(str)); // } // return label_sets_.Add(label_ids); // } // // int label_set_id = GetLabelSet(...); // for (auto id : label_sets_.id_set(label_set_id)) { // S2_LOG(INFO) << id; // } // // This class is similar to SequenceLexicon, except: // // 1. Empty and singleton sets are represented implicitly; they use no space. // 2. Sets are represented rather than sequences; the ordering of values is // not important and duplicates are removed. // 3. The values must be 32-bit non-negative integers (only). class IdSetLexicon { public: IdSetLexicon(); ~IdSetLexicon(); // IdSetLexicon is movable and copyable. IdSetLexicon(const IdSetLexicon&); IdSetLexicon& operator=(const IdSetLexicon&); IdSetLexicon(IdSetLexicon&&); IdSetLexicon& operator=(IdSetLexicon&&); // Clears all data from the lexicon. void Clear(); // Add the given set of integers to the lexicon if it is not already // present, and return the unique id for this set. "begin" and "end" are // forward iterators over a sequence of values that can be converted to // non-negative 32-bit integers. The values are automatically sorted and // duplicates are removed. Returns a signed integer representing this set. // // REQUIRES: All values in [begin, end) are non-negative 32-bit integers. template int32 Add(FwdIterator begin, FwdIterator end); // Add the given set of integers to the lexicon if it is not already // present, and return the unique id for this set. This is a convenience // method equivalent to Add(std::begin(container), std::end(container)). template int32 Add(const Container& container); // Convenience method that returns the unique id for a singleton set. // Note that because singleton sets take up no space, this method is // const. Equivalent to calling Add(&id, &id + 1). int32 AddSingleton(int32 id) const; // Convenience method that returns the unique id for the empty set. Note // that because the empty set takes up no space and has a fixed id, this // method is static. Equivalent to calling Add() with an empty container. static int32 EmptySetId(); // Iterator type; please treat this as an opaque forward iterator. using Iterator = const int32*; // This class represents a set of integers stored in the IdSetLexicon. class IdSet { public: Iterator begin() const; Iterator end() const; size_t size() const; private: friend class IdSetLexicon; IdSet(); IdSet(Iterator begin, Iterator end); explicit IdSet(int32 singleton_id); Iterator begin_, end_; int32 singleton_id_; }; // Return the set of integers corresponding to an id returned by Add(). IdSet id_set(int32 set_id) const; private: // Choose kEmptySetId to be the last id that will ever be generated. // (Non-negative ids are reserved for singleton sets.) static const int32 kEmptySetId = std::numeric_limits::min(); int32 AddInternal(std::vector* ids); SequenceLexicon id_sets_; std::vector tmp_; // temporary storage used during Add() }; ////////////////// Implementation details follow //////////////////// inline IdSetLexicon::Iterator IdSetLexicon::IdSet::begin() const { return begin_; } inline IdSetLexicon::Iterator IdSetLexicon::IdSet::end() const { return end_; } inline size_t IdSetLexicon::IdSet::size() const { return end_ - begin_; } inline IdSetLexicon::IdSet::IdSet() : begin_(&singleton_id_), end_(begin_) { } inline IdSetLexicon::IdSet::IdSet(Iterator begin, Iterator end) : begin_(begin), end_(end) { } inline IdSetLexicon::IdSet::IdSet(int32 singleton_id) : begin_(&singleton_id_), end_(&singleton_id_ + 1), singleton_id_(singleton_id) { } inline int32 IdSetLexicon::AddSingleton(int32 id) const { S2_DCHECK_GE(id, 0); S2_DCHECK_LE(id, std::numeric_limits::max()); // Singleton sets are represented by their element. return id; } /*static*/ inline int32 IdSetLexicon::EmptySetId() { return kEmptySetId; } template int32 IdSetLexicon::Add(FwdIterator begin, FwdIterator end) { tmp_.clear(); for (; begin != end; ++begin) { S2_DCHECK_GE(*begin, 0); S2_DCHECK_LE(*begin, std::numeric_limits::max()); tmp_.push_back(*begin); } return AddInternal(&tmp_); } template int32 IdSetLexicon::Add(const Container& container) { return Add(std::begin(container), std::end(container)); } #endif // S2_ID_SET_LEXICON_H_ s2/src/s2/s2error.h0000644000176200001440000001244014530411473013463 0ustar liggesusers// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // S2Error is a simple class consisting of an error code and a human-readable // error message. #ifndef S2_S2ERROR_H_ #define S2_S2ERROR_H_ #include #include #include #include "s2/base/port.h" // This class is intended to be copied by value as desired. It uses // the default copy constructor and assignment operator. class S2Error { public: enum Code { OK = 0, // No error. //////////////////////////////////////////////////////////////////// // Generic errors, not specific to geometric objects: UNKNOWN = 1000, // Unknown error. UNIMPLEMENTED = 1001, // Operation is not implemented. OUT_OF_RANGE = 1002, // Argument is out of range. INVALID_ARGUMENT = 1003, // Invalid argument (other than a range error). FAILED_PRECONDITION = 1004, // Object is not in the required state. INTERNAL = 1005, // An internal invariant has failed. DATA_LOSS = 1006, // Data loss or corruption. RESOURCE_EXHAUSTED = 1007, // A resource has been exhausted. //////////////////////////////////////////////////////////////////// // Error codes in the following range can be defined by clients: USER_DEFINED_START = 1000000, USER_DEFINED_END = 9999999, //////////////////////////////////////////////////////////////////// // Errors that apply to more than one type of geometry: NOT_UNIT_LENGTH = 1, // Vertex is not unit length. DUPLICATE_VERTICES = 2, // There are two identical vertices. ANTIPODAL_VERTICES = 3, // There are two antipodal vertices. //////////////////////////////////////////////////////////////////// // S2Loop errors: LOOP_NOT_ENOUGH_VERTICES = 100, // Loop with fewer than 3 vertices. LOOP_SELF_INTERSECTION = 101, // Loop has a self-intersection. //////////////////////////////////////////////////////////////////// // S2Polygon errors: POLYGON_LOOPS_SHARE_EDGE = 200, // Two polygon loops share an edge. POLYGON_LOOPS_CROSS = 201, // Two polygon loops cross. POLYGON_EMPTY_LOOP = 202, // Polygon has an empty loop. POLYGON_EXCESS_FULL_LOOP = 203, // Non-full polygon has a full loop. // InitOriented() was called and detected inconsistent loop orientations. POLYGON_INCONSISTENT_LOOP_ORIENTATIONS = 204, // Loop depths don't correspond to any valid nesting hierarchy. POLYGON_INVALID_LOOP_DEPTH = 205, // Actual polygon nesting does not correspond to the nesting hierarchy // encoded by the loop depths. POLYGON_INVALID_LOOP_NESTING = 206, //////////////////////////////////////////////////////////////////// // S2Builder errors: // The S2Builder snap function moved a vertex by more than the specified // snap radius. BUILDER_SNAP_RADIUS_TOO_SMALL = 300, // S2Builder expected all edges to have siblings (as specified by // S2Builder::GraphOptions::SiblingPairs::REQUIRE), but some were missing. BUILDER_MISSING_EXPECTED_SIBLING_EDGES = 301, // S2Builder found an unexpected degenerate edge. For example, // Graph::GetLeftTurnMap() does not support degenerate edges. BUILDER_UNEXPECTED_DEGENERATE_EDGE = 302, // S2Builder found a vertex with (indegree != outdegree), which means // that the given edges cannot be assembled into loops. BUILDER_EDGES_DO_NOT_FORM_LOOPS = 303, // The edges provided to S2Builder cannot be assembled into a polyline. BUILDER_EDGES_DO_NOT_FORM_POLYLINE = 304, // There was an attempt to assemble a polygon from degenerate geometry // without having specified a predicate to decide whether the output is // the empty polygon (containing no points) or the full polygon // (containing all points). BUILDER_IS_FULL_PREDICATE_NOT_SPECIFIED = 305, }; S2Error() : code_(OK), text_() {} // Set the error to the given code and printf-style message. Note that you // can prepend text to an existing error by calling Init() more than once: // // error->Init(error->code(), "Loop %d: %s", j, error->text().c_str()); void Init(Code code, const char* format, ...) ABSL_PRINTF_ATTRIBUTE(3, 4); bool ok() const { return code_ == OK; } Code code() const { return code_; } std::string text() const { return text_; } // Clear the error to contain the OK code and no error message. void Clear(); private: Code code_; std::string text_; }; ////////////////// Implementation details follow //////////////////// inline std::ostream& operator<<(std::ostream& os, const S2Error& error) { return os << error.text(); } inline void S2Error::Clear() { code_ = OK; text_.clear(); } #endif // S2_S2ERROR_H_ s2/src/s2/s2closest_edge_query.h0000644000176200001440000004116514530411473016225 0ustar liggesusers// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2CLOSEST_EDGE_QUERY_H_ #define S2_S2CLOSEST_EDGE_QUERY_H_ #include #include #include #include #include "s2/base/logging.h" #include "absl/base/macros.h" #include "absl/container/inlined_vector.h" #include "s2/_fp_contract_off.h" #include "s2/s1angle.h" #include "s2/s1chord_angle.h" #include "s2/s2cell.h" #include "s2/s2cell_id.h" #include "s2/s2closest_edge_query_base.h" #include "s2/s2edge_distances.h" #include "s2/s2min_distance_targets.h" #include "s2/s2shape_index.h" // S2ClosestEdgeQuery is a helper class for finding the closest edge(s) to a // given point, edge, S2Cell, or geometry collection. For example, given a // set of polylines, the following code efficiently finds the closest 5 edges // to a query point: // // void Test(const vector& polylines, const S2Point& point) { // MutableS2ShapeIndex index; // for (S2Polyline* polyline : polylines) { // index.Add(new S2Polyline::Shape(polyline)); // } // S2ClosestEdgeQuery query(&index); // query.mutable_options()->set_max_results(5); // S2ClosestEdgeQuery::PointTarget target(point); // for (const auto& result : query.FindClosestEdges(&target)) { // // The Result struct contains the following fields: // // "distance" is the distance to the edge. // // "shape_id" identifies the S2Shape containing the edge. // // "edge_id" identifies the edge with the given shape. // // The following convenience methods may also be useful: // // query.GetEdge(result) returns the endpoints of the edge. // // query.Project(point, result) computes the closest point on the // // result edge to the given target point. // int polyline_index = result.shape_id; // int edge_index = result.edge_id; // S1ChordAngle distance = result.distance; // Use ToAngle() for S1Angle. // S2Shape::Edge edge = query.GetEdge(result); // S2Point closest_point = query.Project(point, result); // } // } // // You can find either the k closest edges, or all edges within a given // radius, or both (i.e., the k closest edges up to a given maximum radius). // E.g. to find all the edges within 5 kilometers, call // // query.mutable_options()->set_max_distance( // S2Earth::ToAngle(util::units::Kilometers(5))); // // By default *all* edges are returned, so you should always specify either // max_results() or max_distance() or both. There is also a FindClosestEdge() // convenience method that returns only the closest edge. // // Note that by default, distances are measured to the boundary and interior // of polygons. For example, if a point is inside a polygon then its distance // is zero. To change this behavior, call set_include_interiors(false). // // If you only need to test whether the distance is above or below a given // threshold (e.g., 10 km), you can use the IsDistanceLess() method. This is // much faster than actually calculating the distance with FindClosestEdge(), // since the implementation can stop as soon as it can prove that the minimum // distance is either above or below the threshold. // // To find the closest edges to a query edge rather than a point, use: // // S2ClosestEdgeQuery::EdgeTarget target(v0, v1); // query.FindClosestEdges(&target); // // Similarly you can find the closest edges to an S2Cell by using an // S2ClosestEdgeQuery::CellTarget, and you can find the closest edges to an // arbitrary collection of points, polylines, and polygons by using an // S2ClosestEdgeQuery::ShapeIndexTarget. // // The implementation is designed to be fast for both simple and complex // geometric objects. class S2ClosestEdgeQuery { public: // See S2ClosestEdgeQueryBase for full documentation. // S2MinDistance is a thin wrapper around S1ChordAngle that implements the // Distance concept required by S2ClosestPointQueryBase. using Distance = S2MinDistance; using Base = S2ClosestEdgeQueryBase; // Each "Result" object represents a closest edge. It has the following // fields: // // S1ChordAngle distance; // The distance from the target to this edge. // int32 shape_id; // Identifies an indexed shape. // int32 edge_id; // Identifies an edge within the shape. using Result = Base::Result; // Options that control the set of edges returned. Note that by default // *all* edges are returned, so you will always want to set either the // max_results() option or the max_distance() option (or both). class Options : public Base::Options { public: // See S2ClosestEdgeQueryBase::Options for the full set of options. // Specifies that only edges whose distance to the target is less than // "max_distance" should be returned. // // Note that edges whose distance is exactly equal to "max_distance" are // not returned. Normally this doesn't matter, because distances are not // computed exactly in the first place, but if such edges are needed then // see set_inclusive_max_distance() below. // // DEFAULT: Distance::Infinity() void set_max_distance(S1ChordAngle max_distance); // Like set_max_distance(), except that edges whose distance is exactly // equal to "max_distance" are also returned. Equivalent to calling // set_max_distance(max_distance.Successor()). void set_inclusive_max_distance(S1ChordAngle max_distance); // Like set_inclusive_max_distance(), except that "max_distance" is also // increased by the maximum error in the distance calculation. This // ensures that all edges whose true distance is less than or equal to // "max_distance" will be returned (along with some edges whose true // distance is slightly greater). // // Algorithms that need to do exact distance comparisons can use this // option to find a set of candidate edges that can then be filtered // further (e.g., using s2pred::CompareDistance). void set_conservative_max_distance(S1ChordAngle max_distance); // Versions of set_max_distance that take an S1Angle argument. (Note that // these functions require a conversion, and that the S1ChordAngle versions // are preferred.) void set_max_distance(S1Angle max_distance); void set_inclusive_max_distance(S1Angle max_distance); void set_conservative_max_distance(S1Angle max_distance); // See S2ClosestEdgeQueryBase::Options for documentation. using Base::Options::set_max_error; // S1Chordangle version void set_max_error(S1Angle max_error); // S1Angle version // Inherited options (see s2closest_edge_query_base.h for details): using Base::Options::set_max_results; using Base::Options::set_include_interiors; using Base::Options::set_use_brute_force; }; // "Target" represents the geometry to which the distance is measured. // There are subtypes for measuring the distance to a point, an edge, an // S2Cell, or an S2ShapeIndex (an arbitrary collection of geometry). using Target = S2MinDistanceTarget; // Target subtype that computes the closest distance to a point. class PointTarget final : public S2MinDistancePointTarget { public: explicit PointTarget(const S2Point& point); int max_brute_force_index_size() const override; }; // Target subtype that computes the closest distance to an edge. class EdgeTarget final : public S2MinDistanceEdgeTarget { public: explicit EdgeTarget(const S2Point& a, const S2Point& b); int max_brute_force_index_size() const override; }; // Target subtype that computes the closest distance to an S2Cell // (including the interior of the cell). class CellTarget final : public S2MinDistanceCellTarget { public: explicit CellTarget(const S2Cell& cell); int max_brute_force_index_size() const override; }; // Target subtype that computes the closest distance to an S2ShapeIndex // (an arbitrary collection of points, polylines, and/or polygons). // // By default, distances are measured to the boundary and interior of // polygons in the S2ShapeIndex rather than to polygon boundaries only. // If you wish to change this behavior, you may call // // target.set_include_interiors(false); // // (see S2MinDistanceShapeIndexTarget for details). class ShapeIndexTarget final : public S2MinDistanceShapeIndexTarget { public: explicit ShapeIndexTarget(const S2ShapeIndex* index); int max_brute_force_index_size() const override; }; // Convenience constructor that calls Init(). Options may be specified here // or changed at any time using the mutable_options() accessor method. explicit S2ClosestEdgeQuery(const S2ShapeIndex* index, const Options& options = Options()); // Default constructor; requires Init() to be called. S2ClosestEdgeQuery(); ~S2ClosestEdgeQuery(); // Initializes the query. Options may be specified here or changed at any // time using the mutable_options() accessor method. // // REQUIRES: "index" must persist for the lifetime of this object. // REQUIRES: ReInit() must be called if "index" is modified. void Init(const S2ShapeIndex* index, const Options& options = Options()); // Reinitializes the query. This method must be called whenever the // underlying S2ShapeIndex is modified. void ReInit(); // Returns a reference to the underlying S2ShapeIndex. const S2ShapeIndex& index() const; // Returns the query options. Options can be modified between queries. const Options& options() const; Options* mutable_options(); // Returns the closest edges to the given target that satisfy the current // options. This method may be called multiple times. // // Note that if options().include_interiors() is true, the result vector may // include some entries with edge_id == -1. This indicates that the target // intersects the indexed polygon with the given shape_id. std::vector FindClosestEdges(Target* target); // This version can be more efficient when this method is called many times, // since it does not require allocating a new vector on each call. void FindClosestEdges(Target* target, std::vector* results); //////////////////////// Convenience Methods //////////////////////// // Returns the closest edge to the target. If no edge satisfies the search // criteria, then the Result object will have distance == Infinity(), // is_empty() == true, and shape_id == edge_id == -1. // // Note that if options.include_interiors() is true, edge_id == -1 is also // used to indicate that the target intersects an indexed polygon (but in // that case distance == Zero() and shape_id >= 0). Result FindClosestEdge(Target* target); // Returns the minimum distance to the target. If the index or target is // empty, returns S1ChordAngle::Infinity(). // // Use IsDistanceLess() if you only want to compare the distance against a // threshold value, since it is often much faster. S1ChordAngle GetDistance(Target* target); // Returns true if the distance to "target" is less than "limit". // // This method is usually much faster than GetDistance(), since it is much // less work to determine whether the minimum distance is above or below a // threshold than it is to calculate the actual minimum distance. bool IsDistanceLess(Target* target, S1ChordAngle limit); // Like IsDistanceLess(), but also returns true if the distance to "target" // is exactly equal to "limit". bool IsDistanceLessOrEqual(Target* target, S1ChordAngle limit); // Like IsDistanceLessOrEqual(), except that "limit" is increased by the // maximum error in the distance calculation. This ensures that this // function returns true whenever the true, exact distance is less than // or equal to "limit". // // For example, suppose that we want to test whether two geometries might // intersect each other after they are snapped together using S2Builder // (using the IdentitySnapFunction with a given "snap_radius"). Since // S2Builder uses exact distance predicates (s2predicates.h), we need to // measure the distance between the two geometries conservatively. If the // distance is definitely greater than "snap_radius", then the geometries // are guaranteed to not intersect after snapping. bool IsConservativeDistanceLessOrEqual(Target* target, S1ChordAngle limit); // Returns the endpoints of the given result edge. // // CAVEAT: If options().include_interiors() is true, then clients must not // pass this method any Result objects that correspond to shape interiors, // i.e. those where result.edge_id < 0. // // REQUIRES: result.edge_id >= 0 S2Shape::Edge GetEdge(const Result& result) const; // Returns the point on given result edge that is closest to "point". S2Point Project(const S2Point& point, const Result& result) const; private: Options options_; Base base_; S2ClosestEdgeQuery(const S2ClosestEdgeQuery&) = delete; void operator=(const S2ClosestEdgeQuery&) = delete; }; ////////////////// Implementation details follow //////////////////// inline void S2ClosestEdgeQuery::Options::set_max_distance( S1ChordAngle max_distance) { Base::Options::set_max_distance(Distance(max_distance)); } inline void S2ClosestEdgeQuery::Options::set_max_distance( S1Angle max_distance) { Base::Options::set_max_distance(Distance(max_distance)); } inline void S2ClosestEdgeQuery::Options::set_inclusive_max_distance( S1ChordAngle max_distance) { set_max_distance(max_distance.Successor()); } inline void S2ClosestEdgeQuery::Options::set_inclusive_max_distance( S1Angle max_distance) { set_inclusive_max_distance(S1ChordAngle(max_distance)); } inline void S2ClosestEdgeQuery::Options::set_max_error(S1Angle max_error) { Base::Options::set_max_error(S1ChordAngle(max_error)); } inline S2ClosestEdgeQuery::PointTarget::PointTarget(const S2Point& point) : S2MinDistancePointTarget(point) { } inline S2ClosestEdgeQuery::EdgeTarget::EdgeTarget(const S2Point& a, const S2Point& b) : S2MinDistanceEdgeTarget(a, b) { } inline S2ClosestEdgeQuery::CellTarget::CellTarget(const S2Cell& cell) : S2MinDistanceCellTarget(cell) { } inline S2ClosestEdgeQuery::ShapeIndexTarget::ShapeIndexTarget( const S2ShapeIndex* index) : S2MinDistanceShapeIndexTarget(index) { } inline S2ClosestEdgeQuery::S2ClosestEdgeQuery(const S2ShapeIndex* index, const Options& options) { Init(index, options); } inline void S2ClosestEdgeQuery::Init(const S2ShapeIndex* index, const Options& options) { options_ = options; base_.Init(index); } inline void S2ClosestEdgeQuery::ReInit() { base_.ReInit(); } inline const S2ShapeIndex& S2ClosestEdgeQuery::index() const { return base_.index(); } inline const S2ClosestEdgeQuery::Options& S2ClosestEdgeQuery::options() const { return options_; } inline S2ClosestEdgeQuery::Options* S2ClosestEdgeQuery::mutable_options() { return &options_; } inline std::vector S2ClosestEdgeQuery::FindClosestEdges(Target* target) { return base_.FindClosestEdges(target, options_); } inline void S2ClosestEdgeQuery::FindClosestEdges(Target* target, std::vector* results) { base_.FindClosestEdges(target, options_, results); } inline S2ClosestEdgeQuery::Result S2ClosestEdgeQuery::FindClosestEdge( Target* target) { static_assert(sizeof(Options) <= 32, "Consider not copying Options here"); Options tmp_options = options_; tmp_options.set_max_results(1); return base_.FindClosestEdge(target, tmp_options); } inline S1ChordAngle S2ClosestEdgeQuery::GetDistance(Target* target) { return FindClosestEdge(target).distance(); } inline S2Shape::Edge S2ClosestEdgeQuery::GetEdge(const Result& result) const { return index().shape(result.shape_id())->edge(result.edge_id()); } inline S2Point S2ClosestEdgeQuery::Project(const S2Point& point, const Result& result) const { if (result.edge_id() < 0) return point; auto edge = GetEdge(result); return S2::Project(point, edge.v0, edge.v1); } #endif // S2_S2CLOSEST_EDGE_QUERY_H_ s2/src/s2/s2furthest_edge_query.cc0000644000176200001440000001064614530411473016553 0ustar liggesusers// Copyright Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "s2/s2furthest_edge_query.h" #include #include "s2/s2edge_distances.h" void S2FurthestEdgeQuery::Options::set_conservative_min_distance( S1ChordAngle min_distance) { set_max_distance(Distance(min_distance.PlusError( -S2::GetUpdateMinDistanceMaxError(min_distance)).Predecessor())); } void S2FurthestEdgeQuery::Options::set_conservative_min_distance( S1Angle min_distance) { set_conservative_min_distance(S1ChordAngle(min_distance)); } // See s2closest_edge_query.cc for justifications of // max_brute_force_index_size() for that query. int S2FurthestEdgeQuery::PointTarget::max_brute_force_index_size() const { // Using BM_FindFurthest (which finds the single furthest edge), the // break-even points are approximately 100, 400, and 600 edges for point // cloud, fractal, and regular loop geometry respectively. return 300; } int S2FurthestEdgeQuery::EdgeTarget::max_brute_force_index_size() const { // Using BM_FindFurthestToEdge (which finds the single furthest edge), the // break-even points are approximately 80, 100, and 230 edges for point // cloud, fractal, and regular loop geometry respectively. return 110; } int S2FurthestEdgeQuery::CellTarget::max_brute_force_index_size() const { // Using BM_FindFurthestToCell (which finds the single furthest edge), the // break-even points are approximately 70, 100, and 170 edges for point // cloud, fractal, and regular loop geometry respectively. return 100; } int S2FurthestEdgeQuery::ShapeIndexTarget::max_brute_force_index_size() const { // For BM_FindFurthestToSameSizeAbuttingIndex (which uses two nearby indexes // with similar edge counts), the break-even points are approximately 30, // 100, and 130 edges for point cloud, fractal, and regular loop geometry // respectively. return 70; } S2FurthestEdgeQuery::S2FurthestEdgeQuery() { // Prevent inline constructor bloat by defining here. } S2FurthestEdgeQuery::~S2FurthestEdgeQuery() { // Prevent inline destructor bloat by defining here. } void S2FurthestEdgeQuery::FindFurthestEdges( Target* target, std::vector* results) { results->clear(); for (auto result : base_.FindClosestEdges(target, options_)) { results->push_back(S2FurthestEdgeQuery::Result(result)); } } S2FurthestEdgeQuery::Result S2FurthestEdgeQuery::FindFurthestEdge( Target* target) { static_assert(sizeof(Options) <= 32, "Consider not copying Options here"); Options tmp_options = options_; tmp_options.set_max_results(1); Base::Result base_result = base_.FindClosestEdge(target, tmp_options); return S2FurthestEdgeQuery::Result(base_result); } bool S2FurthestEdgeQuery::IsDistanceGreater( Target* target, S1ChordAngle limit) { static_assert(sizeof(Options) <= 32, "Consider not copying Options here"); Options tmp_options = options_; tmp_options.set_max_results(1); tmp_options.set_min_distance(limit); tmp_options.set_max_error(S1ChordAngle::Straight()); return base_.FindClosestEdge(target, tmp_options).shape_id() >= 0; } bool S2FurthestEdgeQuery::IsDistanceGreaterOrEqual( Target* target, S1ChordAngle limit) { static_assert(sizeof(Options) <= 32, "Consider not copying Options here"); Options tmp_options = options_; tmp_options.set_max_results(1); tmp_options.set_inclusive_min_distance(limit); tmp_options.set_max_error(S1ChordAngle::Straight()); return base_.FindClosestEdge(target, tmp_options).shape_id() >= 0; } bool S2FurthestEdgeQuery::IsConservativeDistanceGreaterOrEqual( Target* target, S1ChordAngle limit) { static_assert(sizeof(Options) <= 32, "Consider not copying Options here"); Options tmp_options = options_; tmp_options.set_max_results(1); tmp_options.set_conservative_min_distance(limit); tmp_options.set_max_error(S1ChordAngle::Straight()); return base_.FindClosestEdge(target, tmp_options).shape_id() >= 0; } s2/src/s2/s2max_distance_targets.h0000644000176200001440000002062314530411473016524 0ustar liggesusers// Copyright Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // This file defines a collection of classes that are useful for computing // maximum distances on the sphere. Their purpose is to allow code to be // shared among the various query classes that find remote geometry, such as // S2FurthestPointQuery and S2FurthestEdgeQuery. #ifndef S2_S2MAX_DISTANCE_TARGETS_H_ #define S2_S2MAX_DISTANCE_TARGETS_H_ #include #include "s2/_fp_contract_off.h" #include "s2/s1angle.h" #include "s2/s1chord_angle.h" #include "s2/s2cell.h" #include "s2/s2distance_target.h" #include "s2/s2edge_distances.h" #include "s2/s2shape_index.h" class S2FurthestEdgeQuery; // S2MaxDistance is a class that allows maximum distances to be computed using // a minimum distance algorithm. Specifically, S2MaxDistance(x) represents the // supplementary distance (Pi - x). This has the effect of inverting the sort // order, i.e. // // (S2MaxDistance(x) < S2MaxDistance(y)) <=> (Pi - x < Pi - y) <=> (x > y) // // All other operations are implemented similarly (using the supplementary // distance Pi - x). For example, S2MaxDistance(x) - S2MaxDistance(y) == // S2MaxDistance(x + y). class S2MaxDistance { public: using Delta = S1ChordAngle; S2MaxDistance() : distance_() {} explicit S2MaxDistance(S1ChordAngle x) : distance_(x) {} explicit operator S1ChordAngle() const { return distance_; } static S2MaxDistance Zero(); static S2MaxDistance Infinity(); static S2MaxDistance Negative(); friend bool operator==(S2MaxDistance x, S2MaxDistance y); friend bool operator<(S2MaxDistance x, S2MaxDistance y); friend S2MaxDistance operator-(S2MaxDistance x, S1ChordAngle delta); S1ChordAngle GetChordAngleBound() const; // If (dist < *this), updates *this and returns true (used internally). bool UpdateMin(const S2MaxDistance& dist); private: S1ChordAngle distance_; }; // S2MaxDistanceTarget represents a geometric object to which maximum distances // on the sphere are measured. // // Subtypes are defined below for measuring the distance to a point, an edge, // an S2Cell, or an S2ShapeIndex (an arbitrary collection of geometry). using S2MaxDistanceTarget = S2DistanceTarget; // An S2DistanceTarget subtype for computing the maximum distance to a point. class S2MaxDistancePointTarget : public S2MaxDistanceTarget { public: explicit S2MaxDistancePointTarget(const S2Point& point); S2Cap GetCapBound() final; bool UpdateMinDistance(const S2Point& p, S2MaxDistance* min_dist) final; bool UpdateMinDistance(const S2Point& v0, const S2Point& v1, S2MaxDistance* min_dist) final; bool UpdateMinDistance(const S2Cell& cell, S2MaxDistance* min_dist) final; bool VisitContainingShapes(const S2ShapeIndex& index, const ShapeVisitor& visitor) final; private: S2Point point_; }; // An S2DistanceTarget subtype for computing the maximum distance to an edge. class S2MaxDistanceEdgeTarget : public S2MaxDistanceTarget { public: explicit S2MaxDistanceEdgeTarget(const S2Point& a, const S2Point& b); S2Cap GetCapBound() final; bool UpdateMinDistance(const S2Point& p, S2MaxDistance* min_dist) final; bool UpdateMinDistance(const S2Point& v0, const S2Point& v1, S2MaxDistance* min_dist) final; bool UpdateMinDistance(const S2Cell& cell, S2MaxDistance* min_dist) final; bool VisitContainingShapes(const S2ShapeIndex& index, const ShapeVisitor& visitor) final; private: S2Point a_, b_; }; // An S2DistanceTarget subtype for computing the maximum distance to an S2Cell // (including the interior of the cell). class S2MaxDistanceCellTarget : public S2MaxDistanceTarget { public: explicit S2MaxDistanceCellTarget(const S2Cell& cell); S2Cap GetCapBound() final; bool UpdateMinDistance(const S2Point& p, S2MaxDistance* min_dist) final; bool UpdateMinDistance(const S2Point& v0, const S2Point& v1, S2MaxDistance* min_dist) final; bool UpdateMinDistance(const S2Cell& cell, S2MaxDistance* min_dist) final; bool VisitContainingShapes(const S2ShapeIndex& index, const ShapeVisitor& visitor) final; private: S2Cell cell_; }; // An S2DistanceTarget subtype for computing the maximum distance to an // S2ShapeIndex (a collection of points, polylines, and/or polygons). // // Note that ShapeIndexTarget has its own options: // // include_interiors() // - specifies that distances are measured to the boundary and interior // of polygons in the S2ShapeIndex. (If set to false, distance is // measured to the polygon boundary only.) // DEFAULT: true. // // brute_force() // - specifies that the distances should be computed by examining every // edge in the S2ShapeIndex (for testing and debugging purposes). // DEFAULT: false. // // These options are specified independently of the corresponding // S2FurthestEdgeQuery options. For example, if include_interiors is true for // a ShapeIndexTarget but false for the S2FurthestEdgeQuery where the target // is used, then distances will be measured from the boundary of one // S2ShapeIndex to the boundary and interior of the other. // class S2MaxDistanceShapeIndexTarget : public S2MaxDistanceTarget { public: explicit S2MaxDistanceShapeIndexTarget(const S2ShapeIndex* index); ~S2MaxDistanceShapeIndexTarget() override; // Specifies that distance will be measured to the boundary and interior // of polygons in the S2ShapeIndex rather than to polygon boundaries only. // // DEFAULT: true bool include_interiors() const; void set_include_interiors(bool include_interiors); // Specifies that the distances should be computed by examining every edge // in the S2ShapeIndex (for testing and debugging purposes). // // DEFAULT: false bool use_brute_force() const; void set_use_brute_force(bool use_brute_force); bool set_max_error(const S1ChordAngle& max_error) override; S2Cap GetCapBound() final; bool UpdateMinDistance(const S2Point& p, S2MaxDistance* min_dist) final; bool UpdateMinDistance(const S2Point& v0, const S2Point& v1, S2MaxDistance* min_dist) final; bool UpdateMinDistance(const S2Cell& cell, S2MaxDistance* min_dist) final; bool VisitContainingShapes(const S2ShapeIndex& query_index, const ShapeVisitor& visitor) final; private: const S2ShapeIndex* index_; std::unique_ptr query_; }; ////////////////// Implementation details follow //////////////////// inline S2MaxDistance S2MaxDistance::Zero() { return S2MaxDistance(S1ChordAngle::Straight()); } inline S2MaxDistance S2MaxDistance::Infinity() { return S2MaxDistance(S1ChordAngle::Negative()); } inline S2MaxDistance S2MaxDistance::Negative() { return S2MaxDistance(S1ChordAngle::Infinity()); } inline bool operator==(S2MaxDistance x, S2MaxDistance y) { return x.distance_ == y.distance_; } inline bool operator<(S2MaxDistance x, S2MaxDistance y) { return x.distance_ > y.distance_; } inline S2MaxDistance operator-(S2MaxDistance x, S1ChordAngle delta) { return S2MaxDistance(x.distance_ + delta); } inline S1ChordAngle S2MaxDistance::GetChordAngleBound() const { return S1ChordAngle::Straight() - distance_; } inline bool S2MaxDistance::UpdateMin(const S2MaxDistance& dist) { if (dist < *this) { *this = dist; return true; } return false; } inline S2MaxDistancePointTarget::S2MaxDistancePointTarget(const S2Point& point) : point_(point) { } inline S2MaxDistanceEdgeTarget::S2MaxDistanceEdgeTarget(const S2Point& a, const S2Point& b) : a_(a), b_(b) { a_.Normalize(); b_.Normalize(); } inline S2MaxDistanceCellTarget::S2MaxDistanceCellTarget(const S2Cell& cell) : cell_(cell) { } #endif // S2_S2MAX_DISTANCE_TARGETS_H_ s2/src/s2/s2shapeutil_get_reference_point.cc0000644000176200001440000001011614530411473020552 0ustar liggesusers// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2shapeutil_get_reference_point.h" #include #include "s2/base/logging.h" #include "s2/s2contains_vertex_query.h" using std::vector; using ReferencePoint = S2Shape::ReferencePoint; namespace s2shapeutil { // This is a helper function for GetReferencePoint() below. // // If the given vertex "vtest" is unbalanced (see definition below), sets // "result" to a ReferencePoint indicating whther "vtest" is contained and // returns true. Otherwise returns false. static bool GetReferencePointAtVertex( const S2Shape& shape, const S2Point& vtest, ReferencePoint* result) { // Let P be an unbalanced vertex. Vertex P is defined to be inside the // region if the region contains a particular direction vector starting from // P, namely the direction S2::Ortho(P). This can be calculated using // S2ContainsVertexQuery. S2ContainsVertexQuery contains_query(vtest); int n = shape.num_edges(); for (int e = 0; e < n; ++e) { auto edge = shape.edge(e); if (edge.v0 == vtest) contains_query.AddEdge(edge.v1, 1); if (edge.v1 == vtest) contains_query.AddEdge(edge.v0, -1); } int contains_sign = contains_query.ContainsSign(); if (contains_sign == 0) { return false; // There are no unmatched edges incident to this vertex. } result->point = vtest; result->contained = contains_sign > 0; return true; } // See documentation in header file. S2Shape::ReferencePoint GetReferencePoint(const S2Shape& shape) { S2_DCHECK_EQ(shape.dimension(), 2); if (shape.num_edges() == 0) { // A shape with no edges is defined to be full if and only if it // contains at least one chain. return ReferencePoint::Contained(shape.num_chains() > 0); } // Define a "matched" edge as one that can be paired with a corresponding // reversed edge. Define a vertex as "balanced" if all of its edges are // matched. In order to determine containment, we must find an unbalanced // vertex. Often every vertex is unbalanced, so we start by trying an // arbitrary vertex. auto edge = shape.edge(0); ReferencePoint result; if (GetReferencePointAtVertex(shape, edge.v0, &result)) { return result; } // That didn't work, so now we do some extra work to find an unbalanced // vertex (if any). Essentially we gather a list of edges and a list of // reversed edges, and then sort them. The first edge that appears in one // list but not the other is guaranteed to be unmatched. int n = shape.num_edges(); vector edges(n), rev_edges(n); for (int i = 0; i < n; ++i) { auto edge = shape.edge(i); edges[i] = edge; rev_edges[i] = S2Shape::Edge(edge.v1, edge.v0); } std::sort(edges.begin(), edges.end()); std::sort(rev_edges.begin(), rev_edges.end()); for (int i = 0; i < n; ++i) { if (edges[i] < rev_edges[i]) { // edges[i] is unmatched S2_CHECK(GetReferencePointAtVertex(shape, edges[i].v0, &result)); return result; } if (rev_edges[i] < edges[i]) { // rev_edges[i] is unmatched S2_CHECK(GetReferencePointAtVertex(shape, rev_edges[i].v0, &result)); return result; } } // All vertices are balanced, so this polygon is either empty or full except // for degeneracies. By convention it is defined to be full if it contains // any chain with no edges. for (int i = 0; i < shape.num_chains(); ++i) { if (shape.chain(i).length == 0) return ReferencePoint::Contained(true); } return ReferencePoint::Contained(false); } } // namespace s2shapeutil s2/src/s2/s2predicates.h0000644000176200001440000003271414530411473014463 0ustar liggesusers// Copyright 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // This class contains various predicates that are guaranteed to produce // correct, consistent results. They are also relatively efficient. This is // achieved by computing conservative error bounds and falling back to high // precision or even exact arithmetic when the result is uncertain. Such // predicates are useful in implementing robust algorithms. // // See also S2EdgeCrosser, which implements various exact // edge-crossing predicates more efficiently than can be done here. // // TODO(ericv): Add InCircleSign() (the Voronoi/Delaunay predicate). // (This is trickier than the usual textbook implementations because we want // to model S2Points as lying exactly on the mathematical unit sphere.) #ifndef S2_S2PREDICATES_H_ #define S2_S2PREDICATES_H_ #include #include #include "s2/_fp_contract_off.h" #include "s2/s1chord_angle.h" #include "s2/s2debug.h" #include "s2/s2pointutil.h" namespace s2pred { // S2EdgeUtil contains the following exact predicates that test for edge // crossings. (Usually you will want to use S2EdgeCrosser, which // implements them much more efficiently.) // // int CrossingSign(const S2Point& a0, const S2Point& a1, // const S2Point& b0, const S2Point& b1); // // bool EdgeOrVertexCrossing(const S2Point& a0, const S2Point& a1, // const S2Point& b0, const S2Point& b1); // Returns +1 if the points A, B, C are counterclockwise, -1 if the points // are clockwise, and 0 if any two points are the same. This function is // essentially like taking the sign of the determinant of ABC, except that // it has additional logic to make sure that the above properties hold even // when the three points are coplanar, and to deal with the limitations of // floating-point arithmetic. // // Sign satisfies the following conditions: // // (1) Sign(a,b,c) == 0 if and only if a == b, b == c, or c == a // (2) Sign(b,c,a) == Sign(a,b,c) for all a,b,c // (3) Sign(c,b,a) == -Sign(a,b,c) for all a,b,c // // In other words: // // (1) The result is zero if and only if two points are the same. // (2) Rotating the order of the arguments does not affect the result. // (3) Exchanging any two arguments inverts the result. // // On the other hand, note that it is not true in general that // Sign(-a,b,c) == -Sign(a,b,c), or any similar identities // involving antipodal points. int Sign(const S2Point& a, const S2Point& b, const S2Point& c); // Given 4 points on the unit sphere, return true if the edges OA, OB, and // OC are encountered in that order while sweeping CCW around the point O. // You can think of this as testing whether A <= B <= C with respect to the // CCW ordering around O that starts at A, or equivalently, whether B is // contained in the range of angles (inclusive) that starts at A and extends // CCW to C. Properties: // // (1) If OrderedCCW(a,b,c,o) && OrderedCCW(b,a,c,o), then a == b // (2) If OrderedCCW(a,b,c,o) && OrderedCCW(a,c,b,o), then b == c // (3) If OrderedCCW(a,b,c,o) && OrderedCCW(c,b,a,o), then a == b == c // (4) If a == b or b == c, then OrderedCCW(a,b,c,o) is true // (5) Otherwise if a == c, then OrderedCCW(a,b,c,o) is false bool OrderedCCW(const S2Point& a, const S2Point& b, const S2Point& c, const S2Point& o); // Returns -1, 0, or +1 according to whether AX < BX, A == B, or AX > BX // respectively. Distances are measured with respect to the positions of X, // A, and B as though they were reprojected to lie exactly on the surface of // the unit sphere. Furthermore, this method uses symbolic perturbations to // ensure that the result is non-zero whenever A != B, even when AX == BX // exactly, or even when A and B project to the same point on the sphere. // Such results are guaranteed to be self-consistent, i.e. if AB < BC and // BC < AC, then AB < AC. int CompareDistances(const S2Point& x, const S2Point& a, const S2Point& b); // Returns -1, 0, or +1 according to whether the distance XY is less than, // equal to, or greater than "r" respectively. Distances are measured with // respect the positions of all points as though they are projected to lie // exactly on the surface of the unit sphere. int CompareDistance(const S2Point& x, const S2Point& y, S1ChordAngle r); // Returns -1, 0, or +1 according to whether the distance from the point X to // the edge A is less than, equal to, or greater than "r" respectively. // Distances are measured with respect the positions of all points as though // they were projected to lie exactly on the surface of the unit sphere. // // REQUIRES: A0 and A1 do not project to antipodal points (e.g., A0 == -A1). // This requires that (A0 != C * A1) for any constant C < 0. // // NOTE(ericv): All of the predicates defined here could be extended to handle // edges consisting of antipodal points by implementing additional symbolic // perturbation logic (similar to Sign) in order to rigorously define the // direction of such edges. int CompareEdgeDistance(const S2Point& x, const S2Point& a0, const S2Point& a1, S1ChordAngle r); // Returns -1, 0, or +1 according to whether the normal of edge A has // negative, zero, or positive dot product with the normal of edge B. This // essentially measures whether the edges A and B are closer to proceeding in // the same direction or in opposite directions around the sphere. // // This method returns an exact result, i.e. the result is zero if and only if // the two edges are exactly perpendicular or at least one edge is degenerate. // (i.e., both edge endpoints project to the same point on the sphere). // // CAVEAT: This method does not use symbolic perturbations. Therefore it can // return zero even when A0 != A1 and B0 != B1, e.g. if (A0 == C * A1) exactly // for some constant C > 0 (which is possible even when both points are // considered "normalized"). // // REQUIRES: Neither edge can consist of antipodal points (e.g., A0 == -A1) // (see comments in CompareEdgeDistance). int CompareEdgeDirections(const S2Point& a0, const S2Point& a1, const S2Point& b0, const S2Point& b1); // Returns Sign(X0, X1, Z) where Z is the circumcenter of triangle ABC. // The return value is +1 if Z is to the left of edge X, and -1 if Z is to the // right of edge X. The return value is zero if A == B, B == C, or C == A // (exactly), and also if X0 and X1 project to identical points on the sphere // (e.g., X0 == X1). // // The result is determined with respect to the positions of all points as // though they were projected to lie exactly on the surface of the unit // sphere. Furthermore this method uses symbolic perturbations to compute a // consistent non-zero result even when Z lies exactly on edge X. // // REQUIRES: X0 and X1 do not project to antipodal points (e.g., X0 == -X1) // (see comments in CompareEdgeDistance). int EdgeCircumcenterSign(const S2Point& x0, const S2Point& x1, const S2Point& a, const S2Point& b, const S2Point& c); // This is a specialized method that is used to compute the intersection of an // edge X with the Voronoi diagram of a set of points, where each Voronoi // region is intersected with a disc of fixed radius "r". // // Given two sites A and B and an edge (X0, X1) such that d(A,X0) < d(B,X0) // and both sites are within the given distance "r" of edge X, this method // intersects the Voronoi region of each site with a disc of radius r and // determines whether either region has an empty intersection with edge X. It // returns FIRST if site A has an empty intersection, SECOND if site B has an // empty intersection, NEITHER if neither site has an empty intersection, or // UNCERTAIN if A == B exactly. Note that it is not possible for both // intersections to be empty because of the requirement that both sites are // within distance r of edge X. (For example, the only reason that Voronoi // region A can have an empty intersection with X is that site B is closer to // all points on X that are within radius r of site A.) // // The result is determined with respect to the positions of all points as // though they were projected to lie exactly on the surface of the unit // sphere. Furthermore this method uses symbolic perturbations to compute a // consistent non-zero result even when A and B lie on opposite sides of X // such that the Voronoi edge between them exactly coincides with edge X, or // when A and B are distinct but project to the same point on the sphere // (i.e., they are linearly dependent). // // REQUIRES: r < S1ChordAngle::Right() (90 degrees) // REQUIRES: s2pred::CompareDistances(x0, a, b) < 0 // REQUIRES: s2pred::CompareEdgeDistance(a, x0, x1, r) <= 0 // REQUIRES: s2pred::CompareEdgeDistance(b, x0, x1, r) <= 0 // REQUIRES: X0 and X1 do not project to antipodal points (e.g., X0 == -X1) // (see comments in CompareEdgeDistance). enum class Excluded { FIRST, SECOND, NEITHER, UNCERTAIN }; std::ostream& operator<<(std::ostream& os, Excluded excluded); Excluded GetVoronoiSiteExclusion(const S2Point& a, const S2Point& b, const S2Point& x0, const S2Point& x1, S1ChordAngle r); /////////////////////////// Low-Level Methods //////////////////////////// // // Most clients will not need the following methods. They can be slightly // more efficient but are harder to use, since they require the client to do // all the actual crossing tests. // A more efficient version of Sign that allows the precomputed // cross-product of A and B to be specified. (Unlike the 3 argument // version this method is also inlined.) inline int Sign(const S2Point& a, const S2Point& b, const S2Point& c, const Vector3_d& a_cross_b); // This version of Sign returns +1 if the points are definitely CCW, -1 if // they are definitely CW, and 0 if two points are identical or the result // is uncertain. Uncertain cases can be resolved, if desired, by calling // ExpensiveSign. // // The purpose of this method is to allow additional cheap tests to be done, // where possible, in order to avoid calling ExpensiveSign unnecessarily. inline int TriageSign(const S2Point& a, const S2Point& b, const S2Point& c, const Vector3_d& a_cross_b); // This function is invoked by Sign() if the sign of the determinant is // uncertain. It always returns a non-zero result unless two of the input // points are the same. It uses a combination of multiple-precision // arithmetic and symbolic perturbations to ensure that its results are // always self-consistent (cf. Simulation of Simplicity, Edelsbrunner and // Muecke). The basic idea is to assign an infinitesimal symbolic // perturbation to every possible S2Point such that no three S2Points are // collinear and no four S2Points are coplanar. These perturbations are so // small that they do not affect the sign of any determinant that was // non-zero before the perturbations. If "perturb" is false, then instead // the exact sign of the unperturbed input points is returned, which can be // zero even when all three points are distinct. // // Unlike Sign(), this method does not require the input points to be // normalized. int ExpensiveSign(const S2Point& a, const S2Point& b, const S2Point& c, bool perturb = true); ////////////////// Implementation details follow //////////////////// inline int Sign(const S2Point& a, const S2Point& b, const S2Point& c, const Vector3_d& a_cross_b) { int sign = TriageSign(a, b, c, a_cross_b); if (sign == 0) sign = ExpensiveSign(a, b, c); return sign; } inline int TriageSign(const S2Point& a, const S2Point& b, const S2Point& c, const Vector3_d& a_cross_b) { // kMaxDetError is the maximum error in computing (AxB).C where all vectors // are unit length. Using standard inequalities, it can be shown that // // fl(AxB) = AxB + D where |D| <= (|AxB| + (2/sqrt(3))*|A|*|B|) * e // // where "fl()" denotes a calculation done in floating-point arithmetic, // |x| denotes either absolute value or the L2-norm as appropriate, and // e = 0.5*DBL_EPSILON. Similarly, // // fl(B.C) = B.C + d where |d| <= (1.5*|B.C| + 1.5*|B|*|C|) * e . // // Applying these bounds to the unit-length vectors A,B,C and neglecting // relative error (which does not affect the sign of the result), we get // // fl((AxB).C) = (AxB).C + d where |d| <= (2.5 + 2/sqrt(3)) * e // // which is about 3.6548 * e, or 1.8274 * DBL_EPSILON. const double kMaxDetError = 1.8274 * DBL_EPSILON; S2_DCHECK(S2::IsUnitLength(a)); S2_DCHECK(S2::IsUnitLength(b)); S2_DCHECK(S2::IsUnitLength(c)); double det = a_cross_b.DotProd(c); // Double-check borderline cases in debug mode. S2_DCHECK(!FLAGS_s2debug || std::fabs(det) <= kMaxDetError || std::fabs(det) >= 100 * kMaxDetError || det * ExpensiveSign(a, b, c) > 0); if (det > kMaxDetError) return 1; if (det < -kMaxDetError) return -1; return 0; } } // namespace s2pred #endif // S2_S2PREDICATES_H_ s2/src/s2/s2region.h0000644000176200001440000001355514530411473013625 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2REGION_H_ #define S2_S2REGION_H_ #include #include "s2/_fp_contract_off.h" #include "s2/s1angle.h" class Decoder; class Encoder; class S2Cap; class S2Cell; class S2CellId; class S2LatLngRect; // An S2Region represents a two-dimensional region over the unit sphere. // It is an abstract interface with various concrete subtypes. // // The main purpose of this interface is to allow complex regions to be // approximated as simpler regions. So rather than having a wide variety // of virtual methods that are implemented by all subtypes, the interface // is restricted to methods that are useful for computing approximations. class S2Region { public: S2Region() = default; S2Region(const S2Region& other) = default; S2Region& operator=(const S2Region&) = default; virtual ~S2Region() {} // Returns a deep copy of the region. // // Note that each subtype of S2Region returns a pointer to an object of its // own type (e.g., S2Cap::Clone() returns an S2Cap*). virtual S2Region* Clone() const = 0; // Returns a bounding spherical cap that contains the region. The bound may // not be tight. virtual S2Cap GetCapBound() const = 0; // Returns a bounding latitude-longitude rectangle that contains the region. // The bound may not be tight. virtual S2LatLngRect GetRectBound() const = 0; // Returns a small collection of S2CellIds whose union covers the region. // The cells are not sorted, may have redundancies (such as cells that // contain other cells), and may cover much more area than necessary. // // This method is not intended for direct use by client code. Clients // should typically use S2RegionCoverer::GetCovering, which has options to // control the size and accuracy of the covering. Alternatively, if you // want a fast covering and don't care about accuracy, consider calling // S2RegionCoverer::GetFastCovering (which returns a cleaned-up version of // the covering computed by this method). // // GetCellUnionBound() implementations should attempt to return a small // covering (ideally 4 cells or fewer) that covers the region and can be // computed quickly. The result is used by S2RegionCoverer as a starting // point for further refinement. // // TODO(ericv): Remove the default implementation. virtual void GetCellUnionBound(std::vector *cell_ids) const; // Returns true if the region completely contains the given cell, otherwise // returns false. virtual bool Contains(const S2Cell& cell) const = 0; // If this method returns false, the region does not intersect the given // cell. Otherwise, either region intersects the cell, or the intersection // relationship could not be determined. // // Note that there is currently exactly one implementation of this method // (S2LatLngRect::MayIntersect) that takes advantage of the semantics above // to be more efficient. For all other S2Region subtypes, this method // returns true if the region intersect the cell and false otherwise. virtual bool MayIntersect(const S2Cell& cell) const = 0; // Returns true if and only if the given point is contained by the region. // The point 'p' is generally required to be unit length, although some // subtypes may relax this restriction. virtual bool Contains(const S2Point& p) const = 0; ////////////////////////////////////////////////////////////////////////// // Many S2Region subtypes also define the following non-virtual methods. ////////////////////////////////////////////////////////////////////////// // Appends a serialized representation of the region to "encoder". // // The representation chosen is left up to the sub-classes but it should // satisfy the following constraints: // - It should encode a version number. // - It should be deserializable using the corresponding Decode method. // - Performance, not space, should be the chief consideration. Encode() and // Decode() should be implemented such that the combination is equivalent // to calling Clone(). // // REQUIRES: "encoder" uses the default constructor, so that its buffer // can be enlarged as necessary by calling Ensure(int). // // void Encode(Encoder* const encoder) const; // Decodes an S2Region encoded with Encode(). Note that this method // requires that an S2Region object of the appropriate concrete type has // already been constructed. It is not possible to decode regions of // unknown type. // // Whenever the Decode method is changed to deal with new serialized // representations, it should be done so in a manner that allows for // older versions to be decoded i.e. the version number in the serialized // representation should be used to decide how to decode the data. // // Returns true on success. // // bool Decode(Decoder* const decoder); // Provides the same functionality as Decode, except that decoded regions // are allowed to point directly into the Decoder's memory buffer rather // than copying the data. This method can be much faster for regions that // have a lot of data (such as polygons), but the decoded region is only // valid within the scope (lifetime) of the Decoder's memory buffer. // // bool DecodeWithinScope(Decoder* const decoder); }; #endif // S2_S2REGION_H_ s2/src/s2/s2debug.cc0000644000176200001440000000145714530411473013564 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2debug.h" #include "s2/base/logging.h" DEFINE_bool(s2debug, !!google::DEBUG_MODE, "Enable automatic validity checking in S2 code"); s2/src/s2/s2lax_polyline_shape.h0000644000176200001440000001120014530411473016202 0ustar liggesusers// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2LAX_POLYLINE_SHAPE_H_ #define S2_S2LAX_POLYLINE_SHAPE_H_ #include #include #include "s2/encoded_s2point_vector.h" #include "s2/s2polyline.h" #include "s2/s2shape.h" // S2LaxPolylineShape represents a polyline. It is similar to // S2Polyline::Shape except that duplicate vertices are allowed, and the // representation is slightly more compact. // // Polylines may have any number of vertices, but note that polylines with // fewer than 2 vertices do not define any edges. (To create a polyline // consisting of a single degenerate edge, either repeat the same vertex twice // or use S2LaxClosedPolylineShape defined in s2_lax_loop_shape.h.) class S2LaxPolylineShape : public S2Shape { public: static constexpr TypeTag kTypeTag = 4; // Constructs an empty polyline. S2LaxPolylineShape() : num_vertices_(0) {} // Constructs an S2LaxPolylineShape with the given vertices. explicit S2LaxPolylineShape(const std::vector& vertices); // Constructs an S2LaxPolylineShape from the given S2Polyline, by copying // its data. explicit S2LaxPolylineShape(const S2Polyline& polyline); // Initializes an S2LaxPolylineShape with the given vertices. void Init(const std::vector& vertices); // Initializes an S2LaxPolylineShape from the given S2Polyline, by copying // its data. void Init(const S2Polyline& polyline); int num_vertices() const { return num_vertices_; } const S2Point& vertex(int i) const { return vertices_[i]; } // Appends an encoded representation of the S2LaxPolylineShape to "encoder". // // REQUIRES: "encoder" uses the default constructor, so that its buffer // can be enlarged as necessary by calling Ensure(int). void Encode(Encoder* encoder, s2coding::CodingHint hint = s2coding::CodingHint::COMPACT) const; // Decodes an S2LaxPolylineShape, returning true on success. (The method // name is chosen for compatibility with EncodedS2LaxPolylineShape below.) bool Init(Decoder* decoder); // S2Shape interface: int num_edges() const final { return std::max(0, num_vertices() - 1); } Edge edge(int e) const final; int dimension() const final { return 1; } ReferencePoint GetReferencePoint() const final { return ReferencePoint::Contained(false); } int num_chains() const final; Chain chain(int i) const final; Edge chain_edge(int i, int j) const final; ChainPosition chain_position(int e) const final; TypeTag type_tag() const override { return kTypeTag; } private: // For clients that have many small polylines, we save some memory by // representing the vertices as an array rather than using std::vector. int32 num_vertices_; std::unique_ptr vertices_; }; // Exactly like S2LaxPolylineShape, except that the vertices are kept in an // encoded form and are decoded only as they are accessed. This allows for // very fast initialization and no additional memory use beyond the encoded // data. The encoded data is not owned by this class; typically it points // into a large contiguous buffer that contains other encoded data as well. class EncodedS2LaxPolylineShape : public S2Shape { public: // Constructs an uninitialized object; requires Init() to be called. EncodedS2LaxPolylineShape() {} // Initializes an EncodedS2LaxPolylineShape. // // REQUIRES: The Decoder data buffer must outlive this object. bool Init(Decoder* decoder); int num_vertices() const { return vertices_.size(); } S2Point vertex(int i) const { return vertices_[i]; } // S2Shape interface: int num_edges() const final { return std::max(0, num_vertices() - 1); } Edge edge(int e) const final; int dimension() const final { return 1; } ReferencePoint GetReferencePoint() const final { return ReferencePoint::Contained(false); } int num_chains() const final; Chain chain(int i) const final; Edge chain_edge(int i, int j) const final; ChainPosition chain_position(int e) const final; private: s2coding::EncodedS2PointVector vertices_; }; #endif // S2_S2LAX_POLYLINE_SHAPE_H_ s2/src/s2/s2coords.h0000644000176200001440000004265614530411473013637 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // This file contains documentation of the various coordinate systems used // throughout the library. Most importantly, S2 defines a framework for // decomposing the unit sphere into a hierarchy of "cells". Each cell is a // quadrilateral bounded by four geodesics. The top level of the hierarchy is // obtained by projecting the six faces of a cube onto the unit sphere, and // lower levels are obtained by subdividing each cell into four children // recursively. Cells are numbered such that sequentially increasing cells // follow a continuous space-filling curve over the entire sphere. The // transformation is designed to make the cells at each level fairly uniform // in size. // // ////////////////////////// S2Cell Decomposition ///////////////////////// // // The following methods define the cube-to-sphere projection used by // the S2Cell decomposition. // // In the process of converting a latitude-longitude pair to a 64-bit cell // id, the following coordinate systems are used: // // (id) // An S2CellId is a 64-bit encoding of a face and a Hilbert curve position // on that face. The Hilbert curve position implicitly encodes both the // position of a cell and its subdivision level (see s2cell_id.h). // // (face, i, j) // Leaf-cell coordinates. "i" and "j" are integers in the range // [0,(2**30)-1] that identify a particular leaf cell on the given face. // The (i, j) coordinate system is right-handed on each face, and the // faces are oriented such that Hilbert curves connect continuously from // one face to the next. // // (face, s, t) // Cell-space coordinates. "s" and "t" are real numbers in the range // [0,1] that identify a point on the given face. For example, the point // (s, t) = (0.5, 0.5) corresponds to the center of the top-level face // cell. This point is also a vertex of exactly four cells at each // subdivision level greater than zero. // // (face, si, ti) // Discrete cell-space coordinates. These are obtained by multiplying // "s" and "t" by 2**31 and rounding to the nearest unsigned integer. // Discrete coordinates lie in the range [0,2**31]. This coordinate // system can represent the edge and center positions of all cells with // no loss of precision (including non-leaf cells). In binary, each // coordinate of a level-k cell center ends with a 1 followed by // (30 - k) 0s. The coordinates of its edges end with (at least) // (31 - k) 0s. // // (face, u, v) // Cube-space coordinates in the range [-1,1]. To make the cells at each // level more uniform in size after they are projected onto the sphere, // we apply a nonlinear transformation of the form u=f(s), v=f(t). // The (u, v) coordinates after this transformation give the actual // coordinates on the cube face (modulo some 90 degree rotations) before // it is projected onto the unit sphere. // // (face, u, v, w) // Per-face coordinate frame. This is an extension of the (face, u, v) // cube-space coordinates that adds a third axis "w" in the direction of // the face normal. It is always a right-handed 3D coordinate system. // Cube-space coordinates can be converted to this frame by setting w=1, // while (u,v,w) coordinates can be projected onto the cube face by // dividing by w, i.e. (face, u/w, v/w). // // (x, y, z) // Direction vector (S2Point). Direction vectors are not necessarily unit // length, and are often chosen to be points on the biunit cube // [-1,+1]x[-1,+1]x[-1,+1]. They can be be normalized to obtain the // corresponding point on the unit sphere. // // (lat, lng) // Latitude and longitude (S2LatLng). Latitudes must be between -90 and // 90 degrees inclusive, and longitudes must be between -180 and 180 // degrees inclusive. // // Note that the (i, j), (s, t), (si, ti), and (u, v) coordinate systems are // right-handed on all six faces. #ifndef S2_S2COORDS_H_ #define S2_S2COORDS_H_ #include #include #include "s2/base/integral_types.h" #include "s2/base/logging.h" #include "s2/r2.h" #include "s2/s2coords_internal.h" #include "s2/s2point.h" #include "s2/util/math/mathutil.h" // S2 is a namespace for constants and simple utility functions that are used // throughout the S2 library. The name "S2" is derived from the mathematical // symbol for the two-dimensional unit sphere (note that the "2" refers to the // dimension of the surface, not the space it is embedded in). namespace S2 { // This is the number of levels needed to specify a leaf cell. This // constant is defined here so that the S2::Metric class and the conversion // functions below can be implemented without including s2cell_id.h. Please // see s2cell_id.h for other useful constants and conversion functions. const int kMaxCellLevel = 30; // The maximum index of a valid leaf cell plus one. The range of valid leaf // cell indices is [0..kLimitIJ-1]. const int kLimitIJ = 1 << kMaxCellLevel; // == S2CellId::kMaxSize // The maximum value of an si- or ti-coordinate. The range of valid (si,ti) // values is [0..kMaxSiTi]. unsigned const int kMaxSiTi = 1U << (kMaxCellLevel + 1); // Convert an s- or t-value to the corresponding u- or v-value. This is // a non-linear transformation from [-1,1] to [-1,1] that attempts to // make the cell sizes more uniform. double STtoUV(double s); // The inverse of the STtoUV transformation. Note that it is not always // true that UVtoST(STtoUV(x)) == x due to numerical errors. double UVtoST(double u); // Convert the i- or j-index of a leaf cell to the minimum corresponding s- // or t-value contained by that cell. The argument must be in the range // [0..2**30], i.e. up to one position beyond the normal range of valid leaf // cell indices. double IJtoSTMin(int i); // Return the i- or j-index of the leaf cell containing the given // s- or t-value. If the argument is outside the range spanned by valid // leaf cell indices, return the index of the closest valid leaf cell (i.e., // return values are clamped to the range of valid leaf cell indices). int STtoIJ(double s); // Convert an si- or ti-value to the corresponding s- or t-value. double SiTitoST(unsigned int si); // Return the si- or ti-coordinate that is nearest to the given s- or // t-value. The result may be outside the range of valid (si,ti)-values. unsigned int STtoSiTi(double s); // Convert (face, u, v) coordinates to a direction vector (not // necessarily unit length). S2Point FaceUVtoXYZ(int face, double u, double v); S2Point FaceUVtoXYZ(int face, const R2Point& uv); // If the dot product of p with the given face normal is positive, // set the corresponding u and v values (which may lie outside the range // [-1,1]) and return true. Otherwise return false. bool FaceXYZtoUV(int face, const S2Point& p, double* pu, double* pv); bool FaceXYZtoUV(int face, const S2Point& p, R2Point* puv); // Given a *valid* face for the given point p (meaning that dot product // of p with the face normal is positive), return the corresponding // u and v values (which may lie outside the range [-1,1]). void ValidFaceXYZtoUV(int face, const S2Point& p, double* pu, double* pv); void ValidFaceXYZtoUV(int face, const S2Point& p, R2Point* puv); // Transform the given point P to the (u,v,w) coordinate frame of the given // face (where the w-axis represents the face normal). S2Point FaceXYZtoUVW(int face, const S2Point& p); // Return the face containing the given direction vector. (For points on // the boundary between faces, the result is arbitrary but repeatable.) int GetFace(const S2Point& p); // Convert a direction vector (not necessarily unit length) to // (face, u, v) coordinates. int XYZtoFaceUV(const S2Point& p, double* pu, double* pv); int XYZtoFaceUV(const S2Point& p, R2Point* puv); // Convert a direction vector (not necessarily unit length) to // (face, si, ti) coordinates and, if p is exactly equal to the center of a // cell, return the level of this cell (-1 otherwise). int XYZtoFaceSiTi(const S2Point& p, int* face, unsigned int* si, unsigned int* ti); // Convert (face, si, ti) coordinates to a direction vector (not necessarily // unit length). S2Point FaceSiTitoXYZ(int face, unsigned int si, unsigned int ti); // Return the right-handed normal (not necessarily unit length) for an // edge in the direction of the positive v-axis at the given u-value on // the given face. (This vector is perpendicular to the plane through // the sphere origin that contains the given edge.) S2Point GetUNorm(int face, double u); // Return the right-handed normal (not necessarily unit length) for an // edge in the direction of the positive u-axis at the given v-value on // the given face. S2Point GetVNorm(int face, double v); // Return the unit-length normal, u-axis, or v-axis for the given face. S2Point GetNorm(int face); S2Point GetUAxis(int face); S2Point GetVAxis(int face); // Return the given axis of the given face (u=0, v=1, w=2). S2Point GetUVWAxis(int face, int axis); // With respect to the (u,v,w) coordinate system of a given face, return the // face that lies in the given direction (negative=0, positive=1) of the // given axis (u=0, v=1, w=2). For example, GetUVWFace(4, 0, 1) returns the // face that is adjacent to face 4 in the positive u-axis direction. int GetUVWFace(int face, int axis, int direction); ////////////////// Implementation details follow //////////////////// // We have implemented three different projections from cell-space (s,t) to // cube-space (u,v): linear, quadratic, and tangent. They have the following // tradeoffs: // // Linear - This is the fastest transformation, but also produces the least // uniform cell sizes. Cell areas vary by a factor of about 5.2, with the // largest cells at the center of each face and the smallest cells in // the corners. // // Tangent - Transforming the coordinates via atan() makes the cell sizes // more uniform. The areas vary by a maximum ratio of 1.4 as opposed to a // maximum ratio of 5.2. However, each call to atan() is about as expensive // as all of the other calculations combined when converting from points to // cell ids, i.e. it reduces performance by a factor of 3. // // Quadratic - This is an approximation of the tangent projection that // is much faster and produces cells that are almost as uniform in size. // It is about 3 times faster than the tangent projection for converting // cell ids to points or vice versa. Cell areas vary by a maximum ratio of // about 2.1. // // Here is a table comparing the cell uniformity using each projection. "Area // ratio" is the maximum ratio over all subdivision levels of the largest cell // area to the smallest cell area at that level, "edge ratio" is the maximum // ratio of the longest edge of any cell to the shortest edge of any cell at // the same level, and "diag ratio" is the ratio of the longest diagonal of // any cell to the shortest diagonal of any cell at the same level. "ToPoint" // and "FromPoint" are the times in microseconds required to convert cell ids // to and from points (unit vectors) respectively. "ToPointRaw" is the time // to convert to a non-unit-length vector, which is all that is needed for // some purposes. // // Area Edge Diag ToPointRaw ToPoint FromPoint // Ratio Ratio Ratio (microseconds) // ------------------------------------------------------------------- // Linear: 5.200 2.117 2.959 0.020 0.087 0.085 // Tangent: 1.414 1.414 1.704 0.237 0.299 0.258 // Quadratic: 2.082 1.802 1.932 0.033 0.096 0.108 // // The worst-case cell aspect ratios are about the same with all three // projections. The maximum ratio of the longest edge to the shortest edge // within the same cell is about 1.4 and the maximum ratio of the diagonals // within the same cell is about 1.7. // // This data was produced using s2cell_test and s2cell_id_test. #define S2_LINEAR_PROJECTION 0 #define S2_TAN_PROJECTION 1 #define S2_QUADRATIC_PROJECTION 2 #define S2_PROJECTION S2_QUADRATIC_PROJECTION #if S2_PROJECTION == S2_LINEAR_PROJECTION inline double STtoUV(double s) { return 2 * s - 1; } inline double UVtoST(double u) { return 0.5 * (u + 1); } #elif S2_PROJECTION == S2_TAN_PROJECTION inline double STtoUV(double s) { // Unfortunately, tan(M_PI_4) is slightly less than 1.0. This isn't due to // a flaw in the implementation of tan(), it's because the derivative of // tan(x) at x=pi/4 is 2, and it happens that the two adjacent floating // point numbers on either side of the infinite-precision value of pi/4 have // tangents that are slightly below and slightly above 1.0 when rounded to // the nearest double-precision result. s = std::tan(M_PI_2 * s - M_PI_4); return s + (1.0 / (int64{1} << 53)) * s; } inline double UVtoST(double u) { volatile double a = std::atan(u); return (2 * M_1_PI) * (a + M_PI_4); } #elif S2_PROJECTION == S2_QUADRATIC_PROJECTION inline double STtoUV(double s) { if (s >= 0.5) return (1/3.) * (4*s*s - 1); else return (1/3.) * (1 - 4*(1-s)*(1-s)); } inline double UVtoST(double u) { if (u >= 0) return 0.5 * std::sqrt(1 + 3*u); else return 1 - 0.5 * std::sqrt(1 - 3*u); } #else #error Unknown value for S2_PROJECTION #endif inline double IJtoSTMin(int i) { S2_DCHECK(i >= 0 && i <= kLimitIJ); return (1.0 / kLimitIJ) * i; } inline int STtoIJ(double s) { return std::max(0, std::min(kLimitIJ - 1, MathUtil::FastIntRound(kLimitIJ * s - 0.5))); } inline double SiTitoST(unsigned int si) { S2_DCHECK_LE(si, kMaxSiTi); return (1.0 / kMaxSiTi) * si; } inline unsigned int STtoSiTi(double s) { // kMaxSiTi == 2^31, so the result doesn't fit in an int32 when s == 1. return static_cast(MathUtil::FastInt64Round(s * kMaxSiTi)); } inline S2Point FaceUVtoXYZ(int face, double u, double v) { switch (face) { case 0: return S2Point( 1, u, v); case 1: return S2Point(-u, 1, v); case 2: return S2Point(-u, -v, 1); case 3: return S2Point(-1, -v, -u); case 4: return S2Point( v, -1, -u); default: return S2Point( v, u, -1); } } inline S2Point FaceUVtoXYZ(int face, const R2Point& uv) { return FaceUVtoXYZ(face, uv[0], uv[1]); } inline void ValidFaceXYZtoUV(int face, const S2Point& p, double* pu, double* pv) { S2_DCHECK_GT(p.DotProd(GetNorm(face)), 0); switch (face) { case 0: *pu = p[1] / p[0]; *pv = p[2] / p[0]; break; case 1: *pu = -p[0] / p[1]; *pv = p[2] / p[1]; break; case 2: *pu = -p[0] / p[2]; *pv = -p[1] / p[2]; break; case 3: *pu = p[2] / p[0]; *pv = p[1] / p[0]; break; case 4: *pu = p[2] / p[1]; *pv = -p[0] / p[1]; break; default: *pu = -p[1] / p[2]; *pv = -p[0] / p[2]; break; } } inline void ValidFaceXYZtoUV(int face, const S2Point& p, R2Point* puv) { ValidFaceXYZtoUV(face, p, &(*puv)[0], &(*puv)[1]); } inline int GetFace(const S2Point& p) { int face = p.LargestAbsComponent(); if (p[face] < 0) face += 3; return face; } inline int XYZtoFaceUV(const S2Point& p, double* pu, double* pv) { int face = GetFace(p); ValidFaceXYZtoUV(face, p, pu, pv); return face; } inline int XYZtoFaceUV(const S2Point& p, R2Point* puv) { return XYZtoFaceUV(p, &(*puv)[0], &(*puv)[1]); } inline bool FaceXYZtoUV(int face, const S2Point& p, double* pu, double* pv) { if (face < 3) { if (p[face] <= 0) return false; } else { if (p[face-3] >= 0) return false; } ValidFaceXYZtoUV(face, p, pu, pv); return true; } inline bool FaceXYZtoUV(int face, const S2Point& p, R2Point* puv) { return FaceXYZtoUV(face, p, &(*puv)[0], &(*puv)[1]); } inline S2Point GetUNorm(int face, double u) { switch (face) { case 0: return S2Point( u, -1, 0); case 1: return S2Point( 1, u, 0); case 2: return S2Point( 1, 0, u); case 3: return S2Point(-u, 0, 1); case 4: return S2Point( 0, -u, 1); default: return S2Point( 0, -1, -u); } } inline S2Point GetVNorm(int face, double v) { switch (face) { case 0: return S2Point(-v, 0, 1); case 1: return S2Point( 0, -v, 1); case 2: return S2Point( 0, -1, -v); case 3: return S2Point( v, -1, 0); case 4: return S2Point( 1, v, 0); default: return S2Point( 1, 0, v); } } inline S2Point GetNorm(int face) { return GetUVWAxis(face, 2); } inline S2Point GetUAxis(int face) { return GetUVWAxis(face, 0); } inline S2Point GetVAxis(int face) { return GetUVWAxis(face, 1); } inline S2Point GetUVWAxis(int face, int axis) { const double* p = internal::kFaceUVWAxes[face][axis]; return S2Point(p[0], p[1], p[2]); } inline int GetUVWFace(int face, int axis, int direction) { S2_DCHECK(face >= 0 && face <= 5); S2_DCHECK(axis >= 0 && axis <= 2); S2_DCHECK(direction >= 0 && direction <= 1); return internal::kFaceUVWFaces[face][axis][direction]; } } // namespace S2 #endif // S2_S2COORDS_H_ s2/src/s2/s2builderutil_testing.h0000644000176200001440000000625714530411473016424 0ustar liggesusers// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2BUILDERUTIL_TESTING_H_ #define S2_S2BUILDERUTIL_TESTING_H_ #include #include "absl/memory/memory.h" #include "s2/s2builder.h" #include "s2/s2builder_graph.h" #include "s2/s2builder_layer.h" namespace s2builderutil { // A class that copies an S2Builder::Graph and owns the underlying data // (unlike S2Builder::Graph, which is just a view). class GraphClone { public: GraphClone() {} // Must call Init(). explicit GraphClone(const S2Builder::Graph& g) { Init(g); } void Init(const S2Builder::Graph& g); const S2Builder::Graph& graph() { return g_; } private: S2Builder::GraphOptions options_; std::vector vertices_; std::vector edges_; std::vector input_edge_id_set_ids_; IdSetLexicon input_edge_id_set_lexicon_; std::vector label_set_ids_; IdSetLexicon label_set_lexicon_; S2Builder::IsFullPolygonPredicate is_full_polygon_predicate_; S2Builder::Graph g_; }; // A layer type that copies an S2Builder::Graph into a GraphClone object // (which owns the underlying data, unlike S2Builder::Graph itself). class GraphCloningLayer : public S2Builder::Layer { public: GraphCloningLayer(const S2Builder::GraphOptions& graph_options, GraphClone* gc) : graph_options_(graph_options), gc_(gc) {} S2Builder::GraphOptions graph_options() const override { return graph_options_; } void Build(const S2Builder::Graph& g, S2Error* error) override { gc_->Init(g); } private: GraphOptions graph_options_; GraphClone* gc_; }; // A layer type that copies an S2Builder::Graph and appends it to a vector, // and appends the corresponding GraphClone object (which owns the Graph data) // to a separate vector. class GraphAppendingLayer : public S2Builder::Layer { public: GraphAppendingLayer( const S2Builder::GraphOptions& graph_options, std::vector* graphs, std::vector>* clones) : graph_options_(graph_options), graphs_(graphs), clones_(clones) {} S2Builder::GraphOptions graph_options() const override { return graph_options_; } void Build(const S2Builder::Graph& g, S2Error* error) override { clones_->push_back(absl::make_unique(g)); graphs_->push_back(clones_->back()->graph()); } private: GraphOptions graph_options_; std::vector* graphs_; std::vector>* clones_; }; } // namespace s2builderutil #endif // S2_S2BUILDERUTIL_TESTING_H_ s2/src/s2/s2builderutil_s2polygon_layer.h0000644000176200001440000001742314530411473020074 0ustar liggesusers// Copyright 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // Note that there are two supported output types for polygons: S2Polygon and // S2LaxPolygonShape. Use S2Polygon if you need the full range of operations // that S2Polygon implements. Use S2LaxPolygonShape if you want to represent // polygons with zero-area degenerate regions, or if you need a type that has // low memory overhead and fast initialization. However, be aware that to // convert from a S2LaxPolygonShape to an S2Polygon you will need to use // S2Builder again. // // Similarly, there are two supported output formats for polygon meshes: // S2LaxPolygonShapeVector and S2PolygonMesh. Use S2PolygonMesh if you need // to be able to determine which polygons are adjacent to each edge or vertex; // otherwise use S2LaxPolygonShapeVector, which uses less memory and is faster // to construct. #ifndef S2_S2BUILDERUTIL_S2POLYGON_LAYER_H_ #define S2_S2BUILDERUTIL_S2POLYGON_LAYER_H_ #include #include #include #include "s2/base/logging.h" #include "absl/container/btree_map.h" #include "absl/memory/memory.h" #include "s2/id_set_lexicon.h" #include "s2/mutable_s2shape_index.h" #include "s2/s2builder.h" #include "s2/s2builder_graph.h" #include "s2/s2builder_layer.h" #include "s2/s2error.h" #include "s2/s2loop.h" #include "s2/s2polygon.h" namespace s2builderutil { // A layer type that assembles edges (directed or undirected) into an // S2Polygon. Returns an error if the edges cannot be assembled into loops. // // If the input edges are directed, they must be oriented such that the // polygon interior is to the left of all edges. Directed edges are always // preferred (see S2Builder::EdgeType). // // Before the edges are assembled into loops, "sibling pairs" consisting of an // edge and its reverse edge are automatically removed. Such edge pairs // represent zero-area degenerate regions, which S2Polygon does not allow. // (If you need to build polygons with degeneracies, use LaxPolygonLayer // instead.) // // S2PolygonLayer is implemented such that if the input to S2Builder is a // polygon and is not modified, then the output has the same cyclic ordering // of loop vertices and the same loop ordering as the input polygon. // // If the polygon has no edges, then the graph's IsFullPolygonPredicate is // called to determine whether the output polygon should be empty (containing // no points) or full (containing all points). This predicate can be // specified as part of the S2Builder input geometry. class S2PolygonLayer : public S2Builder::Layer { public: class Options { public: // Constructor that uses the default options (listed below). Options(); // Constructor that specifies the edge type. explicit Options(S2Builder::EdgeType edge_type); // Indicates whether the input edges provided to S2Builder are directed or // undirected. Directed edges should be used whenever possible (see // S2Builder::EdgeType for details). // // If the input edges are directed, they should be oriented so that the // polygon interior is to the left of all edges. This means that for a // polygon with holes, the outer loops ("shells") should be directed // counter-clockwise while the inner loops ("holes") should be directed // clockwise. Note that S2Builder::AddPolygon() does this automatically. // // DEFAULT: S2Builder::EdgeType::DIRECTED S2Builder::EdgeType edge_type() const; void set_edge_type(S2Builder::EdgeType edge_type); // If true, calls FindValidationError() on the output polygon. If any // error is found, it will be returned by S2Builder::Build(). // // Note that this option calls set_s2debug_override(S2Debug::DISABLE) in // order to turn off the default error checking in debug builds. // // DEFAULT: false bool validate() const; void set_validate(bool validate); private: S2Builder::EdgeType edge_type_; bool validate_; }; // Specifies that a polygon should be constructed using the given options. explicit S2PolygonLayer(S2Polygon* polygon, const Options& options = Options()); // Specifies that a polygon should be constructed using the given options, // and that any labels attached to the input edges should be returned in // "label_set_ids" and "label_set_lexicion". // // The labels associated with the edge "polygon.loop(i).vertex({j, j+1})" // can be retrieved as follows: // // for (int32 label : label_set_lexicon.id_set(label_set_ids[i][j])) {...} using LabelSetIds = std::vector>; S2PolygonLayer(S2Polygon* polygon, LabelSetIds* label_set_ids, IdSetLexicon* label_set_lexicon, const Options& options = Options()); // Layer interface: GraphOptions graph_options() const override; void Build(const Graph& g, S2Error* error) override; private: void Init(S2Polygon* polygon, LabelSetIds* label_set_ids, IdSetLexicon* label_set_lexicon, const Options& options); void AppendS2Loops(const Graph& g, const std::vector& edge_loops, std::vector>* loops) const; void AppendEdgeLabels(const Graph& g, const std::vector& edge_loops); using LoopMap = absl::btree_map>; void InitLoopMap(const std::vector>& loops, LoopMap* loop_map) const; void ReorderEdgeLabels(const LoopMap& loop_map); S2Polygon* polygon_; LabelSetIds* label_set_ids_; IdSetLexicon* label_set_lexicon_; Options options_; }; // Like S2PolygonLayer, but adds the polygon to a MutableS2ShapeIndex (if the // polygon is non-empty). class IndexedS2PolygonLayer : public S2Builder::Layer { public: using Options = S2PolygonLayer::Options; explicit IndexedS2PolygonLayer(MutableS2ShapeIndex* index, const Options& options = Options()) : index_(index), polygon_(new S2Polygon), layer_(polygon_.get(), options) {} GraphOptions graph_options() const override { return layer_.graph_options(); } void Build(const Graph& g, S2Error* error) override { layer_.Build(g, error); if (error->ok() && !polygon_->is_empty()) { index_->Add( absl::make_unique(std::move(polygon_))); } } private: MutableS2ShapeIndex* index_; std::unique_ptr polygon_; S2PolygonLayer layer_; }; ////////////////// Implementation details follow //////////////////// inline S2PolygonLayer::Options::Options() : edge_type_(S2Builder::EdgeType::DIRECTED), validate_(false) { } inline S2PolygonLayer::Options::Options(S2Builder::EdgeType edge_type) : edge_type_(edge_type), validate_(false) { } inline S2Builder::EdgeType S2PolygonLayer::Options::edge_type() const { return edge_type_; } inline void S2PolygonLayer::Options::set_edge_type( S2Builder::EdgeType edge_type) { edge_type_ = edge_type; } inline bool S2PolygonLayer::Options::validate() const { return validate_; } inline void S2PolygonLayer::Options::set_validate(bool validate) { validate_ = validate; } } // namespace s2builderutil #endif // S2_S2BUILDERUTIL_S2POLYGON_LAYER_H_ s2/src/s2/s2polyline.h0000644000176200001440000003567514530411473014204 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2POLYLINE_H_ #define S2_S2POLYLINE_H_ #include #include #include "s2/base/logging.h" #include "s2/_fp_contract_off.h" #include "s2/s1angle.h" #include "s2/s2debug.h" #include "s2/s2error.h" #include "s2/s2latlng_rect.h" #include "s2/s2region.h" #include "s2/s2shape.h" #include "absl/base/macros.h" #include "absl/memory/memory.h" class Decoder; class Encoder; class S1Angle; class S2Cap; class S2Cell; class S2LatLng; // An S2Polyline represents a sequence of zero or more vertices connected by // straight edges (geodesics). Edges of length 0 and 180 degrees are not // allowed, i.e. adjacent vertices should not be identical or antipodal. class S2Polyline final : public S2Region { public: // Creates an empty S2Polyline that should be initialized by calling Init() // or Decode(). S2Polyline(); #ifndef SWIG // S2Polyline is movable, but only privately copyable. S2Polyline(S2Polyline&&); S2Polyline& operator=(S2Polyline&&); #endif // SWIG // Convenience constructors that call Init() with the given vertices. explicit S2Polyline(const std::vector& vertices); explicit S2Polyline(const std::vector& vertices); // Convenience constructors to disable the automatic validity checking // controlled by the --s2debug flag. Example: // // S2Polyline* line = new S2Polyline(vertices, S2Debug::DISABLE); // // This is equivalent to: // // S2Polyline* line = new S2Polyline; // line->set_s2debug_override(S2Debug::DISABLE); // line->Init(vertices); // // The main reason to use this constructors is if you intend to call // IsValid() explicitly. See set_s2debug_override() for details. S2Polyline(const std::vector& vertices, S2Debug override); S2Polyline(const std::vector& vertices, S2Debug override); // Initialize a polyline that connects the given vertices. Empty polylines are // allowed. Adjacent vertices should not be identical or antipodal. All // vertices should be unit length. void Init(const std::vector& vertices); // Convenience initialization function that accepts latitude-longitude // coordinates rather than S2Points. void Init(const std::vector& vertices); ~S2Polyline() override; // Allows overriding the automatic validity checks controlled by the // --s2debug flag. If this flag is true, then polylines are automatically // checked for validity as they are initialized. The main reason to disable // this flag is if you intend to call IsValid() explicitly, like this: // // S2Polyline line; // line.set_s2debug_override(S2Debug::DISABLE); // line.Init(...); // if (!line.IsValid()) { ... } // // Without the call to set_s2debug_override(), invalid data would cause a // fatal error in Init() whenever the --s2debug flag is enabled. void set_s2debug_override(S2Debug override); S2Debug s2debug_override() const; // Return true if the given vertices form a valid polyline. bool IsValid() const; // Returns true if this is *not* a valid polyline and sets "error" // appropriately. Otherwise returns false and leaves "error" unchanged. // // REQUIRES: error != nullptr bool FindValidationError(S2Error* error) const; int num_vertices() const { return num_vertices_; } const S2Point& vertex(int k) const { S2_DCHECK_GE(k, 0); S2_DCHECK_LT(k, num_vertices_); return vertices_[k]; } // Return the length of the polyline. S1Angle GetLength() const; // Return the true centroid of the polyline multiplied by the length of the // polyline (see s2centroids.h for details on centroids). The result is not // unit length, so you may want to normalize it. // // Prescaling by the polyline length makes it easy to compute the centroid // of several polylines (by simply adding up their centroids). S2Point GetCentroid() const; // Return the point whose distance from vertex 0 along the polyline is the // given fraction of the polyline's total length. Fractions less than zero // or greater than one are clamped. The return value is unit length. This // cost of this function is currently linear in the number of vertices. // The polyline must not be empty. S2Point Interpolate(double fraction) const; // Like Interpolate(), but also return the index of the next polyline // vertex after the interpolated point P. This allows the caller to easily // construct a given suffix of the polyline by concatenating P with the // polyline vertices starting at "next_vertex". Note that P is guaranteed // to be different than vertex(*next_vertex), so this will never result in // a duplicate vertex. // // The polyline must not be empty. Note that if "fraction" >= 1.0, then // "next_vertex" will be set to num_vertices() (indicating that no vertices // from the polyline need to be appended). The value of "next_vertex" is // always between 1 and num_vertices(). // // This method can also be used to construct a prefix of the polyline, by // taking the polyline vertices up to "next_vertex - 1" and appending the // returned point P if it is different from the last vertex (since in this // case there is no guarantee of distinctness). S2Point GetSuffix(double fraction, int* next_vertex) const; // The inverse operation of GetSuffix/Interpolate. Given a point on the // polyline, returns the ratio of the distance to the point from the // beginning of the polyline over the length of the polyline. The return // value is always betwen 0 and 1 inclusive. See GetSuffix() for the // meaning of "next_vertex". // // The polyline should not be empty. If it has fewer than 2 vertices, the // return value is zero. double UnInterpolate(const S2Point& point, int next_vertex) const; // Given a point, returns a point on the polyline that is closest to the given // point. See GetSuffix() for the meaning of "next_vertex", which is chosen // here w.r.t. the projected point as opposed to the interpolated point in // GetSuffix(). // // The polyline must be non-empty. S2Point Project(const S2Point& point, int* next_vertex) const; // Returns true if the point given is on the right hand side of the polyline, // using a naive definition of "right-hand-sideness" where the point is on // the RHS of the polyline iff the point is on the RHS of the line segment in // the polyline which it is closest to. // // The polyline must have at least 2 vertices. bool IsOnRight(const S2Point& point) const; // Return true if this polyline intersects the given polyline. If the // polylines share a vertex they are considered to be intersecting. When a // polyline endpoint is the only intersection with the other polyline, the // function may return true or false arbitrarily. // // The running time is quadratic in the number of vertices. (To intersect // polylines more efficiently, or compute the actual intersection geometry, // use S2BooleanOperation.) bool Intersects(const S2Polyline* line) const; // Reverse the order of the polyline vertices. void Reverse(); // Return a subsequence of vertex indices such that the polyline connecting // these vertices is never further than "tolerance" from the original // polyline. Provided the first and last vertices are distinct, they are // always preserved; if they are not, the subsequence may contain only a // single index. // // Some useful properties of the algorithm: // // - It runs in linear time. // // - The output is always a valid polyline. In particular, adjacent // output vertices are never identical or antipodal. // // - The method is not optimal, but it tends to produce 2-3% fewer // vertices than the Douglas-Peucker algorithm with the same tolerance. // // - The output is *parametrically* equivalent to the original polyline to // within the given tolerance. For example, if a polyline backtracks on // itself and then proceeds onwards, the backtracking will be preserved // (to within the given tolerance). This is different than the // Douglas-Peucker algorithm, which only guarantees geometric equivalence. // // See also S2PolylineSimplifier, which uses the same algorithm but is more // efficient and supports more features, and also S2Builder, which can // simplify polylines and polygons, supports snapping (e.g. to E7 lat/lng // coordinates or S2CellId centers), and can split polylines at intersection // points. void SubsampleVertices(S1Angle tolerance, std::vector* indices) const; // Return true if two polylines are exactly the same. bool Equals(const S2Polyline* b) const; // Return true if two polylines have the same number of vertices, and // corresponding vertex pairs are separated by no more than "max_error". // (For testing purposes.) bool ApproxEquals(const S2Polyline& b, S1Angle max_error = S1Angle::Radians(1e-15)) const; // Return true if "covered" is within "max_error" of a contiguous subpath of // this polyline over its entire length. Specifically, this method returns // true if this polyline has parameterization a:[0,1] -> S^2, "covered" has // parameterization b:[0,1] -> S^2, and there is a non-decreasing function // f:[0,1] -> [0,1] such that distance(a(f(t)), b(t)) <= max_error for all t. // // You can think of this as testing whether it is possible to drive a car // along "covered" and a car along some subpath of this polyline such that no // car ever goes backward, and the cars are always within "max_error" of each // other. // // This function is well-defined for empty polylines: // anything.covers(empty) = true // empty.covers(nonempty) = false bool NearlyCovers(const S2Polyline& covered, S1Angle max_error) const; // Returns the total number of bytes used by the polyline. size_t SpaceUsed() const; //////////////////////////////////////////////////////////////////////// // S2Region interface (see s2region.h for details): S2Polyline* Clone() const override; S2Cap GetCapBound() const override; S2LatLngRect GetRectBound() const override; bool Contains(const S2Cell& cell) const override { return false; } bool MayIntersect(const S2Cell& cell) const override; // Always return false, because "containment" is not numerically // well-defined except at the polyline vertices. bool Contains(const S2Point& p) const override { return false; } // Appends a serialized representation of the S2Polyline to "encoder". // // REQUIRES: "encoder" uses the default constructor, so that its buffer // can be enlarged as necessary by calling Ensure(int). void Encode(Encoder* const encoder) const; // Decodes an S2Polyline encoded with Encode(). Returns true on success. bool Decode(Decoder* const decoder); #ifndef SWIG // Wrapper class for indexing a polyline (see S2ShapeIndex). Once this // object is inserted into an S2ShapeIndex it is owned by that index, and // will be automatically deleted when no longer needed by the index. Note // that this class does not take ownership of the polyline itself (see // OwningShape below). You can also subtype this class to store additional // data (see S2Shape for details). class Shape : public S2Shape { public: static constexpr TypeTag kTypeTag = 2; Shape() : polyline_(nullptr) {} // Must call Init(). // Initialization. Does not take ownership of "polyline". // // Note that a polyline with one vertex is defined to have no edges. Use // S2LaxPolylineShape or S2LaxClosedPolylineShape if you want to define a // polyline consisting of a single degenerate edge. explicit Shape(const S2Polyline* polyline) { Init(polyline); } void Init(const S2Polyline* polyline); const S2Polyline* polyline() const { return polyline_; } // Encodes the polyline using S2Polyline::Encode(). void Encode(Encoder* encoder) const { // TODO(geometry-library): Support compressed encodings. polyline_->Encode(encoder); } // Decoding is defined only for S2Polyline::OwningShape below. // S2Shape interface: int num_edges() const final { return std::max(0, polyline_->num_vertices() - 1); } Edge edge(int e) const final { return Edge(polyline_->vertex(e), polyline_->vertex(e + 1)); } int dimension() const final { return 1; } ReferencePoint GetReferencePoint() const final { return ReferencePoint::Contained(false); } int num_chains() const final; Chain chain(int i) const final; Edge chain_edge(int i, int j) const final { S2_DCHECK_EQ(i, 0); return Edge(polyline_->vertex(j), polyline_->vertex(j + 1)); } ChainPosition chain_position(int e) const final { return ChainPosition(0, e); } TypeTag type_tag() const override { return kTypeTag; } private: const S2Polyline* polyline_; }; // Like Shape, except that the S2Polyline is automatically deleted when this // object is deleted by the S2ShapeIndex. This is useful when an S2Polyline // is constructed solely for the purpose of indexing it. class OwningShape : public Shape { public: OwningShape() {} // Must call Init(). explicit OwningShape(std::unique_ptr polyline) : Shape(polyline.get()), owned_polyline_(std::move(polyline)) {} void Init(std::unique_ptr polyline) { Shape::Init(polyline.get()); owned_polyline_ = std::move(polyline); } bool Init(Decoder* decoder) { auto polyline = absl::make_unique(); if (!polyline->Decode(decoder)) return false; Shape::Init(polyline.get()); owned_polyline_ = std::move(polyline); return true; } private: std::unique_ptr owned_polyline_; }; #endif // SWIG private: // Internal copy constructor used only by Clone() that makes a deep copy of // its argument. S2Polyline(const S2Polyline& src); // Allows overriding the automatic validity checking controlled by the // --s2debug flag. S2Debug s2debug_override_; // We store the vertices in an array rather than a vector because we don't // need any STL methods, and computing the number of vertices using size() // would be relatively expensive (due to division by sizeof(S2Point) == 24). int num_vertices_ = 0; std::unique_ptr vertices_; #ifndef SWIG void operator=(const S2Polyline&) = delete; #endif // SWIG }; #endif // S2_S2POLYLINE_H_ s2/src/s2/s2shapeutil_contains_brute_force.cc0000644000176200001440000000243314530411473020744 0ustar liggesusers// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2shapeutil_contains_brute_force.h" #include #include "s2/s2edge_crosser.h" namespace s2shapeutil { bool ContainsBruteForce(const S2Shape& shape, const S2Point& point) { if (shape.dimension() < 2) return false; S2Shape::ReferencePoint ref_point = shape.GetReferencePoint(); if (ref_point.point == point) return ref_point.contained; S2CopyingEdgeCrosser crosser(ref_point.point, point); bool inside = ref_point.contained; for (int e = 0; e < shape.num_edges(); ++e) { auto edge = shape.edge(e); inside ^= crosser.EdgeOrVertexCrossing(edge.v0, edge.v1); } return inside; } } // namespace s2shapeutil s2/src/s2/encoded_uint_vector.h0000644000176200001440000002357414530411473016121 0ustar liggesusers// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_ENCODED_UINT_VECTOR_H_ #define S2_ENCODED_UINT_VECTOR_H_ #include #include #include "absl/base/internal/unaligned_access.h" #include "absl/types/span.h" #include "s2/util/coding/coder.h" namespace s2coding { // Encodes a vector of unsigned integers in a format that can later be // decoded as an EncodedUintVector. // // REQUIRES: T is an unsigned integer type. // REQUIRES: 2 <= sizeof(T) <= 8 // REQUIRES: "encoder" uses the default constructor, so that its buffer // can be enlarged as necessary by calling Ensure(int). template void EncodeUintVector(absl::Span v, Encoder* encoder); // This class represents an encoded vector of unsigned integers of type T. // Values are decoded only when they are accessed. This allows for very fast // initialization and no additional memory use beyond the encoded data. // The encoded data is not owned by this class; typically it points into a // large contiguous buffer that contains other encoded data as well. // // This is one of several helper classes that allow complex data structures to // be initialized from an encoded format in constant time and then decoded on // demand. This can be a big performance advantage when only a small part of // the data structure is actually used. // // Values are encoded using a fixed number of bytes per value, where the // number of bytes depends on the largest value present. // // REQUIRES: T is an unsigned integer type. // REQUIRES: 2 <= sizeof(T) <= 8 template class EncodedUintVector { public: static_assert(std::is_unsigned::value, "Unsupported signed integer"); static_assert(sizeof(T) & 0xe, "Unsupported integer length"); // Constructs an uninitialized object; requires Init() to be called. EncodedUintVector() {} // Initializes the EncodedUintVector. Returns false on errors, leaving the // vector in an unspecified state. // // REQUIRES: The Decoder data buffer must outlive this object. bool Init(Decoder* decoder); // Resets the vector to be empty. void Clear(); // Returns the size of the original vector. size_t size() const; // Returns the element at the given index. T operator[](int i) const; // Returns the index of the first element x such that (x >= target), or // size() if no such element exists. // // REQUIRES: The vector elements are sorted in non-decreasing order. size_t lower_bound(T target) const; // Decodes and returns the entire original vector. std::vector Decode() const; private: template size_t lower_bound(T target) const; const char* data_; uint32 size_; uint8 len_; }; // Encodes an unsigned integer in little-endian format using "length" bytes. // (The client must ensure that the encoder's buffer is large enough.) // // REQUIRES: T is an unsigned integer type. // REQUIRES: 2 <= sizeof(T) <= 8 // REQUIRES: 0 <= length <= sizeof(T) // REQUIRES: value < 256 ** length // REQUIRES: encoder->avail() >= length template void EncodeUintWithLength(T value, int length, Encoder* encoder); // Decodes a variable-length integer consisting of "length" bytes starting at // "ptr" in little-endian format. // // REQUIRES: T is an unsigned integer type. // REQUIRES: 2 <= sizeof(T) <= 8 // REQUIRES: 0 <= length <= sizeof(T) template T GetUintWithLength(const void* ptr, int length); // Decodes and consumes a variable-length integer consisting of "length" bytes // in little-endian format. Returns false if not enough bytes are available. // // REQUIRES: T is an unsigned integer type. // REQUIRES: 2 <= sizeof(T) <= 8 // REQUIRES: 0 <= length <= sizeof(T) template bool DecodeUintWithLength(int length, Decoder* decoder, T* result); ////////////////// Implementation details follow //////////////////// template inline void EncodeUintWithLength(T value, int length, Encoder* encoder) { static_assert(std::is_unsigned::value, "Unsupported signed integer"); static_assert(sizeof(T) & 0xe, "Unsupported integer length"); S2_DCHECK(length >= 0 && length <= sizeof(T)); S2_DCHECK_GE(encoder->avail(), length); while (--length >= 0) { encoder->put8(value); value >>= 8; } S2_DCHECK_EQ(value, 0); } template inline T GetUintWithLength(const char* ptr, int length) { static_assert(std::is_unsigned::value, "Unsupported signed integer"); static_assert(sizeof(T) & 0xe, "Unsupported integer length"); S2_DCHECK(length >= 0 && length <= sizeof(T)); // Note that the following code is faster than any of the following: // // - A loop that repeatedly loads and shifts one byte. // - memcpying "length" bytes to a local variable of type T. // - A switch statement that handles each length optimally. // // The following code is slightly faster: // // T mask = (length == 0) ? 0 : ~T{0} >> 8 * (sizeof(T) - length); // return *reinterpret_cast(ptr) & mask; // // However this technique is unsafe because in extremely rare cases it might // access out-of-bounds heap memory. (This can only happen if "ptr" is // within (sizeof(T) - length) bytes of the end of a memory page and the // following page in the address space is unmapped.) if (length & sizeof(T)) { if (sizeof(T) == 8) return ABSL_INTERNAL_UNALIGNED_LOAD64(ptr); if (sizeof(T) == 4) return ABSL_INTERNAL_UNALIGNED_LOAD32(ptr); if (sizeof(T) == 2) return ABSL_INTERNAL_UNALIGNED_LOAD16(ptr); S2_DCHECK_EQ(sizeof(T), 1); return *ptr; } T x = 0; ptr += length; if (sizeof(T) > 4 && (length & 4)) { x = ABSL_INTERNAL_UNALIGNED_LOAD32(ptr -= sizeof(uint32)); } if (sizeof(T) > 2 && (length & 2)) { x = (x << 16) + ABSL_INTERNAL_UNALIGNED_LOAD16(ptr -= sizeof(uint16)); } if (sizeof(T) > 1 && (length & 1)) { x = (x << 8) + static_cast(*--ptr); } return x; } template bool DecodeUintWithLength(int length, Decoder* decoder, T* result) { if (decoder->avail() < length) return false; const char* ptr = reinterpret_cast(decoder->ptr()); *result = GetUintWithLength(ptr, length); decoder->skip(length); return true; } template void EncodeUintVector(absl::Span v, Encoder* encoder) { // The encoding is as follows: // // varint64: (v.size() * sizeof(T)) | (len - 1) // array of v.size() elements ["len" bytes each] // // Note that we don't allow (len == 0) since this would require an extra bit // to encode the length. T one_bits = 1; // Ensures len >= 1. for (auto x : v) one_bits |= x; int len = (Bits::FindMSBSetNonZero64(one_bits) >> 3) + 1; S2_DCHECK(len >= 1 && len <= 8); // Note that the multiplication is optimized into a bit shift. encoder->Ensure(Varint::kMax64 + v.size() * len); uint64 size_len = (uint64{v.size()} * sizeof(T)) | (len - 1); encoder->put_varint64(size_len); for (auto x : v) { EncodeUintWithLength(x, len, encoder); } } template bool EncodedUintVector::Init(Decoder* decoder) { uint64 size_len; if (!decoder->get_varint64(&size_len)) return false; size_ = size_len / sizeof(T); // Optimized into bit shift. len_ = (size_len & (sizeof(T) - 1)) + 1; if (size_ > std::numeric_limits::max() / sizeof(T)) return false; size_t bytes = size_ * len_; if (decoder->avail() < bytes) return false; data_ = reinterpret_cast(decoder->ptr()); decoder->skip(bytes); return true; } template void EncodedUintVector::Clear() { size_ = 0; data_ = nullptr; } template inline size_t EncodedUintVector::size() const { return size_; } template inline T EncodedUintVector::operator[](int i) const { S2_DCHECK(i >= 0 && i < size_); return GetUintWithLength(data_ + i * len_, len_); } template size_t EncodedUintVector::lower_bound(T target) const { static_assert(sizeof(T) & 0xe, "Unsupported integer length"); S2_DCHECK(len_ >= 1 && len_ <= sizeof(T)); // TODO(ericv): Consider using the unused 28 bits of "len_" to store the // last result of lower_bound() to be used as a hint. This should help in // common situation where the same element is looked up repeatedly. This // would require declaring the new field (length_lower_bound_hint_) as // mutable std::atomic (accessed using std::memory_order_relaxed) // with a custom copy constructor that resets the hint component to zero. switch (len_) { case 1: return lower_bound<1>(target); case 2: return lower_bound<2>(target); case 3: return lower_bound<3>(target); case 4: return lower_bound<4>(target); case 5: return lower_bound<5>(target); case 6: return lower_bound<6>(target); case 7: return lower_bound<7>(target); default: return lower_bound<8>(target); } } template template inline size_t EncodedUintVector::lower_bound(T target) const { size_t lo = 0, hi = size_; while (lo < hi) { size_t mid = (lo + hi) >> 1; T value = GetUintWithLength(data_ + mid * length, length); if (value < target) { lo = mid + 1; } else { hi = mid; } } return lo; } template std::vector EncodedUintVector::Decode() const { std::vector result(size_); for (int i = 0; i < size_; ++i) { result[i] = (*this)[i]; } return result; } } // namespace s2coding #endif // S2_ENCODED_UINT_VECTOR_H_ s2/src/s2/s2wedge_relations.cc0000644000176200001440000000612014530411473015641 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2wedge_relations.h" #include "s2/s2predicates.h" namespace S2 { bool WedgeContains( const S2Point& a0, const S2Point& ab1, const S2Point& a2, const S2Point& b0, const S2Point& b2) { // For A to contain B (where each loop interior is defined to be its left // side), the CCW edge order around ab1 must be a2 b2 b0 a0. We split // this test into two parts that test three vertices each. return (s2pred::OrderedCCW(a2, b2, b0, ab1) && s2pred::OrderedCCW(b0, a0, a2, ab1)); } bool WedgeIntersects( const S2Point& a0, const S2Point& ab1, const S2Point& a2, const S2Point& b0, const S2Point& b2) { // For A not to intersect B (where each loop interior is defined to be // its left side), the CCW edge order around ab1 must be a0 b2 b0 a2. // Note that it's important to write these conditions as negatives // (!OrderedCCW(a,b,c,o) rather than Ordered(c,b,a,o)) to get correct // results when two vertices are the same. return !(s2pred::OrderedCCW(a0, b2, b0, ab1) && s2pred::OrderedCCW(b0, a2, a0, ab1)); } WedgeRelation GetWedgeRelation( const S2Point& a0, const S2Point& ab1, const S2Point& a2, const S2Point& b0, const S2Point& b2) { // There are 6 possible edge orderings at a shared vertex (all // of these orderings are circular, i.e. abcd == bcda): // // (1) a2 b2 b0 a0: A contains B // (2) a2 a0 b0 b2: B contains A // (3) a2 a0 b2 b0: A and B are disjoint // (4) a2 b0 a0 b2: A and B intersect in one wedge // (5) a2 b2 a0 b0: A and B intersect in one wedge // (6) a2 b0 b2 a0: A and B intersect in two wedges // // We do not distinguish between 4, 5, and 6. // We pay extra attention when some of the edges overlap. When edges // overlap, several of these orderings can be satisfied, and we take // the most specific. if (a0 == b0 && a2 == b2) return WEDGE_EQUALS; if (s2pred::OrderedCCW(a0, a2, b2, ab1)) { // The cases with this vertex ordering are 1, 5, and 6, // although case 2 is also possible if a2 == b2. if (s2pred::OrderedCCW(b2, b0, a0, ab1)) return WEDGE_PROPERLY_CONTAINS; // We are in case 5 or 6, or case 2 if a2 == b2. return (a2 == b2) ? WEDGE_IS_PROPERLY_CONTAINED : WEDGE_PROPERLY_OVERLAPS; } // We are in case 2, 3, or 4. if (s2pred::OrderedCCW(a0, b0, b2, ab1)) return WEDGE_IS_PROPERLY_CONTAINED; return s2pred::OrderedCCW(a0, b0, a2, ab1) ? WEDGE_IS_DISJOINT : WEDGE_PROPERLY_OVERLAPS; } } // namespace S2 s2/src/s2/s2contains_vertex_query.h0000644000176200001440000000454614530411473017002 0ustar liggesusers// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2CONTAINS_VERTEX_QUERY_H_ #define S2_S2CONTAINS_VERTEX_QUERY_H_ #include "absl/container/btree_map.h" #include "s2/s2point.h" // This class determines whether a polygon contains one of its vertices given // the edges incident to that vertex. The result is +1 if the vertex is // contained, -1 if it is not contained, and 0 if the incident edges consist // of matched sibling pairs (in which case the result cannot be determined // locally). // // Point containment is defined according to the "semi-open" boundary model // (see S2VertexModel), which means that if several polygons tile the region // around a vertex, then exactly one of those polygons contains that vertex. // // This class is not thread-safe. To use it in parallel, each thread should // construct its own instance (this is not expensive). class S2ContainsVertexQuery { public: // "target" is the vertex whose containment will be determined. explicit S2ContainsVertexQuery(const S2Point& target); // Indicates that the polygon has an edge between "target" and "v" in the // given direction (+1 = outgoing, -1 = incoming, 0 = degenerate). void AddEdge(const S2Point& v, int direction); // Returns +1 if the vertex is contained, -1 if it is not contained, and 0 // if the incident edges consisted of matched sibling pairs. int ContainsSign(); private: S2Point target_; absl::btree_map edge_map_; }; ////////////////// Implementation details follow //////////////////// inline S2ContainsVertexQuery::S2ContainsVertexQuery(const S2Point& target) : target_(target) { } inline void S2ContainsVertexQuery::AddEdge(const S2Point& v, int direction) { edge_map_[v] += direction; } #endif // S2_S2CONTAINS_VERTEX_QUERY_H_ s2/src/s2/s2point_index.h0000644000176200001440000002447314530411473014663 0ustar liggesusers// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2POINT_INDEX_H_ #define S2_S2POINT_INDEX_H_ #include #include #include "absl/container/btree_map.h" #include "s2/s2cell_id.h" // S2PointIndex maintains an index of points sorted by leaf S2CellId. Each // point can optionally store auxiliary data such as an integer or pointer. // This can be used to map results back to client data structures. // // The class supports adding or removing points dynamically, and provides a // seekable iterator interface for navigating the index. // // You can use this class in conjuction with S2ClosestPointQuery to find the // closest index points to a given query point. For example: // // void Test(const vector& index_points, // const vector& target_points) { // // The template argument allows auxiliary data to be attached to each // // point (in this case, the array index). // S2PointIndex index; // for (int i = 0; i < index_points.size(); ++i) { // index.Add(index_points[i], i); // } // S2ClosestPointQuery query(&index); // query.mutable_options()->set_max_results(5); // for (const S2Point& target_point : target_points) { // S2ClosestPointQueryPointTarget target(target_point); // for (const auto& result : query.FindClosestPoints(&target)) { // // The Result class contains the following methods: // // distance() is the distance to the target. // // point() is the indexed point. // // data() is the auxiliary data. // DoSomething(target_point, result); // } // } // } // // The Data argument defaults to an empty class, which uses no additional // space beyond the S2Point itself. In this case the Data argument is // required. For example: // // S2PointIndex<> index; // index.Add(point); // // Points can be added or removed from the index at any time by calling Add() // or Remove(). However when the index is modified, you must call Init() on // each iterator before using it again (or simply create a new iterator). // // index.Add(new_point, 123456); // it.Init(&index); // it.Seek(target.range_min()); // // You can also access the index directly using the iterator interface. For // example, here is how to iterate through all the points in a given S2CellId // "target_id": // // S2PointIndex::Iterator it(&index); // it.Seek(target_id.range_min()); // for (; !it.done() && it.id() <= target_id.range_max(); it.Next()) { // DoSomething(it.id(), it.point(), it.data()); // } // // TODO(ericv): Consider adding an S2PointIndexRegion class, which could be // used to efficiently compute coverings of a collection of S2Points. // // REQUIRES: "Data" has default and copy constructors. // REQUIRES: "Data" has operator== and operator<. template /*empty class*/> class S2PointIndex { public: // PointData is essentially std::pair with named fields. It stores an // S2Point and its associated data, taking advantage of the "empty base // optimization" to ensure that no extra space is used when Data is empty. class PointData { public: PointData() {} // Needed by STL PointData(const S2Point& point, const Data& data) : tuple_(point, data) {} const S2Point& point() const { return std::get<0>(tuple_); } const Data& data() const { return std::get<1>(tuple_); } friend bool operator==(const PointData& x, const PointData& y) { return x.tuple_ == y.tuple_; } friend bool operator<(const PointData& x, const PointData& y) { return x.tuple_ < y.tuple_; } private: // Note that std::tuple has special template magic to ensure that Data // doesn't take up any space when it is empty. (This is not true if you // simply declare a member of type Data.) std::tuple tuple_; }; // Default constructor. S2PointIndex(); // Returns the number of points in the index. int num_points() const; // Adds the given point to the index. Invalidates all iterators. void Add(const S2Point& point, const Data& data); void Add(const PointData& point_data); // Convenience function for the case when Data is an empty class. void Add(const S2Point& point); // Removes the given point from the index. Both the "point" and "data" // fields must match the point to be removed. Returns false if the given // point was not present. Invalidates all iterators. bool Remove(const S2Point& point, const Data& data); bool Remove(const PointData& point_data); // Convenience function for the case when Data is an empty class. void Remove(const S2Point& point); // Resets the index to its original empty state. Invalidates all iterators. void Clear(); private: // Defined here because the Iterator class below uses it. using Map = absl::btree_multimap; public: class Iterator { public: // Default constructor; must be followed by a call to Init(). Iterator(); // Convenience constructor that calls Init(). explicit Iterator(const S2PointIndex* index); // Initializes an iterator for the given S2PointIndex. If the index is // non-empty, the iterator is positioned at the first cell. // // This method may be called multiple times, e.g. to make an iterator // valid again after the index is modified. void Init(const S2PointIndex* index); // The S2CellId for the current index entry. // REQUIRES: !done() S2CellId id() const; // The point associated with the current index entry. // REQUIRES: !done() const S2Point& point() const; // The client-supplied data associated with the current index entry. // REQUIRES: !done() const Data& data() const; // The (S2Point, data) pair associated with the current index entry. const PointData& point_data() const; // Returns true if the iterator is positioned past the last index entry. bool done() const; // Positions the iterator at the first index entry (if any). void Begin(); // Positions the iterator so that done() is true. void Finish(); // Advances the iterator to the next index entry. // REQUIRES: !done() void Next(); // If the iterator is already positioned at the beginning, returns false. // Otherwise positions the iterator at the previous entry and returns true. bool Prev(); // Positions the iterator at the first entry with id() >= target, or at the // end of the index if no such entry exists. void Seek(S2CellId target); private: const Map* map_; typename Map::const_iterator iter_, end_; }; private: friend class Iterator; Map map_; S2PointIndex(const S2PointIndex&) = delete; void operator=(const S2PointIndex&) = delete; }; ////////////////// Implementation details follow //////////////////// template S2PointIndex::S2PointIndex() { } template inline int S2PointIndex::num_points() const { return map_.size(); } template void S2PointIndex::Add(const PointData& point_data) { S2CellId id(point_data.point()); map_.insert(std::make_pair(id, point_data)); } template void S2PointIndex::Add(const S2Point& point, const Data& data) { Add(PointData(point, data)); } template void S2PointIndex::Add(const S2Point& point) { static_assert(std::is_empty::value, "Data must be empty"); Add(point, {}); } template bool S2PointIndex::Remove(const PointData& point_data) { S2CellId id(point_data.point()); for (typename Map::iterator it = map_.lower_bound(id), end = map_.end(); it != end && it->first == id; ++it) { if (it->second == point_data) { map_.erase(it); return true; } } return false; } template bool S2PointIndex::Remove(const S2Point& point, const Data& data) { return Remove(PointData(point, data)); } template void S2PointIndex::Remove(const S2Point& point) { static_assert(std::is_empty::value, "Data must be empty"); Remove(point, {}); } template void S2PointIndex::Clear() { map_.clear(); } template inline S2PointIndex::Iterator::Iterator() : map_(nullptr) { } template inline S2PointIndex::Iterator::Iterator( const S2PointIndex* index) { Init(index); } template inline void S2PointIndex::Iterator::Init( const S2PointIndex* index) { map_ = &index->map_; iter_ = map_->begin(); end_ = map_->end(); } template inline S2CellId S2PointIndex::Iterator::id() const { S2_DCHECK(!done()); return iter_->first; } template inline const S2Point& S2PointIndex::Iterator::point() const { S2_DCHECK(!done()); return iter_->second.point(); } template inline const Data& S2PointIndex::Iterator::data() const { S2_DCHECK(!done()); return iter_->second.data(); } template inline const typename S2PointIndex::PointData& S2PointIndex::Iterator::point_data() const { S2_DCHECK(!done()); return iter_->second; } template inline bool S2PointIndex::Iterator::done() const { return iter_ == end_; } template inline void S2PointIndex::Iterator::Begin() { iter_ = map_->begin(); } template inline void S2PointIndex::Iterator::Finish() { iter_ = end_; } template inline void S2PointIndex::Iterator::Next() { S2_DCHECK(!done()); ++iter_; } template inline bool S2PointIndex::Iterator::Prev() { if (iter_ == map_->begin()) return false; --iter_; return true; } template inline void S2PointIndex::Iterator::Seek(S2CellId target) { iter_ = map_->lower_bound(target); } #endif // S2_S2POINT_INDEX_H_ s2/src/s2/s2r2rect.h0000644000176200001440000002675614530411473013552 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2R2RECT_H_ #define S2_S2R2RECT_H_ #include #include "s2/base/logging.h" #include "s2/_fp_contract_off.h" #include "s2/r1interval.h" #include "s2/r2.h" #include "s2/r2rect.h" #include "s2/s1angle.h" #include "s2/s2region.h" class Decoder; class Encoder; class R1Interval; class S2Cap; class S2Cell; class S2CellId; class S2LatLngRect; // This class is a stopgap measure that allows some of the S2 spherical // geometry machinery to be applied to planar geometry. An S2R2Rect // represents a closed axis-aligned rectangle in the (x,y) plane (an R2Rect), // but it also happens to be a subtype of S2Region, which means that you can // use an S2RegionCoverer to approximate it as a collection of S2CellIds. // // With respect to the S2Cell decomposition, an S2R2Rect is interpreted as a // region of (s,t)-space on face 0. In particular, the rectangle [0,1]x[0,1] // corresponds to the S2CellId that covers all of face 0. This means that // only rectangles that are subsets of [0,1]x[0,1] can be approximated using // the S2RegionCoverer interface. // // The S2R2Rect class is also a convenient way to find the (s,t)-region // covered by a given S2CellId (see the FromCell and FromCellId methods). // // TODO(ericv): If the geometry library is extended to have better support // for planar geometry, then this class should no longer be necessary. // // This class is intended to be copied by value as desired. It uses // the default copy constructor and assignment operator, however it is // not a "plain old datatype" (POD) because it has virtual functions. class S2R2Rect final : public S2Region { public: // Construct a rectangle from an R2Rect. explicit S2R2Rect(const R2Rect& rect); // Construct a rectangle from the given lower-left and upper-right points. S2R2Rect(const R2Point& lo, const R2Point& hi); // Construct a rectangle from the given intervals in x and y. The two // intervals must either be both empty or both non-empty. S2R2Rect(const R1Interval& x, const R1Interval& y); // The canonical empty rectangle. Use is_empty() to test for empty // rectangles, since they have more than one representation. static S2R2Rect Empty(); // Construct a rectangle that corresponds to the boundary of the given cell // is (s,t)-space. Such rectangles are always a subset of [0,1]x[0,1]. static S2R2Rect FromCell(const S2Cell& cell); static S2R2Rect FromCellId(S2CellId id); // Construct a rectangle from a center point and size in each dimension. // Both components of size should be non-negative, i.e. this method cannot // be used to create an empty rectangle. static S2R2Rect FromCenterSize(const R2Point& center, const R2Point& size); // Convenience method to construct a rectangle containing a single point. static S2R2Rect FromPoint(const R2Point& p); // Convenience method to construct the minimal bounding rectangle containing // the two given points. This is equivalent to starting with an empty // rectangle and calling AddPoint() twice. Note that it is different than // the S2R2Rect(lo, hi) constructor, where the first point is always // used as the lower-left corner of the resulting rectangle. static S2R2Rect FromPointPair(const R2Point& p1, const R2Point& p2); // Accessor methods. const R1Interval& x() const; const R1Interval& y() const; R2Point lo() const; R2Point hi() const; // Methods that allow the S2R2Rect to be accessed as a vector. const R1Interval& operator[](int i) const; R1Interval& operator[](int i); // Return true if the rectangle is valid, which essentially just means // that if the bound for either axis is empty then both must be. bool is_valid() const; // Return true if the rectangle is empty, i.e. it contains no points at all. bool is_empty() const; // Return the k-th vertex of the rectangle (k = 0,1,2,3) in CCW order. // Vertex 0 is in the lower-left corner. For convenience, the argument is // reduced modulo 4 to the range [0..3]. R2Point GetVertex(int k) const; // Return the vertex in direction "i" along the x-axis (0=left, 1=right) and // direction "j" along the y-axis (0=down, 1=up). Equivalently, return the // vertex constructed by selecting endpoint "i" of the x-interval (0=lo, // 1=hi) and vertex "j" of the y-interval. R2Point GetVertex(int i, int j) const; // Return the center of the rectangle in (x,y)-space // (in general this is not the center of the region on the sphere). R2Point GetCenter() const; // Return the width and height of this rectangle in (x,y)-space. Empty // rectangles have a negative width and height. R2Point GetSize() const; // Return true if the rectangle contains the given point. Note that // rectangles are closed regions, i.e. they contain their boundary. bool Contains(const R2Point& p) const; // Return true if and only if the given point is contained in the interior // of the region (i.e. the region excluding its boundary). bool InteriorContains(const R2Point& p) const; // Return true if and only if the rectangle contains the given other // rectangle. bool Contains(const S2R2Rect& other) const; // Return true if and only if the interior of this rectangle contains all // points of the given other rectangle (including its boundary). bool InteriorContains(const S2R2Rect& other) const; // Return true if this rectangle and the given other rectangle have any // points in common. bool Intersects(const S2R2Rect& other) const; // Return true if and only if the interior of this rectangle intersects // any point (including the boundary) of the given other rectangle. bool InteriorIntersects(const S2R2Rect& other) const; // Increase the size of the bounding rectangle to include the given point. // The rectangle is expanded by the minimum amount possible. void AddPoint(const R2Point& p); // Return the closest point in the rectangle to the given point "p". // The rectangle must be non-empty. R2Point Project(const R2Point& p) const; // Return a rectangle that has been expanded on each side in the x-direction // by margin.x(), and on each side in the y-direction by margin.y(). If // either margin is negative, then shrink the interval on the corresponding // sides instead. The resulting rectangle may be empty. Any expansion of // an empty rectangle remains empty. S2R2Rect Expanded(const R2Point& margin) const; S2R2Rect Expanded(double margin) const; // Return the smallest rectangle containing the union of this rectangle and // the given rectangle. S2R2Rect Union(const S2R2Rect& other) const; // Return the smallest rectangle containing the intersection of this // rectangle and the given rectangle. S2R2Rect Intersection(const S2R2Rect& other) const; // Return true if two rectangles contains the same set of points. bool operator==(const S2R2Rect& other) const; // Return true if the x- and y-intervals of the two rectangles are the same // up to the given tolerance (see r1interval.h for details). bool ApproxEquals(const S2R2Rect& other, S1Angle max_error = S1Angle::Radians(1e-15)) const; // Return the unit-length S2Point corresponding to the given point "p" in // the (s,t)-plane. "p" need not be restricted to the range [0,1]x[0,1]. static S2Point ToS2Point(const R2Point& p); //////////////////////////////////////////////////////////////////////// // S2Region interface (see s2region.h for details): S2R2Rect* Clone() const override; S2Cap GetCapBound() const override; S2LatLngRect GetRectBound() const override; bool Contains(const S2Point& p) const override; bool Contains(const S2Cell& cell) const override; bool MayIntersect(const S2Cell& cell) const override; private: R2Rect rect_; }; std::ostream& operator<<(std::ostream& os, const S2R2Rect& r); ////////////////// Implementation details follow //////////////////// inline S2R2Rect::S2R2Rect(const R2Rect& rect) : rect_(rect) {} inline S2R2Rect::S2R2Rect(const R2Point& lo, const R2Point& hi) : rect_(lo, hi) {} inline S2R2Rect::S2R2Rect(const R1Interval& x, const R1Interval& y) : rect_(x, y) {} inline S2R2Rect S2R2Rect::FromCenterSize(const R2Point& center, const R2Point& size) { return S2R2Rect(R2Rect::FromCenterSize(center, size)); } inline S2R2Rect S2R2Rect::FromPoint(const R2Point& p) { return S2R2Rect(R2Rect::FromPoint(p)); } inline S2R2Rect S2R2Rect::FromPointPair(const R2Point& p1, const R2Point& p2) { return S2R2Rect(R2Rect::FromPointPair(p1, p2)); } inline const R1Interval& S2R2Rect::x() const { return rect_.x(); } inline const R1Interval& S2R2Rect::y() const { return rect_.y(); } inline R2Point S2R2Rect::lo() const { return rect_.lo(); } inline R2Point S2R2Rect::hi() const { return rect_.hi(); } inline const R1Interval& S2R2Rect::operator[](int i) const { return rect_[i]; } inline R1Interval& S2R2Rect::operator[](int i) { return rect_[i]; } inline S2R2Rect S2R2Rect::Empty() { return S2R2Rect(R2Rect::Empty()); } inline bool S2R2Rect::is_valid() const { return rect_.is_valid(); } inline bool S2R2Rect::is_empty() const { return rect_.is_empty(); } inline R2Point S2R2Rect::GetVertex(int k) const { return rect_.GetVertex(k); } inline R2Point S2R2Rect::GetVertex(int i, int j) const { return rect_.GetVertex(i, j); } inline R2Point S2R2Rect::GetCenter() const { return rect_.GetCenter(); } inline R2Point S2R2Rect::GetSize() const { return rect_.GetSize(); } inline bool S2R2Rect::Contains(const R2Point& p) const { return rect_.Contains(p); } inline bool S2R2Rect::InteriorContains(const R2Point& p) const { return rect_.InteriorContains(p); } inline bool S2R2Rect::Contains(const S2R2Rect& other) const { return rect_.Contains(other.rect_); } inline bool S2R2Rect::InteriorContains(const S2R2Rect& other) const { return rect_.InteriorContains(other.rect_); } inline bool S2R2Rect::Intersects(const S2R2Rect& other) const { return rect_.Intersects(other.rect_); } inline bool S2R2Rect::InteriorIntersects(const S2R2Rect& other) const { return rect_.InteriorIntersects(other.rect_); } inline void S2R2Rect::AddPoint(const R2Point& p) { rect_.AddPoint(p); } inline R2Point S2R2Rect::Project(const R2Point& p) const { return rect_.Project(p); } inline S2R2Rect S2R2Rect::Expanded(const R2Point& margin) const { return S2R2Rect(rect_.Expanded(margin)); } inline S2R2Rect S2R2Rect::Expanded(double margin) const { return S2R2Rect(rect_.Expanded(margin)); } inline S2R2Rect S2R2Rect::Union(const S2R2Rect& other) const { return S2R2Rect(rect_.Union(other.rect_)); } inline S2R2Rect S2R2Rect::Intersection(const S2R2Rect& other) const { return S2R2Rect(rect_.Intersection(other.rect_)); } inline bool S2R2Rect::operator==(const S2R2Rect& other) const { return rect_ == other.rect_; } inline bool S2R2Rect::ApproxEquals(const S2R2Rect& other, S1Angle max_error) const { return rect_.ApproxEquals(other.rect_, max_error.radians()); } #endif // S2_S2R2RECT_H_ s2/src/s2/s2region_term_indexer.h0000644000176200001440000003162714530411473016372 0ustar liggesusers// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // S2RegionTermIndexer is a helper class for adding spatial data to an // information retrieval system. Such systems work by converting documents // into a collection of "index terms" (e.g., representing words or phrases), // and then building an "inverted index" that maps each term to a list of // documents (and document positions) where that term occurs. // // This class deals with the problem of converting spatial data into index // terms, which can then be indexed along with the other document information. // // Spatial data is represented using the S2Region type. Useful S2Region // subtypes include: // // S2Cap // - a disc-shaped region // // S2LatLngRect // - a rectangle in latitude-longitude coordinates // // S2Polyline // - a polyline // // S2Polygon // - a polygon, possibly with multiple holes and/or shells // // S2CellUnion // - a region approximated as a collection of S2CellIds // // S2ShapeIndexRegion // - an arbitrary collection of points, polylines, and polygons // // S2ShapeIndexBufferedRegion // - like the above, but expanded by a given radius // // S2RegionUnion, S2RegionIntersection // - the union or intersection of arbitrary other regions // // So for example, if you want to query documents that are within 500 meters // of a polyline, you could use an S2ShapeIndexBufferedRegion containing the // polyline with a radius of 500 meters. // // Example usage: // // // This class is intended to be used with an external key-value store, // // but for this example will we use an unordered_map. The key is an // // index term, and the value is a set of document ids. // std::unordered_map> index; // // // Create an indexer that uses up to 10 cells to approximate each region. // S2RegionTermIndexer::Options options; // options.set_max_cells(10); // S2RegionTermIndexer indexer(options); // // // For this example, we index a disc-shaped region with a 10km radius. // S2LatLng center = S2LatLng::FromDegrees(44.1, -56.235); // S1Angle radius = S2Earth::ToAngle(util::units::Kilometers(10.0)); // S2Cap cap(center.ToPoint(), radius); // // // Add the terms for this disc-shaped region to the index. // for (const auto& term : indexer.GetIndexTerms(cap)) { // index[term].push_back(kSomeDocumentId); // } // // // And now at query time: build a latitude-longitude rectangle. // S2LatLngRect rect(S2LatLng::FromDegrees(-12.1, 10.2), // S2LatLng::FromDegrees(-9.2, 120.5)); // // // Convert the query region to a set of terms, and compute the union // // of the document ids associated with those terms. // std::set doc_ids; // for (const auto& term : indexer.GetQueryTerms(rect)) { // doc_ids.insert(index[term].begin(), index[term].end()); // } // // // "doc_ids" now contains all documents that intersect the query region, // // along with some documents that nearly intersect it. The results can // // be further pruned if desired by retrieving the original regions that // // were indexed (i.e., the document contents) and checking for exact // // intersection with the query region. #ifndef S2_S2REGION_TERM_INDEXER_H_ #define S2_S2REGION_TERM_INDEXER_H_ #include #include #include "s2/s2cell_union.h" #include "s2/s2region.h" #include "s2/s2region_coverer.h" #include "absl/strings/string_view.h" class S2RegionTermIndexer { public: // The following parameters control the tradeoffs between index size, query // size, and accuracy (see s2region_coverer.h for details). // // IMPORTANT: You must use the same values for min_level(), max_level(), and // level_mod() for both indexing and queries, otherwise queries will return // incorrect results. However, max_cells() can be changed as often as // desired -- you can even change this parameter for every region. class Options : public S2RegionCoverer::Options { public: Options(); ///////////////// Options Inherited From S2RegionCoverer //////////////// // max_cells() controls the maximum number of cells when approximating // each region. This parameter value may be changed as often as desired // (using mutable_options(), see below), e.g. to approximate some regions // more accurately than others. // // Increasing this value during indexing will make indexes more accurate // but larger. Increasing this value for queries will make queries more // accurate but slower. (See s2region_coverer.h for details on how this // parameter affects accuracy.) For example, if you don't mind large // indexes but want fast serving, it might be reasonable to set // max_cells() == 100 during indexing and max_cells() == 8 for queries. // // DEFAULT: 8 (coarse approximations) using S2RegionCoverer::Options::max_cells; using S2RegionCoverer::Options::set_max_cells; // min_level() and max_level() control the minimum and maximum size of the // S2Cells used to approximate regions. Setting these parameters // appropriately can reduce the size of the index and speed up queries by // reducing the number of terms needed. For example, if you know that // your query regions will rarely be less than 100 meters in width, then // you could set max_level() as follows: // // options.set_max_level(S2::kAvgEdge.GetClosestLevel( // S2Earth::MetersToRadians(100))); // // This restricts the index to S2Cells that are approximately 100 meters // across or larger. Similar, if you know that query regions will rarely // be larger than 1000km across, then you could set min_level() similarly. // // If min_level() is set too high, then large regions may generate too // many query terms. If max_level() is set too low, then small query // regions will not be able to discriminate which regions they intersect // very precisely and may return many more candidates than necessary. // // If you have no idea about the scale of the regions being queried, // it is perfectly fine to set min_level() == 0 and max_level() == 30 // (== S2::kMaxLevel). The only drawback is that may result in a larger // index and slower queries. // // The default parameter values are suitable for query regions ranging // from about 100 meters to 3000 km across. // // DEFAULT: 4 (average cell width == 600km) using S2RegionCoverer::Options::min_level; using S2RegionCoverer::Options::set_min_level; // DEFAULT: 16 (average cell width == 150m) using S2RegionCoverer::Options::max_level; using S2RegionCoverer::Options::set_max_level; // Setting level_mod() to a value greater than 1 increases the effective // branching factor of the S2Cell hierarchy by skipping some levels. For // example, if level_mod() == 2 then every second level is skipped (which // increases the effective branching factor to 16). You might want to // consider doing this if your query regions are typically very small // (e.g., single points) and you don't mind increasing the index size // (since skipping levels will reduce the accuracy of cell coverings for a // given max_cells() limit). // // DEFAULT: 1 (don't skip any cell levels) using S2RegionCoverer::Options::level_mod; using S2RegionCoverer::Options::set_level_mod; // If your index will only contain points (rather than regions), be sure // to set this flag. This will generate smaller and faster queries that // are specialized for the points-only case. // // With the default quality settings, this flag reduces the number of // query terms by about a factor of two. (The improvement gets smaller // as max_cells() is increased, but there is really no reason not to use // this flag if your index consists entirely of points.) // // DEFAULT: false bool index_contains_points_only() const { return points_only_; } void set_index_contains_points_only(bool value) { points_only_ = value; } // If true, the index will be optimized for space rather than for query // time. With the default quality settings, this flag reduces the number // of index terms and increases the number of query terms by the same // factor (approximately 1.3). The factor increases up to a limiting // ratio of 2.0 as max_cells() is increased. // // CAVEAT: This option has no effect if the index contains only points. // // DEFAULT: false bool optimize_for_space() const { return optimize_for_space_; } void set_optimize_for_space(bool value) { optimize_for_space_ = value; } // A non-alphanumeric character that is used internally to distinguish // between two different types of terms (by adding this character). // // REQUIRES: "ch" is non-alphanumeric. // DEFAULT: '$' const std::string& marker() const { return marker_; } char marker_character() const { return marker_[0]; } void set_marker_character(char ch); private: bool points_only_ = false; bool optimize_for_space_ = false; std::string marker_ = std::string(1, '$'); }; // Default constructor. Options can be set using mutable_options(). S2RegionTermIndexer(); ~S2RegionTermIndexer(); // Constructs an S2RegionTermIndexer with the given options. explicit S2RegionTermIndexer(const Options& options); // S2RegionTermIndexer is movable but not copyable. S2RegionTermIndexer(const S2RegionTermIndexer&) = delete; S2RegionTermIndexer& operator=(const S2RegionTermIndexer&) = delete; S2RegionTermIndexer(S2RegionTermIndexer&&); S2RegionTermIndexer& operator=(S2RegionTermIndexer&&); // Returns the current options. Options can be modifed between calls. const Options& options() const { return options_; } Options* mutable_options() { return &options_; } // Converts the given region into a set of terms for indexing. Terms // consist of lowercase letters, numbers, '$', and an optional prefix. // // "prefix" is a unique prefix used to distinguish S2 terms from other terms // in the repository. The prefix may also be used to index documents with // multiple types of location information (e.g. store footprint, entrances, // parking lots, etc). The prefix should be kept short since it is // prepended to every term. std::vector GetIndexTerms(const S2Region& region, absl::string_view prefix); // Converts a given query region into a set of terms. If you compute the // union of all the documents associated with these terms, the result will // include all documents whose index region intersects the query region. // // "prefix" should match the corresponding value used when indexing. std::vector GetQueryTerms(const S2Region& region, absl::string_view prefix); // Convenience methods that accept an S2Point rather than S2Region. (These // methods are also faster.) // // Note that you can index an S2LatLng by converting it to an S2Point first: // auto terms = GetIndexTerms(S2Point(latlng), ...); std::vector GetIndexTerms(const S2Point& point, absl::string_view prefix); std::vector GetQueryTerms(const S2Point& point, absl::string_view prefix); // Low-level methods that accept an S2CellUnion covering of the region to be // indexed or queried. // // REQUIRES: "covering" satisfies the S2RegionCoverer::Options for this // class (i.e., max_cells, min_level, max_level, and level_mod). // // If you have a covering that was computed using different options, then // you can either call the regular S2Region methods (since S2CellUnion is a // type of S2Region), or "canonicalize" the covering first by calling // S2RegionCoverer::CanonicalizeCovering() with the same options. std::vector GetIndexTermsForCanonicalCovering( const S2CellUnion& covering, absl::string_view prefix); std::vector GetQueryTermsForCanonicalCovering( const S2CellUnion& covering, absl::string_view prefix); private: enum TermType { ANCESTOR, COVERING }; std::string GetTerm(TermType term_type, const S2CellId& id, absl::string_view prefix) const; Options options_; S2RegionCoverer coverer_; }; #endif // S2_S2REGION_TERM_INDEXER_H_ s2/src/s2/s2convex_hull_query.h0000644000176200001440000001016214530411473016104 0ustar liggesusers// Copyright 2015 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2CONVEX_HULL_QUERY_H_ #define S2_S2CONVEX_HULL_QUERY_H_ #include #include #include "s2/_fp_contract_off.h" #include "s2/s2cap.h" #include "s2/s2latlng_rect.h" #include "s2/s2loop.h" #include "s2/s2polygon.h" #include "s2/s2polyline.h" // S2ConvexHullQuery builds the convex hull of any collection of points, // polylines, loops, and polygons. It returns a single convex loop. // // The convex hull is defined as the smallest convex region on the sphere that // contains all of your input geometry. Recall that a region is "convex" if // for every pair of points inside the region, the straight edge between them // is also inside the region. In our case, a "straight" edge is a geodesic, // i.e. the shortest path on the sphere between two points. // // Containment of input geometry is defined as follows: // // - Each input loop and polygon is contained by the convex hull exactly // (i.e., according to S2Polygon::Contains(S2Polygon)). // // - Each input point is either contained by the convex hull or is a vertex // of the convex hull. (Recall that S2Loops do not necessarily contain their // vertices.) // // - For each input polyline, the convex hull contains all of its vertices // according to the rule for points above. (The definition of convexity // then ensures that the convex hull also contains the polyline edges.) // // To use this class, call the Add*() methods to add your input geometry, and // then call GetConvexHull(). Note that GetConvexHull() does *not* reset the // state; you can continue adding geometry if desired and compute the convex // hull again. If you want to start from scratch, simply declare a new // S2ConvexHullQuery object (they are cheap to create). // // This class is not thread-safe. There are no "const" methods. class S2ConvexHullQuery { public: S2ConvexHullQuery(); // Add a point to the input geometry. void AddPoint(const S2Point& point); // Add a polyline to the input geometry. void AddPolyline(const S2Polyline& polyline); // Add a loop to the input geometry. void AddLoop(const S2Loop& loop); // Add a polygon to the input geometry. void AddPolygon(const S2Polygon& polygon); // Compute a bounding cap for the input geometry provided. // // Note that this method does not clear the geometry; you can continue // adding to it and call this method again if desired. S2Cap GetCapBound(); // Compute the convex hull of the input geometry provided. // // If there is no geometry, this method returns an empty loop containing no // points (see S2Loop::is_empty()). // // If the geometry spans more than half of the sphere, this method returns a // full loop containing the entire sphere (see S2Loop::is_full()). // // If the geometry contains 1 or 2 points, or a single edge, this method // returns a very small loop consisting of three vertices (which are a // superset of the input vertices). // // Note that this method does not clear the geometry; you can continue // adding to it and call this method again if desired. std::unique_ptr GetConvexHull(); private: void GetMonotoneChain(std::vector* output); std::unique_ptr GetSinglePointLoop(const S2Point& p); std::unique_ptr GetSingleEdgeLoop(const S2Point& a, const S2Point& b); S2LatLngRect bound_; std::vector points_; S2ConvexHullQuery(const S2ConvexHullQuery&) = delete; void operator=(const S2ConvexHullQuery&) = delete; }; #endif // S2_S2CONVEX_HULL_QUERY_H_ s2/src/s2/s2shape_index_measures.cc0000644000176200001440000000465714530411473016676 0ustar liggesusers// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2shape_index_measures.h" #include "s2/s2shape_measures.h" namespace S2 { int GetDimension(const S2ShapeIndex& index) { int dim = -1; for (int i = 0; i < index.num_shape_ids(); ++i) { S2Shape* shape = index.shape(i); if (shape) dim = std::max(dim, shape->dimension()); } return dim; } int GetNumPoints(const S2ShapeIndex& index) { int count = 0; for (int i = 0; i < index.num_shape_ids(); ++i) { S2Shape* shape = index.shape(i); if (shape && shape->dimension() == 0) { count += shape->num_edges(); } } return count; } S1Angle GetLength(const S2ShapeIndex& index) { S1Angle length; for (int i = 0; i < index.num_shape_ids(); ++i) { S2Shape* shape = index.shape(i); if (shape) length += S2::GetLength(*shape); } return length; } S1Angle GetPerimeter(const S2ShapeIndex& index) { S1Angle perimeter; for (int i = 0; i < index.num_shape_ids(); ++i) { S2Shape* shape = index.shape(i); if (shape) perimeter += S2::GetPerimeter(*shape); } return perimeter; } double GetArea(const S2ShapeIndex& index) { double area = 0; for (int i = 0; i < index.num_shape_ids(); ++i) { S2Shape* shape = index.shape(i); if (shape) area += S2::GetArea(*shape); } return area; } double GetApproxArea(const S2ShapeIndex& index) { double area = 0; for (int i = 0; i < index.num_shape_ids(); ++i) { S2Shape* shape = index.shape(i); if (shape) area += S2::GetApproxArea(*shape); } return area; } S2Point GetCentroid(const S2ShapeIndex& index) { int dim = GetDimension(index); S2Point centroid; for (int i = 0; i < index.num_shape_ids(); ++i) { S2Shape* shape = index.shape(i); if (shape && shape->dimension() == dim) { centroid += S2::GetCentroid(*shape); } } return centroid; } } // namespace S2 s2/src/s2/s2shape_index.h0000644000176200001440000007111514530411473014625 0ustar liggesusers// Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // S2ShapeIndex is an abstract base class for indexing polygonal geometry in // memory. The main documentation is with the class definition below. // (Some helper classes are defined first.) #ifndef S2_S2SHAPE_INDEX_H_ #define S2_S2SHAPE_INDEX_H_ #include #include #include #include #include #include #include #include "s2/base/integral_types.h" #include "s2/base/logging.h" #include "s2/base/mutex.h" #include "s2/base/spinlock.h" #include "s2/_fp_contract_off.h" #include "s2/s2cell_id.h" #include "s2/s2pointutil.h" #include "s2/s2shape.h" #include "absl/base/macros.h" #include "absl/base/thread_annotations.h" #include "absl/memory/memory.h" #include "s2/util/gtl/compact_array.h" class R1Interval; class S2PaddedCell; // S2ClippedShape represents the part of a shape that intersects an S2Cell. // It consists of the set of edge ids that intersect that cell, and a boolean // indicating whether the center of the cell is inside the shape (for shapes // that have an interior). // // Note that the edges themselves are not clipped; we always use the original // edges for intersection tests so that the results will be the same as the // original shape. class S2ClippedShape { public: // The shape id of the clipped shape. int shape_id() const; // Returns true if the center of the S2CellId is inside the shape. Returns // false for shapes that do not have an interior. bool contains_center() const; // The number of edges that intersect the S2CellId. int num_edges() const; // Returns the edge id of the given edge in this clipped shape. Edges are // sorted in increasing order of edge id. // // REQUIRES: 0 <= i < num_edges() int edge(int i) const; // Returns true if the clipped shape contains the given edge id. bool ContainsEdge(int id) const; private: // This class may be copied by value, but note that it does *not* own its // underlying data. (It is owned by the containing S2ShapeIndexCell.) friend class MutableS2ShapeIndex; friend class S2ShapeIndexCell; friend class S2Stats; // Internal methods are documented with their definition. void Init(int32 shape_id, int32 num_edges); void Destruct(); bool is_inline() const; void set_contains_center(bool contains_center); void set_edge(int i, int edge); // All fields are packed into 16 bytes (assuming 64-bit pointers). Up to // two edge ids are stored inline; this is an important optimization for // clients that use S2Shapes consisting of a single edge. int32 shape_id_; uint32 contains_center_ : 1; // shape contains the cell center uint32 num_edges_ : 31; // If there are more than two edges, this field holds a pointer. // Otherwise it holds an array of edge ids. union { int32* edges_; // Owned by the containing S2ShapeIndexCell. std::array inline_edges_; }; }; // S2ShapeIndexCell stores the index contents for a particular S2CellId. // It consists of a set of clipped shapes. class S2ShapeIndexCell { public: S2ShapeIndexCell() {} ~S2ShapeIndexCell(); // Returns the number of clipped shapes in this cell. int num_clipped() const { return shapes_.size(); } // Returns the clipped shape at the given index. Shapes are kept sorted in // increasing order of shape id. // // REQUIRES: 0 <= i < num_clipped() const S2ClippedShape& clipped(int i) const { return shapes_[i]; } // Returns a pointer to the clipped shape corresponding to the given shape, // or nullptr if the shape does not intersect this cell. const S2ClippedShape* find_clipped(const S2Shape* shape) const; const S2ClippedShape* find_clipped(int shape_id) const; // Convenience method that returns the total number of edges in all clipped // shapes. int num_edges() const; // Appends an encoded representation of the S2ShapeIndexCell to "encoder". // "num_shape_ids" should be set to index.num_shape_ids(); this information // allows the encoding to be more compact in some cases. // // REQUIRES: "encoder" uses the default constructor, so that its buffer // can be enlarged as necessary by calling Ensure(int). void Encode(int num_shape_ids, Encoder* encoder) const; // Decodes an S2ShapeIndexCell, returning true on success. // "num_shape_ids" should be set to index.num_shape_ids(). bool Decode(int num_shape_ids, Decoder* decoder); private: friend class MutableS2ShapeIndex; friend class EncodedS2ShapeIndex; friend class S2Stats; // Internal methods are documented with their definitions. S2ClippedShape* add_shapes(int n); static void EncodeEdges(const S2ClippedShape& clipped, Encoder* encoder); static bool DecodeEdges(int num_edges, S2ClippedShape* clipped, Decoder* decoder); using S2ClippedShapeSet = gtl::compact_array; S2ClippedShapeSet shapes_; S2ShapeIndexCell(const S2ShapeIndexCell&) = delete; void operator=(const S2ShapeIndexCell&) = delete; }; // S2ShapeIndex is an abstract base class for indexing polygonal geometry in // memory. The objects in the index are known as "shapes", and may consist of // points, polylines, and/or polygons, possibly overlapping. The index makes // it very fast to answer queries such as finding nearby shapes, measuring // distances, testing for intersection and containment, etc. // // Each object in the index implements the S2Shape interface. An S2Shape is a // collection of edges that optionally defines an interior. The edges do not // need to be connected, so for example an S2Shape can represent a polygon // with multiple shells and/or holes, or a set of polylines, or a set of // points. All geometry within a single S2Shape must have the same dimension, // so for example if you want to create an S2ShapeIndex containing a polyline // and 10 points, then you will need at least two different S2Shape objects. // // The most important type of S2ShapeIndex is MutableS2ShapeIndex, which // allows you to build an index incrementally by adding or removing shapes. // Soon there will also be an EncodedS2ShapeIndex type that makes it possible // to keep the index data in encoded form. Code that only needs read-only // ("const") access to an index should use the S2ShapeIndex base class as the // parameter type, so that it will work with any S2ShapeIndex subtype. For // example: // // void DoSomething(const S2ShapeIndex& index) { // ... works with MutableS2ShapeIndex or EncodedS2ShapeIndex ... // } // // There are a number of built-in classes that work with S2ShapeIndex objects. // Generally these classes accept any collection of geometry that can be // represented by an S2ShapeIndex, i.e. any combination of points, polylines, // and polygons. Such classes include: // // - S2ContainsPointQuery: returns the shape(s) that contain a given point. // // - S2ClosestEdgeQuery: returns the closest edge(s) to a given point, edge, // S2CellId, or S2ShapeIndex. // // - S2CrossingEdgeQuery: returns the edge(s) that cross a given edge. // // - S2BooleanOperation: computes boolean operations such as union, // and boolean predicates such as containment. // // - S2ShapeIndexRegion: computes approximations for a collection of geometry. // // - S2ShapeIndexBufferedRegion: computes approximations that have been // expanded by a given radius. // // Here is an example showing how to index a set of polygons and then // determine which polygon(s) contain each of a set of query points: // // void TestContainment(const vector& points, // const vector& polygons) { // MutableS2ShapeIndex index; // for (auto polygon : polygons) { // index.Add(absl::make_unique(polygon)); // } // auto query = MakeS2ContainsPointQuery(&index); // for (const auto& point : points) { // for (S2Shape* shape : query.GetContainingShapes(point)) { // S2Polygon* polygon = polygons[shape->id()]; // ... do something with (point, polygon) ... // } // } // } // // This example uses S2Polygon::Shape, which is one example of an S2Shape // object. S2Polyline and S2Loop also have nested Shape classes, and there are // additional S2Shape types defined in *_shape.h. // // Internally, an S2ShapeIndex is essentially a map from S2CellIds to the set // of shapes that intersect each S2CellId. It is adaptively refined to ensure // that no cell contains more than a small number of edges. // // In addition to implementing a shared set of virtual methods, all // S2ShapeIndex subtypes define an Iterator type with the same API. This // makes it easy to convert code that uses a particular S2ShapeIndex subtype // to instead use the abstract base class (or vice versa). You can also // choose to avoid the overhead of virtual method calls by making the // S2ShapeIndex type a template argument, like this: // // template // void DoSomething(const IndexType& index) { // for (typename IndexType::Iterator it(&index, S2ShapeIndex::BEGIN); // !it.done(); it.Next()) { // ... // } // } // // Subtypes provided by the S2 library have the same thread-safety properties // as std::vector. That is, const methods may be called concurrently from // multiple threads, and non-const methods require exclusive access to the // S2ShapeIndex. class S2ShapeIndex { protected: class IteratorBase; public: virtual ~S2ShapeIndex() {} // Returns the number of distinct shape ids in the index. This is the same // as the number of shapes provided that no shapes have ever been removed. // (Shape ids are never reused.) virtual int num_shape_ids() const = 0; // Returns a pointer to the shape with the given id, or nullptr if the shape // has been removed from the index. virtual S2Shape* shape(int id) const = 0; // Allows iterating over the indexed shapes using range-based for loops: // // for (S2Shape* shape : index) { ... } // // CAVEAT: Returns nullptr for shapes that have been removed from the index. class ShapeIterator : public std::iterator { public: ShapeIterator() = default; S2Shape* operator*() const; ShapeIterator& operator++(); ShapeIterator operator++(int); // REQUIRES: "it" and *this must reference the same S2ShapeIndex. bool operator==(ShapeIterator it) const; // REQUIRES: "it" and *this must reference the same S2ShapeIndex. bool operator!=(ShapeIterator it) const; private: friend class S2ShapeIndex; ShapeIterator(const S2ShapeIndex* index, int shape_id) : index_(index), shape_id_(shape_id) {} const S2ShapeIndex* index_ = nullptr; int shape_id_ = 0; }; ShapeIterator begin() const; ShapeIterator end() const; // Returns the number of bytes currently occupied by the index (including any // unused space at the end of vectors, etc). virtual size_t SpaceUsed() const = 0; // Minimizes memory usage by requesting that any data structures that can be // rebuilt should be discarded. This method invalidates all iterators. // // Like all non-const methods, this method is not thread-safe. virtual void Minimize() = 0; // The possible relationships between a "target" cell and the cells of the // S2ShapeIndex. If the target is an index cell or is contained by an index // cell, it is "INDEXED". If the target is subdivided into one or more // index cells, it is "SUBDIVIDED". Otherwise it is "DISJOINT". enum CellRelation { INDEXED, // Target is contained by an index cell SUBDIVIDED, // Target is subdivided into one or more index cells DISJOINT // Target does not intersect any index cells }; // When passed to an Iterator constructor, specifies whether the iterator // should be positioned at the beginning of the index (BEGIN), the end of // the index (END), or arbitrarily (UNPOSITIONED). By default iterators are // unpositioned, since this avoids an extra seek in this situation where one // of the seek methods (such as Locate) is immediately called. enum InitialPosition { BEGIN, END, UNPOSITIONED }; // A random access iterator that provides low-level access to the cells of // the index. Cells are sorted in increasing order of S2CellId. class Iterator { public: // Default constructor; must be followed by a call to Init(). Iterator() : iter_(nullptr) {} // Constructs an iterator positioned as specified. By default iterators // are unpositioned, since this avoids an extra seek in this situation // where one of the seek methods (such as Locate) is immediately called. // // If you want to position the iterator at the beginning, e.g. in order to // loop through the entire index, do this instead: // // for (S2ShapeIndex::Iterator it(&index, S2ShapeIndex::BEGIN); // !it.done(); it.Next()) { ... } explicit Iterator(const S2ShapeIndex* index, InitialPosition pos = UNPOSITIONED) : iter_(index->NewIterator(pos)) {} // Initializes an iterator for the given S2ShapeIndex. This method may // also be called in order to restore an iterator to a valid state after // the underlying index has been updated (although it is usually easier // just to declare a new iterator whenever required, since iterator // construction is cheap). void Init(const S2ShapeIndex* index, InitialPosition pos = UNPOSITIONED) { iter_ = index->NewIterator(pos); } // Iterators are copyable and movable. Iterator(const Iterator&); Iterator& operator=(const Iterator&); Iterator(Iterator&&); Iterator& operator=(Iterator&&); // Returns the S2CellId of the current index cell. If done() is true, // returns a value larger than any valid S2CellId (S2CellId::Sentinel()). S2CellId id() const { return iter_->id(); } // Returns the center point of the cell. // REQUIRES: !done() S2Point center() const { return id().ToPoint(); } // Returns a reference to the contents of the current index cell. // REQUIRES: !done() const S2ShapeIndexCell& cell() const { return iter_->cell(); } // Returns true if the iterator is positioned past the last index cell. bool done() const { return iter_->done(); } // Positions the iterator at the first index cell (if any). void Begin() { iter_->Begin(); } // Positions the iterator past the last index cell. void Finish() { iter_->Finish(); } // Positions the iterator at the next index cell. // REQUIRES: !done() void Next() { iter_->Next(); } // If the iterator is already positioned at the beginning, returns false. // Otherwise positions the iterator at the previous entry and returns true. bool Prev() { return iter_->Prev(); } // Positions the iterator at the first cell with id() >= target, or at the // end of the index if no such cell exists. void Seek(S2CellId target) { iter_->Seek(target); } // Positions the iterator at the cell containing "target". If no such cell // exists, returns false and leaves the iterator positioned arbitrarily. // The returned index cell is guaranteed to contain all edges that might // intersect the line segment between "target" and the cell center. bool Locate(const S2Point& target) { return IteratorBase::LocateImpl(target, this); } // Let T be the target S2CellId. If T is contained by some index cell I // (including equality), this method positions the iterator at I and // returns INDEXED. Otherwise if T contains one or more (smaller) index // cells, it positions the iterator at the first such cell I and returns // SUBDIVIDED. Otherwise it returns DISJOINT and leaves the iterator // positioned arbitrarily. CellRelation Locate(S2CellId target) { return IteratorBase::LocateImpl(target, this); } private: // Although S2ShapeIndex::Iterator can be used to iterate over any // index subtype, it is more efficient to use the subtype's iterator when // the subtype is known at compile time. For example, MutableS2ShapeIndex // should use a MutableS2ShapeIndex::Iterator. // // The following declarations prevent accidental use of // S2ShapeIndex::Iterator when the actual subtype is known. (If you // really want to do this, you can down_cast the index argument to // S2ShapeIndex.) template explicit Iterator(const T* index, InitialPosition pos = UNPOSITIONED) {} template void Init(const T* index, InitialPosition pos = UNPOSITIONED) {} std::unique_ptr iter_; }; // ShapeFactory is an interface for decoding vectors of S2Shapes. It allows // random access to the shapes in order to support lazy decoding. See // s2shapeutil_coding.h for useful subtypes. class ShapeFactory { public: virtual ~ShapeFactory() {} // Returns the number of S2Shapes in the vector. virtual int size() const = 0; // Returns the S2Shape object corresponding to the given "shape_id". // Returns nullptr if a shape cannot be decoded or a shape is missing // (e.g., because MutableS2ShapeIndex::Release() was called). virtual std::unique_ptr operator[](int shape_id) const = 0; // Returns a deep copy of this ShapeFactory. virtual std::unique_ptr Clone() const = 0; }; protected: // Each subtype of S2ShapeIndex should define an Iterator type derived // from the following base class. class IteratorBase { public: virtual ~IteratorBase() {} IteratorBase(const IteratorBase&); IteratorBase& operator=(const IteratorBase&); // Returns the S2CellId of the current index cell. If done() is true, // returns a value larger than any valid S2CellId (S2CellId::Sentinel()). S2CellId id() const; // Returns the center point of the cell. // REQUIRES: !done() S2Point center() const; // Returns a reference to the contents of the current index cell. // REQUIRES: !done() const S2ShapeIndexCell& cell() const; // Returns true if the iterator is positioned past the last index cell. bool done() const; // Positions the iterator at the first index cell (if any). virtual void Begin() = 0; // Positions the iterator past the last index cell. virtual void Finish() = 0; // Positions the iterator at the next index cell. // REQUIRES: !done() virtual void Next() = 0; // If the iterator is already positioned at the beginning, returns false. // Otherwise positions the iterator at the previous entry and returns true. virtual bool Prev() = 0; // Positions the iterator at the first cell with id() >= target, or at the // end of the index if no such cell exists. virtual void Seek(S2CellId target) = 0; // Positions the iterator at the cell containing "target". If no such cell // exists, returns false and leaves the iterator positioned arbitrarily. // The returned index cell is guaranteed to contain all edges that might // intersect the line segment between "target" and the cell center. virtual bool Locate(const S2Point& target) = 0; // Let T be the target S2CellId. If T is contained by some index cell I // (including equality), this method positions the iterator at I and // returns INDEXED. Otherwise if T contains one or more (smaller) index // cells, it positions the iterator at the first such cell I and returns // SUBDIVIDED. Otherwise it returns DISJOINT and leaves the iterator // positioned arbitrarily. virtual CellRelation Locate(S2CellId target) = 0; protected: IteratorBase() : id_(S2CellId::Sentinel()), cell_(nullptr) {} // Sets the iterator state. "cell" typically points to the cell contents, // but may also be given as "nullptr" in order to implement decoding on // demand. In that situation, the first that the client attempts to // access the cell contents, the GetCell() method is called and "cell_" is // updated in a thread-safe way. void set_state(S2CellId id, const S2ShapeIndexCell* cell); // Sets the iterator state so that done() is true. void set_finished(); // Returns the current contents of the "cell_" field, which may be nullptr // if the cell contents have not been decoded yet. const S2ShapeIndexCell* raw_cell() const; // This method is called to decode the contents of the current cell, if // set_state() was previously called with a nullptr "cell" argument. This // allows decoding on demand for subtypes that keep the cell contents in // an encoded state. It does not need to be implemented at all if // set_state() is always called with (cell != nullptr). // // REQUIRES: This method is thread-safe. // REQUIRES: Multiple calls to this method return the same value. virtual const S2ShapeIndexCell* GetCell() const = 0; // Returns an exact copy of this iterator. virtual std::unique_ptr Clone() const = 0; // Makes a copy of the given source iterator. // REQUIRES: "other" has the same concrete type as "this". virtual void Copy(const IteratorBase& other) = 0; // The default implementation of Locate(S2Point). It is instantiated by // each subtype in order to (1) minimize the number of virtual method // calls (since subtypes are typically "final") and (2) ensure that the // correct versions of non-virtual methods such as cell() are called. template static bool LocateImpl(const S2Point& target, Iter* it); // The default implementation of Locate(S2CellId) (see comments above). template static CellRelation LocateImpl(S2CellId target, Iter* it); private: friend class Iterator; // This method is "const" because it is used internally by "const" methods // in order to implement decoding on demand. void set_cell(const S2ShapeIndexCell* cell) const; S2CellId id_; mutable std::atomic cell_; }; // Returns a new iterator positioned as specified. virtual std::unique_ptr NewIterator(InitialPosition pos) const = 0; }; ////////////////// Implementation details follow //////////////////// inline int S2ClippedShape::shape_id() const { return shape_id_; } inline bool S2ClippedShape::contains_center() const { return contains_center_; } inline int S2ClippedShape::num_edges() const { return num_edges_; } inline int S2ClippedShape::edge(int i) const { return is_inline() ? inline_edges_[i] : edges_[i]; } // Initialize an S2ClippedShape to hold the given number of edges. inline void S2ClippedShape::Init(int32 shape_id, int32 num_edges) { shape_id_ = shape_id; num_edges_ = num_edges; contains_center_ = false; if (!is_inline()) { edges_ = new int32[num_edges]; } } // Free any memory allocated by this S2ClippedShape. We don't do this in // the destructor because S2ClippedShapes are copied by STL code, and we // don't want to repeatedly copy and free the edge data. Instead the data // is owned by the containing S2ShapeIndexCell. inline void S2ClippedShape::Destruct() { if (!is_inline()) delete[] edges_; } inline bool S2ClippedShape::is_inline() const { return num_edges_ <= inline_edges_.size(); } // Set "contains_center_" to indicate whether this clipped shape contains the // center of the cell to which it belongs. inline void S2ClippedShape::set_contains_center(bool contains_center) { contains_center_ = contains_center; } // Set the i-th edge of this clipped shape to be the given edge of the // original shape. inline void S2ClippedShape::set_edge(int i, int edge) { if (is_inline()) { inline_edges_[i] = edge; } else { edges_[i] = edge; } } inline const S2ClippedShape* S2ShapeIndexCell::find_clipped( const S2Shape* shape) const { return find_clipped(shape->id()); } // Inline because an index cell frequently contains just one shape. inline int S2ShapeIndexCell::num_edges() const { int n = 0; for (int i = 0; i < num_clipped(); ++i) n += clipped(i).num_edges(); return n; } inline S2Shape* S2ShapeIndex::ShapeIterator::operator*() const { return index_->shape(shape_id_); } inline S2ShapeIndex::ShapeIterator& S2ShapeIndex::ShapeIterator::operator++() { ++shape_id_; return *this; } inline S2ShapeIndex::ShapeIterator S2ShapeIndex::ShapeIterator::operator++( int) { return ShapeIterator(index_, shape_id_++); } inline bool S2ShapeIndex::ShapeIterator::operator==(ShapeIterator it) const { S2_DCHECK_EQ(index_, it.index_); return shape_id_ == it.shape_id_; } inline bool S2ShapeIndex::ShapeIterator::operator!=(ShapeIterator it) const { S2_DCHECK_EQ(index_, it.index_); return shape_id_ != it.shape_id_; } inline S2ShapeIndex::ShapeIterator S2ShapeIndex::begin() const { return ShapeIterator(this, 0); } inline S2ShapeIndex::ShapeIterator S2ShapeIndex::end() const { return ShapeIterator(this, num_shape_ids()); } inline S2ShapeIndex::IteratorBase::IteratorBase(const IteratorBase& other) : id_(other.id_), cell_(other.raw_cell()) { } inline S2ShapeIndex::IteratorBase& S2ShapeIndex::IteratorBase::operator=(const IteratorBase& other) { id_ = other.id_; set_cell(other.raw_cell()); return *this; } inline S2CellId S2ShapeIndex::IteratorBase::id() const { return id_; } inline const S2ShapeIndexCell& S2ShapeIndex::IteratorBase::cell() const { // Like other const methods, this method is thread-safe provided that it // does not overlap with calls to non-const methods. S2_DCHECK(!done()); auto cell = raw_cell(); if (cell == nullptr) { cell = GetCell(); set_cell(cell); } return *cell; } inline bool S2ShapeIndex::IteratorBase::done() const { return id_ == S2CellId::Sentinel(); } inline S2Point S2ShapeIndex::IteratorBase::center() const { S2_DCHECK(!done()); return id().ToPoint(); } inline void S2ShapeIndex::IteratorBase::set_state( S2CellId id, const S2ShapeIndexCell* cell) { id_ = id; set_cell(cell); } inline void S2ShapeIndex::IteratorBase::set_finished() { id_ = S2CellId::Sentinel(); set_cell(nullptr); } inline const S2ShapeIndexCell* S2ShapeIndex::IteratorBase::raw_cell() const { return cell_.load(std::memory_order_relaxed); } inline void S2ShapeIndex::IteratorBase::set_cell( const S2ShapeIndexCell* cell) const { cell_.store(cell, std::memory_order_relaxed); } template inline bool S2ShapeIndex::IteratorBase::LocateImpl( const S2Point& target_point, Iter* it) { // Let I = cell_map_->lower_bound(T), where T is the leaf cell containing // "target_point". Then if T is contained by an index cell, then the // containing cell is either I or I'. We test for containment by comparing // the ranges of leaf cells spanned by T, I, and I'. S2CellId target(target_point); it->Seek(target); if (!it->done() && it->id().range_min() <= target) return true; if (it->Prev() && it->id().range_max() >= target) return true; return false; } template inline S2ShapeIndex::CellRelation S2ShapeIndex::IteratorBase::LocateImpl(S2CellId target, Iter* it) { // Let T be the target, let I = cell_map_->lower_bound(T.range_min()), and // let I' be the predecessor of I. If T contains any index cells, then T // contains I. Similarly, if T is contained by an index cell, then the // containing cell is either I or I'. We test for containment by comparing // the ranges of leaf cells spanned by T, I, and I'. it->Seek(target.range_min()); if (!it->done()) { if (it->id() >= target && it->id().range_min() <= target) return INDEXED; if (it->id() <= target.range_max()) return SUBDIVIDED; } if (it->Prev() && it->id().range_max() >= target) return INDEXED; return DISJOINT; } inline S2ShapeIndex::Iterator::Iterator(const Iterator& other) : iter_(other.iter_->Clone()) { } inline S2ShapeIndex::Iterator& S2ShapeIndex::Iterator::operator=( const Iterator& other) { iter_->Copy(*other.iter_); return *this; } inline S2ShapeIndex::Iterator::Iterator(Iterator&& other) : iter_(std::move(other.iter_)) { } inline S2ShapeIndex::Iterator& S2ShapeIndex::Iterator::operator=( Iterator&& other) { iter_ = std::move(other.iter_); return *this; } #endif // S2_S2SHAPE_INDEX_H_ s2/src/s2/s1interval.h0000644000176200001440000002464014530411473014162 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S1INTERVAL_H_ #define S2_S1INTERVAL_H_ #include #include #include #include "s2/base/logging.h" #include "s2/_fp_contract_off.h" #include "s2/util/math/vector.h" // IWYU pragma: export // An S1Interval represents a closed interval on a unit circle (also known // as a 1-dimensional sphere). It is capable of representing the empty // interval (containing no points), the full interval (containing all // points), and zero-length intervals (containing a single point). // // Points are represented by the angle they make with the positive x-axis in // the range [-Pi, Pi]. An interval is represented by its lower and upper // bounds (both inclusive, since the interval is closed). The lower bound may // be greater than the upper bound, in which case the interval is "inverted" // (i.e. it passes through the point (-1, 0)). // // Note that the point (-1, 0) has two valid representations, Pi and -Pi. // The normalized representation of this point internally is Pi, so that // endpoints of normal intervals are in the range (-Pi, Pi]. However, we // take advantage of the point -Pi to construct two special intervals: // the Full() interval is [-Pi, Pi], and the Empty() interval is [Pi, -Pi]. // // This class is intended to be copied by value as desired. It uses // the default copy constructor and assignment operator. class S1Interval { public: // Constructor. Both endpoints must be in the range -Pi to Pi inclusive. // The value -Pi is converted internally to Pi except for the Full() // and Empty() intervals. S1Interval(double lo, double hi); // The default constructor creates an empty interval. // // Note: Don't construct an interval using the default constructor and // set_lo()/set_hi(). If you need to set both endpoints, use the // constructor above: // // lng_bounds_ = S1Interval(lng_lo, lng_hi); S1Interval(); // Returns the empty interval. static S1Interval Empty(); // Returns the full interval. static S1Interval Full(); // Convenience method to construct an interval containing a single point. static S1Interval FromPoint(double p); // Convenience method to construct the minimal interval containing // the two given points. This is equivalent to starting with an empty // interval and calling AddPoint() twice, but it is more efficient. static S1Interval FromPointPair(double p1, double p2); // Accessors methods. double lo() const { return bounds_[0]; } double hi() const { return bounds_[1]; } // Methods that allow the S1Interval to be accessed as a vector. (The // recommended style is to use lo() and hi() whenever possible, but these // methods are useful when the endpoint to be selected is not constant.) // // Only const versions of these methods are provided, since S1Interval // has invariants that must be maintained after each update. double operator[](int i) const { return bounds_[i]; } const Vector2_d& bounds() const { return bounds_; } // An interval is valid if neither bound exceeds Pi in absolute value, // and the value -Pi appears only in the Empty() and Full() intervals. bool is_valid() const; // Return true if the interval contains all points on the unit circle. bool is_full() const { return lo() == -M_PI && hi() == M_PI; } // Return true if the interval is empty, i.e. it contains no points. bool is_empty() const { return lo() == M_PI && hi() == -M_PI; } // Return true if lo() > hi(). (This is true for empty intervals.) bool is_inverted() const { return lo() > hi(); } // Return the midpoint of the interval. For full and empty intervals, // the result is arbitrary. double GetCenter() const; // Return the length of the interval. The length of an empty interval // is negative. double GetLength() const; // Return the complement of the interior of the interval. An interval and // its complement have the same boundary but do not share any interior // values. The complement operator is not a bijection, since the complement // of a singleton interval (containing a single value) is the same as the // complement of an empty interval. S1Interval Complement() const; // Return the midpoint of the complement of the interval. For full and empty // intervals, the result is arbitrary. For a singleton interval (containing a // single point), the result is its antipodal point on S1. double GetComplementCenter() const; // Return true if the interval (which is closed) contains the point 'p'. bool Contains(double p) const; // Return true if the interior of the interval contains the point 'p'. bool InteriorContains(double p) const; // Return true if the interval contains the given interval 'y'. // Works for empty, full, and singleton intervals. bool Contains(const S1Interval& y) const; // Returns true if the interior of this interval contains the entire // interval 'y'. Note that x.InteriorContains(x) is true only when // x is the empty or full interval, and x.InteriorContains(S1Interval(p,p)) // is equivalent to x.InteriorContains(p). bool InteriorContains(const S1Interval& y) const; // Return true if the two intervals contain any points in common. // Note that the point +/-Pi has two representations, so the intervals // [-Pi,-3] and [2,Pi] intersect, for example. bool Intersects(const S1Interval& y) const; // Return true if the interior of this interval contains any point of the // interval 'y' (including its boundary). Works for empty, full, and // singleton intervals. bool InteriorIntersects(const S1Interval& y) const; // Return the Hausdorff distance to the given interval 'y'. For two // S1Intervals x and y, this distance is defined by // h(x, y) = max_{p in x} min_{q in y} d(p, q), // where d(.,.) is measured along S1. double GetDirectedHausdorffDistance(const S1Interval& y) const; // Expand the interval by the minimum amount necessary so that it // contains the given point "p" (an angle in the range [-Pi, Pi]). void AddPoint(double p); // Return the closest point in the interval to the given point "p". // The interval must be non-empty. double Project(double p) const; // Return an interval that has been expanded on each side by the given // distance "margin". If "margin" is negative, then shrink the interval on // each side by "margin" instead. The resulting interval may be empty or // full. Any expansion (positive or negative) of a full interval remains // full, and any expansion of an empty interval remains empty. S1Interval Expanded(double margin) const; // Return the smallest interval that contains this interval and the // given interval "y". S1Interval Union(const S1Interval& y) const; // Return the smallest interval that contains the intersection of this // interval with "y". Note that the region of intersection may // consist of two disjoint intervals. S1Interval Intersection(const S1Interval& y) const; // Return true if two intervals contains the same set of points. bool operator==(const S1Interval& y) const; // Return true if this interval can be transformed into the given interval by // moving each endpoint by at most "max_error" (and without the endpoints // crossing, which would invert the interval). Empty and full intervals are // considered to start at an arbitrary point on the unit circle, thus any // interval with (length <= 2*max_error) matches the empty interval, and any // interval with (length >= 2*Pi - 2*max_error) matches the full interval. bool ApproxEquals(const S1Interval& y, double max_error = 1e-15) const; // Low-level methods to modify one endpoint of an existing S1Interval. // These methods should really be private because setting just one endpoint // can violate the invariants maintained by S1Interval. In particular: // // - It is not valid to call these methods on an Empty() or Full() // interval, since these intervals do not have any endpoints. // // - It is not allowed to set an endpoint to -Pi. (When these methods are // used internally, values of -Pi have already been normalized to Pi.) // // The preferred way to modify both endpoints of an interval is to use a // constructor, e.g. lng = S1Interval(lng_lo, lng_hi). void set_lo(double p); void set_hi(double p); private: enum ArgsChecked { ARGS_CHECKED }; // Internal constructor that assumes that both arguments are in the // correct range, i.e. normalization from -Pi to Pi is already done. S1Interval(double lo, double hi, ArgsChecked dummy); // Return true if the interval (which is closed) contains the point 'p'. // Skips the normalization of 'p' from -Pi to Pi. bool FastContains(double p) const; Vector2_d bounds_; }; inline S1Interval::S1Interval(double lo, double hi) : bounds_(lo, hi) { if (lo == -M_PI && hi != M_PI) set_lo(M_PI); if (hi == -M_PI && lo != M_PI) set_hi(M_PI); S2_DCHECK(is_valid()); } inline S1Interval::S1Interval(double lo, double hi, ArgsChecked dummy) : bounds_(lo, hi) { S2_DCHECK(is_valid()); } inline S1Interval::S1Interval() : bounds_(M_PI, -M_PI) { } inline S1Interval S1Interval::Empty() { return S1Interval(); } inline S1Interval S1Interval::Full() { return S1Interval(-M_PI, M_PI, ARGS_CHECKED); } inline bool S1Interval::is_valid() const { return (std::fabs(lo()) <= M_PI && std::fabs(hi()) <= M_PI && !(lo() == -M_PI && hi() != M_PI) && !(hi() == -M_PI && lo() != M_PI)); } inline bool S1Interval::operator==(const S1Interval& y) const { return lo() == y.lo() && hi() == y.hi(); } inline void S1Interval::set_lo(double p) { bounds_[0] = p; S2_DCHECK(is_valid()); } inline void S1Interval::set_hi(double p) { bounds_[1] = p; S2_DCHECK(is_valid()); } inline std::ostream& operator<<(std::ostream& os, const S1Interval& x) { return os << "[" << x.lo() << ", " << x.hi() << "]"; } #endif // S2_S1INTERVAL_H_ s2/src/s2/s2shapeutil_build_polygon_boundaries.cc0000644000176200001440000001077014530411473021633 0ustar liggesusers// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2shapeutil_build_polygon_boundaries.h" #include "absl/container/btree_map.h" #include "absl/memory/memory.h" #include "s2/mutable_s2shape_index.h" #include "s2/s2contains_point_query.h" #include "s2/s2shape_index.h" #include "s2/s2shapeutil_contains_brute_force.h" using absl::WrapUnique; using std::vector; namespace s2shapeutil { void BuildPolygonBoundaries(const vector>& components, vector>* polygons) { polygons->clear(); if (components.empty()) return; // Since the loop boundaries do not cross, a loop nesting hierarchy can be // defined by choosing any point on the sphere as the "point at infinity". // Loop A then contains loop B if (1) A contains the boundary of B and (2) // loop A does not contain the point at infinity. // // We choose S2::Origin() for this purpose. The loop nesting hierarchy then // determines the face structure. Here are the details: // // 1. Build an S2ShapeIndex of all loops that do not contain S2::Origin(). // This leaves at most one unindexed loop per connected component // (the "outer loop"). // // 2. For each component, choose a representative vertex and determine // which indexed loops contain it. The "depth" of this component is // defined as the number of such loops. // // 3. Assign the outer loop of each component to the containing loop whose // depth is one less. This generates a set of multi-loop polygons. // // 4. The outer loops of all components at depth 0 become a single face. MutableS2ShapeIndex index; // A map from shape.id() to the corresponding component number. vector component_ids; vector outer_loops; for (int i = 0; i < components.size(); ++i) { const auto& component = components[i]; for (S2Shape* loop : component) { if (component.size() > 1 && !s2shapeutil::ContainsBruteForce(*loop, S2::Origin())) { // Ownership is transferred back at the end of this function. index.Add(WrapUnique(loop)); component_ids.push_back(i); } else { outer_loops.push_back(loop); } } // Check that there is exactly one outer loop in each component. S2_DCHECK_EQ(i + 1, outer_loops.size()) << "Component is not a subdivision"; } // Find the loops containing each component. vector> ancestors(components.size()); auto contains_query = MakeS2ContainsPointQuery(&index); for (int i = 0; i < outer_loops.size(); ++i) { auto loop = outer_loops[i]; S2_DCHECK_GT(loop->num_edges(), 0); ancestors[i] = contains_query.GetContainingShapes(loop->edge(0).v0); } // Assign each outer loop to the component whose depth is one less. // Components at depth 0 become a single face. absl::btree_map> children; for (int i = 0; i < outer_loops.size(); ++i) { S2Shape* ancestor = nullptr; int depth = ancestors[i].size(); if (depth > 0) { for (auto candidate : ancestors[i]) { if (ancestors[component_ids[candidate->id()]].size() == depth - 1) { S2_DCHECK(ancestor == nullptr); ancestor = candidate; } } S2_DCHECK(ancestor != nullptr); } children[ancestor].push_back(outer_loops[i]); } // There is one face per loop that is not an outer loop, plus one for the // outer loops of components at depth 0. polygons->resize(index.num_shape_ids() + 1); for (int i = 0; i < index.num_shape_ids(); ++i) { auto polygon = &(*polygons)[i]; auto loop = index.shape(i); auto itr = children.find(loop); if (itr != children.end()) { *polygon = itr->second; } polygon->push_back(loop); } polygons->back() = children[nullptr]; // Explicitly release the shapes from the index so they are not deleted. for (auto& ptr : index.ReleaseAll()) ptr.release(); } } // namespace s2shapeutil s2/src/s2/s2builderutil_lax_polygon_layer.h0000644000176200001440000002110614530411473020464 0ustar liggesusers// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // Note that there are two supported output types for polygons: S2Polygon and // S2LaxPolygonShape. Use S2Polygon if you need the full range of operations // that S2Polygon implements. Use S2LaxPolygonShape if you want to represent // polygons with zero-area degenerate regions, or if you need a type that has // low memory overhead and fast initialization. However, be aware that to // convert from S2LaxPolygonShape to S2Polygon you will need to use S2Builder // again. // // Similarly, there are two supported output formats for polygon meshes: // S2PolygonMesh and S2LaxPolygonShapeVector. Use S2PolygonMesh if you need // to be able to determine which polygons are adjacent to each edge or vertex; // otherwise use S2LaxPolygonShapeVector, which uses less memory and is faster // to construct. #ifndef S2_S2BUILDERUTIL_LAX_POLYGON_LAYER_H_ #define S2_S2BUILDERUTIL_LAX_POLYGON_LAYER_H_ #include #include #include "s2/base/logging.h" #include "absl/memory/memory.h" #include "s2/id_set_lexicon.h" #include "s2/mutable_s2shape_index.h" #include "s2/s2builder.h" #include "s2/s2builder_graph.h" #include "s2/s2builder_layer.h" #include "s2/s2error.h" #include "s2/s2lax_polygon_shape.h" namespace s2builderutil { // A layer type that assembles edges (directed or undirected) into an // S2LaxPolygonShape. Returns an error if the edges cannot be assembled into // loops. // // If the input edges are directed, they must be oriented such that the // polygon interior is to the left of all edges. Directed edges are always // preferred (see S2Builder::EdgeType). // // LaxPolygonLayer is implemented such that if the input to S2Builder is a // polygon and is not modified, then the output has the same cyclic ordering // of loop vertices and the same loop ordering as the input polygon. // // If the given edge graph is degenerate (i.e., it consists entirely of // degenerate edges and sibling pairs), then the IsFullPolygonPredicate // associated with the edge graph is called to determine whether the output // polygon should be empty (possibly with degenerate shells) or full (possibly // with degenerate holes). This predicate can be specified as part of the // S2Builder input geometry. class LaxPolygonLayer : public S2Builder::Layer { public: class Options { public: // Constructor that uses the default options (listed below). Options(); // Constructor that specifies the edge type. explicit Options(S2Builder::EdgeType edge_type); // Indicates whether the input edges provided to S2Builder are directed or // undirected. Directed edges should be used whenever possible (see // S2Builder::EdgeType for details). // // If the input edges are directed, they should be oriented so that the // polygon interior is to the left of all edges. This means that for a // polygon with holes, the outer loops ("shells") should be directed // counter-clockwise while the inner loops ("holes") should be directed // clockwise. Note that S2Builder::AddPolygon() does this automatically. // // DEFAULT: S2Builder::EdgeType::DIRECTED S2Builder::EdgeType edge_type() const; void set_edge_type(S2Builder::EdgeType edge_type); // Specifies whether degenerate boundaries should be discarded or kept. // (A degenerate boundary consists of either a sibling edge pair or an // edge from a vertex to itself.) Optionally, degenerate boundaries may // be kept only if they represent shells, or only if they represent holes. // // This option is useful for normalizing polygons with various boundary // conditions. For example, DISCARD_HOLES can be used to normalize closed // polygons (those that include their boundary), since degenerate holes do // not affect the set of points contained by such polygons. Similarly, // DISCARD_SHELLS can be used to normalize polygons with open boundaries. // DISCARD is used to normalize polygons with semi-open boundaries (since // degenerate loops do not affect point containment in that case), and // finally KEEP is useful for working with any type of polygon where // degeneracies are assumed to contain an infinitesmal interior. (This // last model is the most useful for working with simplified geometry, // since it maintains the closest fidelity to the original geometry.) // // DEFAULT: DegenerateBoundaries::KEEP enum class DegenerateBoundaries { DISCARD, DISCARD_HOLES, DISCARD_SHELLS, KEEP }; DegenerateBoundaries degenerate_boundaries() const; void set_degenerate_boundaries(DegenerateBoundaries degenerate_boundaries); private: S2Builder::EdgeType edge_type_; DegenerateBoundaries degenerate_boundaries_; }; // Specifies that a polygon should be constructed using the given options. explicit LaxPolygonLayer(S2LaxPolygonShape* polygon, const Options& options = Options()); // Specifies that a polygon should be constructed using the given options, // and that any labels attached to the input edges should be returned in // "label_set_ids" and "label_set_lexicion". // // The labels associated with the edge "polygon.chain_edge(i, j)" // can be retrieved as follows: // // for (int32 label : label_set_lexicon.id_set(label_set_ids[i][j])) {...} using LabelSetIds = std::vector>; LaxPolygonLayer(S2LaxPolygonShape* polygon, LabelSetIds* label_set_ids, IdSetLexicon* label_set_lexicon, const Options& options = Options()); // Layer interface: GraphOptions graph_options() const override; void Build(const Graph& g, S2Error* error) override; private: void Init(S2LaxPolygonShape* polygon, LabelSetIds* label_set_ids, IdSetLexicon* label_set_lexicon, const Options& options); void AppendPolygonLoops(const Graph& g, const std::vector& edge_loops, std::vector>* loops) const; void AppendEdgeLabels(const Graph& g, const std::vector& edge_loops); void BuildDirected(Graph g, S2Error* error); S2LaxPolygonShape* polygon_; LabelSetIds* label_set_ids_; IdSetLexicon* label_set_lexicon_; Options options_; }; // Like LaxPolygonLayer, but adds the polygon to a MutableS2ShapeIndex (if the // polygon is non-empty). class IndexedLaxPolygonLayer : public S2Builder::Layer { public: using Options = LaxPolygonLayer::Options; explicit IndexedLaxPolygonLayer(MutableS2ShapeIndex* index, const Options& options = Options()) : index_(index), polygon_(new S2LaxPolygonShape), layer_(polygon_.get(), options) {} GraphOptions graph_options() const override { return layer_.graph_options(); } void Build(const Graph& g, S2Error* error) override { layer_.Build(g, error); if (error->ok() && !polygon_->is_empty()) { index_->Add(std::move(polygon_)); } } private: MutableS2ShapeIndex* index_; std::unique_ptr polygon_; LaxPolygonLayer layer_; }; ////////////////// Implementation details follow //////////////////// inline LaxPolygonLayer::Options::Options() : Options(S2Builder::EdgeType::DIRECTED) { } inline LaxPolygonLayer::Options::Options(S2Builder::EdgeType edge_type) : edge_type_(edge_type), degenerate_boundaries_(DegenerateBoundaries::KEEP) { } inline S2Builder::EdgeType LaxPolygonLayer::Options::edge_type() const { return edge_type_; } inline void LaxPolygonLayer::Options::set_edge_type( S2Builder::EdgeType edge_type) { edge_type_ = edge_type; } inline LaxPolygonLayer::Options::DegenerateBoundaries LaxPolygonLayer::Options::degenerate_boundaries() const { return degenerate_boundaries_; } inline void LaxPolygonLayer::Options::set_degenerate_boundaries( DegenerateBoundaries degenerate_boundaries) { degenerate_boundaries_ = degenerate_boundaries; } } // namespace s2builderutil #endif // S2_S2BUILDERUTIL_LAX_POLYGON_LAYER_H_ s2/src/s2/s2builderutil_find_polygon_degeneracies.cc0000644000176200001440000003544714530411473022275 0ustar liggesusers// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2builderutil_find_polygon_degeneracies.h" #include #include #include #include "absl/memory/memory.h" #include "s2/mutable_s2shape_index.h" #include "s2/s2builder_graph.h" #include "s2/s2builderutil_graph_shape.h" #include "s2/s2contains_vertex_query.h" #include "s2/s2crossing_edge_query.h" #include "s2/s2edge_crosser.h" #include "s2/s2pointutil.h" #include "s2/s2predicates.h" using absl::make_unique; using std::make_pair; using std::pair; using std::vector; using EdgeType = S2Builder::EdgeType; using Graph = S2Builder::Graph; using GraphOptions = S2Builder::GraphOptions; using Edge = Graph::Edge; using EdgeId = Graph::EdgeId; using VertexId = Graph::VertexId; using DegenerateEdges = GraphOptions::DegenerateEdges; using SiblingPairs = GraphOptions::SiblingPairs; using ShapeEdgeId = s2shapeutil::ShapeEdgeId; namespace s2builderutil { namespace { // The algorithm builds a set of connected components containing all edges // that form degeneracies. The shell/hole status of each degeneracy is // initially unknown, and is expressed relative to the root vertex: "is_hole" // means that the degeneracy is a hole if and only if the root vertex turns // out to be inside the polygon. struct Component { // The root vertex from which this component was built. VertexId root; // +1 if "root" inside the polygon, -1 if outside, and 0 if unknown. int root_sign = 0; // The degeneracies found in this component. "is_hole" is expressed // relative to the root vertex: the degeneracy is a hole iff the root vertex // turns out to be inside the polygon (i.e., root_sign > 0). vector degeneracies; }; // The actual implementation of FindPolygonDegeneracies. class DegeneracyFinder { public: explicit DegeneracyFinder(const S2Builder::Graph* g) : g_(*g), in_(g_), out_(g_) { } vector Run(S2Error* error); private: // Methods are documented below. int ComputeDegeneracies(); Component BuildComponent(VertexId root); bool CrossingParity(VertexId v0, VertexId v1, bool include_same) const; VertexId FindUnbalancedVertex() const; int ContainsVertexSign(VertexId v0) const; void ComputeUnknownSignsBruteForce(VertexId known_vertex, int known_vertex_sign, vector* components) const; void ComputeUnknownSignsIndexed(VertexId known_vertex, int known_vertex_sign, vector* components) const; vector MergeDegeneracies( const vector& components) const; const Graph& g_; Graph::VertexInMap in_; Graph::VertexOutMap out_; vector is_vertex_used_; // Has vertex been visited? vector is_edge_degeneracy_; // Belongs to a degeneracy? vector is_vertex_unbalanced_; // Has unbalanced sibling pairs? }; vector DegeneracyFinder::Run(S2Error* error) { // Mark all degenerate edges and sibling pairs in the "is_edge_degeneracy_" // vector, and mark any vertices with unbalanced edges in the // "is_vertex_unbalanced_" vector. int num_degeneracies = ComputeDegeneracies(); if (num_degeneracies == 0) return {}; // If all edges are degenerate, then use IsFullPolygon() to classify the // degeneracies (they are necessarily all the same type). if (num_degeneracies == g_.num_edges()) { bool is_hole = g_.IsFullPolygon(error); vector result(g_.num_edges()); for (int e = 0; e < g_.num_edges(); ++e) { result[e] = PolygonDegeneracy(e, is_hole); } return result; } // Otherwise repeatedly build components starting from an unvisited // degeneracy. (This avoids building components that don't contain any // degeneracies.) Each component records the "is_hole" status of each // degeneracy relative to the root vertex of that component. If the // component contains any non-degenerate portions, then we also determine // whether the root vertex is contained by the component (root_sign). // In addition we keep track of the number of components that were // completely degenerate (to help us decide whether to build an index). vector components; VertexId known_vertex = -1; int known_vertex_sign = 0; int num_unknown_signs = 0; is_vertex_used_.resize(g_.num_vertices()); for (int e = 0; e < g_.num_edges(); ++e) { if (is_edge_degeneracy_[e]) { VertexId root = g_.edge(e).first; if (is_vertex_used_[root]) continue; Component component = BuildComponent(root); if (component.root_sign == 0) { ++num_unknown_signs; } else { known_vertex = root; known_vertex_sign = component.root_sign; } components.push_back(component); } } // If some components have an unknown root_sign (i.e., it is unknown whether // the root vertex is contained by the polygon or not), we determine the // sign of those root vertices by counting crossings starting from a vertex // whose sign is known. Depending on how many components we need to do this // for, it may be worthwhile to build an index first. if (num_unknown_signs > 0) { if (known_vertex_sign == 0) { known_vertex = FindUnbalancedVertex(); known_vertex_sign = ContainsVertexSign(known_vertex); } const int kMaxUnindexedContainsCalls = 20; // Tuned using benchmarks. if (num_unknown_signs <= kMaxUnindexedContainsCalls) { ComputeUnknownSignsBruteForce(known_vertex, known_vertex_sign, &components); } else { ComputeUnknownSignsIndexed(known_vertex, known_vertex_sign, &components); } } // Finally we convert the "is_hole" status of each degeneracy from a // relative value (compared to the component's root vertex) to an absolute // one, and sort all the degeneracies by EdgeId. return MergeDegeneracies(components); } int DegeneracyFinder::ComputeDegeneracies() { is_edge_degeneracy_.resize(g_.num_edges()); is_vertex_unbalanced_.resize(g_.num_vertices()); int num_degeneracies = 0; const vector& in_edge_ids = in_.in_edge_ids(); int n = g_.num_edges(); for (int in = 0, out = 0; out < n; ++out) { Edge out_edge = g_.edge(out); if (out_edge.first == out_edge.second) { is_edge_degeneracy_[out] = true; ++num_degeneracies; } else { while (in < n && Graph::reverse(g_.edge(in_edge_ids[in])) < out_edge) { ++in; } if (in < n && Graph::reverse(g_.edge(in_edge_ids[in])) == out_edge) { is_edge_degeneracy_[out] = true; ++num_degeneracies; } else { // This edge does not have a sibling, which mean that we can determine // whether either vertex is contained by the polygon (using semi-open // boundaries) by examining only the edges incident to that vertex. // We only mark the first vertex since there is no advantage to // finding more than one unbalanced vertex per connected component. is_vertex_unbalanced_[out_edge.first] = true; } } } return num_degeneracies; } // Build a connected component starting at the given root vertex. The // information returned includes: the root vertex, whether the containment // status of the root vertex could be determined using only the edges in this // component, and a vector of the edges that belong to degeneracies along with // the shell/hole status of each such edge relative to the root vertex. Component DegeneracyFinder::BuildComponent(VertexId root) { Component result; result.root = root; // We keep track of the frontier of unexplored vertices, and whether each // vertex is on the same side of the polygon boundary as the root vertex. vector> frontier; frontier.push_back(make_pair(root, true)); is_vertex_used_[root] = true; while (!frontier.empty()) { VertexId v0 = frontier.back().first; bool v0_same_inside = frontier.back().second; // Same as root vertex? frontier.pop_back(); if (result.root_sign == 0 && is_vertex_unbalanced_[v0]) { int v0_sign = ContainsVertexSign(v0); S2_DCHECK_NE(v0_sign, 0); result.root_sign = v0_same_inside ? v0_sign : -v0_sign; } for (EdgeId e : out_.edge_ids(v0)) { VertexId v1 = g_.edge(e).second; bool same_inside = v0_same_inside ^ CrossingParity(v0, v1, false); if (is_edge_degeneracy_[e]) { result.degeneracies.push_back(PolygonDegeneracy(e, same_inside)); } if (is_vertex_used_[v1]) continue; same_inside ^= CrossingParity(v1, v0, true); frontier.push_back(make_pair(v1, same_inside)); is_vertex_used_[v1] = true; } } return result; } // Counts the number of times that (v0, v1) crosses the edges incident to v0, // and returns the result modulo 2. This is equivalent to calling // S2::VertexCrossing for the edges incident to v0, except that this // implementation is more efficient (since it doesn't need to determine which // two edge vertices are the same). // // If "include_same" is false, then the edge (v0, v1) and its sibling (v1, v0) // (if any) are excluded from the parity calculation. bool DegeneracyFinder::CrossingParity(VertexId v0, VertexId v1, bool include_same) const { int crossings = 0; S2Point p0 = g_.vertex(v0); S2Point p1 = g_.vertex(v1); S2Point p0_ref = S2::Ortho(p0); for (const Edge& edge : out_.edges(v0)) { if (edge.second == v1) { if (include_same) ++crossings; } else if (s2pred::OrderedCCW(p0_ref, g_.vertex(edge.second), p1, p0)) { ++crossings; } } for (EdgeId e : in_.edge_ids(v0)) { Edge edge = g_.edge(e); if (edge.first == v1) { if (include_same) ++crossings; } else if (s2pred::OrderedCCW(p0_ref, g_.vertex(edge.first), p1, p0)) { ++crossings; } } return crossings & 1; } VertexId DegeneracyFinder::FindUnbalancedVertex() const { for (VertexId v = 0; v < g_.num_vertices(); ++v) { if (is_vertex_unbalanced_[v]) return v; } S2_LOG(DFATAL) << "Could not find previously marked unbalanced vertex"; return -1; } int DegeneracyFinder::ContainsVertexSign(VertexId v0) const { S2ContainsVertexQuery query(g_.vertex(v0)); for (const Edge& edge : out_.edges(v0)) { query.AddEdge(g_.vertex(edge.second), 1); } for (EdgeId e : in_.edge_ids(v0)) { query.AddEdge(g_.vertex(g_.edge(e).first), -1); } return query.ContainsSign(); } // Determines any unknown signs of component root vertices by counting // crossings starting from a vertex whose sign is known. This version simply // tests all edges for crossings. void DegeneracyFinder::ComputeUnknownSignsBruteForce( VertexId known_vertex, int known_vertex_sign, vector* components) const { S2EdgeCrosser crosser; for (Component& component : *components) { if (component.root_sign != 0) continue; bool inside = known_vertex_sign > 0; crosser.Init(&g_.vertex(known_vertex), &g_.vertex(component.root)); for (EdgeId e = 0; e < g_.num_edges(); ++e) { if (is_edge_degeneracy_[e]) continue; const Edge& edge = g_.edge(e); inside ^= crosser.EdgeOrVertexCrossing(&g_.vertex(edge.first), &g_.vertex(edge.second)); } component.root_sign = inside ? 1 : -1; } } // Like ComputeUnknownSignsBruteForce, except that this method uses an index // to find the set of edges that cross a given edge. void DegeneracyFinder::ComputeUnknownSignsIndexed( VertexId known_vertex, int known_vertex_sign, vector* components) const { MutableS2ShapeIndex index; index.Add(make_unique(&g_)); S2CrossingEdgeQuery query(&index); vector crossing_edges; S2EdgeCrosser crosser; for (Component& component : *components) { if (component.root_sign != 0) continue; bool inside = known_vertex_sign > 0; crosser.Init(&g_.vertex(known_vertex), &g_.vertex(component.root)); query.GetCandidates(g_.vertex(known_vertex), g_.vertex(component.root), *index.shape(0), &crossing_edges); for (ShapeEdgeId id : crossing_edges) { int e = id.edge_id; if (is_edge_degeneracy_[e]) continue; inside ^= crosser.EdgeOrVertexCrossing(&g_.vertex(g_.edge(e).first), &g_.vertex(g_.edge(e).second)); } component.root_sign = inside ? 1 : -1; } } // Merges the degeneracies from all components together, and computes the // final "is_hole" status of each edge (since up to this point, the "is_hole" // value has been expressed relative to the root vertex of each component). vector DegeneracyFinder::MergeDegeneracies( const vector& components) const { vector result; for (const Component& component : components) { S2_DCHECK_NE(component.root_sign, 0); bool invert = component.root_sign < 0; for (const auto& d : component.degeneracies) { result.push_back(PolygonDegeneracy(d.edge_id, d.is_hole ^ invert)); } } std::sort(result.begin(), result.end()); return result; } void CheckGraphOptions(const Graph& g) { S2_DCHECK(g.options().edge_type() == EdgeType::DIRECTED); S2_DCHECK(g.options().degenerate_edges() == DegenerateEdges::DISCARD || g.options().degenerate_edges() == DegenerateEdges::DISCARD_EXCESS); S2_DCHECK(g.options().sibling_pairs() == SiblingPairs::DISCARD || g.options().sibling_pairs() == SiblingPairs::DISCARD_EXCESS); } } // namespace vector FindPolygonDegeneracies(const Graph& g, S2Error* error) { CheckGraphOptions(g); if (g.options().degenerate_edges() == DegenerateEdges::DISCARD && g.options().sibling_pairs() == SiblingPairs::DISCARD) { return {}; // All degeneracies have already been discarded. } return DegeneracyFinder(&g).Run(error); } bool IsFullyDegenerate(const S2Builder::Graph& g) { CheckGraphOptions(g); const vector& edges = g.edges(); for (int e = 0; e < g.num_edges(); ++e) { Edge edge = edges[e]; if (edge.first == edge.second) continue; if (!std::binary_search(edges.begin(), edges.end(), Graph::reverse(edge))) { return false; } } return true; } } // namespace s2builderutil s2/src/s2/s2closest_point_query_base.h0000644000176200001440000007511514530411473017446 0ustar liggesusers// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) // // See S2ClosestPointQueryBase (defined below) for an overview. #ifndef S2_S2CLOSEST_POINT_QUERY_BASE_H_ #define S2_S2CLOSEST_POINT_QUERY_BASE_H_ #include #include "s2/base/logging.h" #include "absl/container/inlined_vector.h" #include "s2/s1chord_angle.h" #include "s2/s2cap.h" #include "s2/s2cell_id.h" #include "s2/s2cell_union.h" #include "s2/s2distance_target.h" #include "s2/s2edge_distances.h" #include "s2/s2point_index.h" #include "s2/s2region_coverer.h" // Options that control the set of points returned. Note that by default // *all* points are returned, so you will always want to set either the // max_results() option or the max_distance() option (or both). // // This class is also available as S2ClosestPointQueryBase::Options. // (It is defined here to avoid depending on the "Data" template argument.) // // The Distance template argument is described below. template class S2ClosestPointQueryBaseOptions { public: using Delta = typename Distance::Delta; S2ClosestPointQueryBaseOptions(); // Specifies that at most "max_results" points should be returned. // // REQUIRES: max_results >= 1 // DEFAULT: numeric_limits::max() int max_results() const; void set_max_results(int max_results); static constexpr int kMaxMaxResults = std::numeric_limits::max(); // Specifies that only points whose distance to the target is less than // "max_distance" should be returned. // // Note that points whose distance is exactly equal to "max_distance" are // not returned. In most cases this doesn't matter (since distances are // not computed exactly in the first place), but if such points are needed // then you can retrieve them by specifying "max_distance" as the next // largest representable Distance. For example, if Distance is an // S1ChordAngle then you can specify max_distance.Successor(). // // DEFAULT: Distance::Infinity() Distance max_distance() const; void set_max_distance(Distance max_distance); // Specifies that points up to max_error() further away than the true // closest points may be substituted in the result set, as long as such // points satisfy all the remaining search criteria (such as max_distance). // This option only has an effect if max_results() is also specified; // otherwise all points closer than max_distance() will always be returned. // // Note that this does not affect how the distance between points is // computed; it simply gives the algorithm permission to stop the search // early as soon as the best possible improvement drops below max_error(). // // This can be used to implement distance predicates efficiently. For // example, to determine whether the minimum distance is less than D, the // IsDistanceLess() method sets max_results() == 1 and max_distance() == // max_error() == D. This causes the algorithm to terminate as soon as it // finds any point whose distance is less than D, rather than continuing to // search for a point that is even closer. // // DEFAULT: Distance::Delta::Zero() Delta max_error() const; void set_max_error(Delta max_error); // Specifies that points must be contained by the given S2Region. "region" // is owned by the caller and must persist during the lifetime of this // object. The value may be changed between calls to FindClosestPoints(), // or reset by calling set_region(nullptr). // // Note that if you want to set the region to a disc around a target point, // it is faster to use a PointTarget with set_max_distance() instead. You // can also call both methods, e.g. to set a maximum distance and also // require that points lie within a given rectangle. const S2Region* region() const; void set_region(const S2Region* region); // Specifies that distances should be computed by examining every point // rather than using the S2ShapeIndex. This is useful for testing, // benchmarking, and debugging. // // DEFAULT: false bool use_brute_force() const; void set_use_brute_force(bool use_brute_force); private: Distance max_distance_ = Distance::Infinity(); Delta max_error_ = Delta::Zero(); const S2Region* region_ = nullptr; int max_results_ = kMaxMaxResults; bool use_brute_force_ = false; }; // S2ClosestPointQueryBase is a templatized class for finding the closest // point(s) to a given target. It is not intended to be used directly, but // rather to serve as the implementation of various specialized classes with // more convenient APIs (such as S2ClosestPointQuery). It is flexible enough // so that it can be adapted to compute maximum distances and even potentially // Hausdorff distances. // // By using the appropriate options, this class can answer questions such as: // // - Find the minimum distance between a point collection A and a target B. // - Find all points in collection A that are within a distance D of target B. // - Find the k points of collection A that are closest to a given point P. // // The target is any class that implements the S2DistanceTarget interface. // There are predefined targets for points, edges, S2Cells, and S2ShapeIndexes // (arbitrary collctions of points, polylines, and polygons). // // The Distance template argument is used to represent distances. Usually it // is a thin wrapper around S1ChordAngle, but another distance type may be // used as long as it implements the Distance concept described in // s2distance_targets.h. For example this can be used to measure maximum // distances, to get more accuracy, or to measure non-spheroidal distances. template class S2ClosestPointQueryBase { public: using Delta = typename Distance::Delta; using Index = S2PointIndex; using PointData = typename Index::PointData; using Options = S2ClosestPointQueryBaseOptions; // The Target class represents the geometry to which the distance is // measured. For example, there can be subtypes for measuring the distance // to a point, an edge, or to an S2ShapeIndex (an arbitrary collection of // geometry). // // Implementations do *not* need to be thread-safe. They may cache data or // allocate temporary data structures in order to improve performance. using Target = S2DistanceTarget; // Each "Result" object represents a closest point. class Result { public: // The default constructor creates an "empty" result, with a distance() of // Infinity() and non-dereferencable point() and data() values. Result() : distance_(Distance::Infinity()), point_data_(nullptr) {} // Constructs a Result object for the given point. Result(Distance distance, const PointData* point_data) : distance_(distance), point_data_(point_data) {} // Returns true if this Result object does not refer to any data point. // (The only case where an empty Result is returned is when the // FindClosestPoint() method does not find any points that meet the // specified criteria.) bool is_empty() const { return point_data_ == nullptr; } // The distance from the target to this point. Distance distance() const { return distance_; } // The point itself. const S2Point& point() const { return point_data_->point(); } // The client-specified data associated with this point. const Data& data() const { return point_data_->data(); } // Returns true if two Result objects are identical. friend bool operator==(const Result& x, const Result& y) { return (x.distance_ == y.distance_ && x.point_data_ == y.point_data_); } // Compares two Result objects first by distance, then by point_data(). friend bool operator<(const Result& x, const Result& y) { if (x.distance_ < y.distance_) return true; if (y.distance_ < x.distance_) return false; return x.point_data_ < y.point_data_; } private: Distance distance_; const PointData* point_data_; }; // The minimum number of points that a cell must contain to enqueue it // rather than processing its contents immediately. static constexpr int kMinPointsToEnqueue = 13; // Default constructor; requires Init() to be called. S2ClosestPointQueryBase(); ~S2ClosestPointQueryBase(); // Convenience constructor that calls Init(). explicit S2ClosestPointQueryBase(const Index* index); // S2ClosestPointQueryBase is not copyable. S2ClosestPointQueryBase(const S2ClosestPointQueryBase&) = delete; void operator=(const S2ClosestPointQueryBase&) = delete; // Initializes the query. // REQUIRES: ReInit() must be called if "index" is modified. void Init(const Index* index); // Reinitializes the query. This method must be called whenever the // underlying index is modified. void ReInit(); // Return a reference to the underlying S2PointIndex. const Index& index() const; // Returns the closest points to the given target that satisfy the given // options. This method may be called multiple times. std::vector FindClosestPoints(Target* target, const Options& options); // This version can be more efficient when this method is called many times, // since it does not require allocating a new vector on each call. void FindClosestPoints(Target* target, const Options& options, std::vector* results); // Convenience method that returns exactly one point. If no points satisfy // the given search criteria, then a Result with distance() == Infinity() // and is_empty() == true is returned. // // REQUIRES: options.max_results() == 1 Result FindClosestPoint(Target* target, const Options& options); private: using Iterator = typename Index::Iterator; const Options& options() const { return *options_; } void FindClosestPointsInternal(Target* target, const Options& options); void FindClosestPointsBruteForce(); void FindClosestPointsOptimized(); void InitQueue(); void InitCovering(); void AddInitialRange(S2CellId first_id, S2CellId last_id); void MaybeAddResult(const PointData* point_data); bool ProcessOrEnqueue(S2CellId id, Iterator* iter, bool seek); const Index* index_; const Options* options_; Target* target_; // True if max_error() must be subtracted from priority queue cell distances // in order to ensure that such distances are measured conservatively. This // is true only if the target takes advantage of max_error() in order to // return faster results, and 0 < max_error() < distance_limit_. bool use_conservative_cell_distance_; // For the optimized algorihm we precompute the top-level S2CellIds that // will be added to the priority queue. There can be at most 6 of these // cells. Essentially this is just a covering of the indexed points. std::vector index_covering_; // The distance beyond which we can safely ignore further candidate points. // (Candidates that are exactly at the limit are ignored; this is more // efficient for UpdateMinDistance() and should not affect clients since // distance measurements have a small amount of error anyway.) // // Initially this is the same as the maximum distance specified by the user, // but it can also be updated by the algorithm (see MaybeAddResult). Distance distance_limit_; // The current result set is stored in one of three ways: // // - If max_results() == 1, the best result is kept in result_singleton_. // // - If max_results() == "infinity", results are appended to result_vector_ // and sorted/uniqued at the end. // // - Otherwise results are kept in a priority queue so that we can // progressively reduce the distance limit once max_results() results // have been found. Result result_singleton_; std::vector result_vector_; std::priority_queue> result_set_; // The algorithm maintains a priority queue of unprocessed S2CellIds, sorted // in increasing order of distance from the target. struct QueueEntry { // A lower bound on the distance from the target to "id". This is the key // of the priority queue. Distance distance; // The cell being queued. S2CellId id; QueueEntry(Distance _distance, S2CellId _id) : distance(_distance), id(_id) {} bool operator<(const QueueEntry& other) const { // The priority queue returns the largest elements first, so we want the // "largest" entry to have the smallest distance. return other.distance < distance; } }; using CellQueue = std::priority_queue>; CellQueue queue_; // Temporaries, defined here to avoid multiple allocations / initializations. Iterator iter_; std::vector region_covering_; std::vector max_distance_covering_; std::vector intersection_with_region_; std::vector intersection_with_max_distance_; const PointData* tmp_point_data_[kMinPointsToEnqueue - 1]; }; ////////////////// Implementation details follow //////////////////// template inline S2ClosestPointQueryBaseOptions::S2ClosestPointQueryBaseOptions() { } template inline int S2ClosestPointQueryBaseOptions::max_results() const { return max_results_; } template inline void S2ClosestPointQueryBaseOptions::set_max_results( int max_results) { S2_DCHECK_GE(max_results, 1); max_results_ = max_results; } template inline Distance S2ClosestPointQueryBaseOptions::max_distance() const { return max_distance_; } template inline void S2ClosestPointQueryBaseOptions::set_max_distance( Distance max_distance) { max_distance_ = max_distance; } template inline typename Distance::Delta S2ClosestPointQueryBaseOptions::max_error() const { return max_error_; } template inline void S2ClosestPointQueryBaseOptions::set_max_error( Delta max_error) { max_error_ = max_error; } template inline const S2Region* S2ClosestPointQueryBaseOptions::region() const { return region_; } template inline void S2ClosestPointQueryBaseOptions::set_region( const S2Region* region) { region_ = region; } template inline bool S2ClosestPointQueryBaseOptions::use_brute_force() const { return use_brute_force_; } template inline void S2ClosestPointQueryBaseOptions::set_use_brute_force( bool use_brute_force) { use_brute_force_ = use_brute_force; } template S2ClosestPointQueryBase::S2ClosestPointQueryBase() { } template S2ClosestPointQueryBase::~S2ClosestPointQueryBase() { // Prevent inline destructor bloat by providing a definition. } template inline S2ClosestPointQueryBase::S2ClosestPointQueryBase( const S2PointIndex* index) : S2ClosestPointQueryBase() { Init(index); } template void S2ClosestPointQueryBase::Init( const S2PointIndex* index) { index_ = index; ReInit(); } template void S2ClosestPointQueryBase::ReInit() { iter_.Init(index_); index_covering_.clear(); } template inline const S2PointIndex& S2ClosestPointQueryBase::index() const { return *index_; } template inline std::vector::Result> S2ClosestPointQueryBase::FindClosestPoints( Target* target, const Options& options) { std::vector results; FindClosestPoints(target, options, &results); return results; } template typename S2ClosestPointQueryBase::Result S2ClosestPointQueryBase::FindClosestPoint( Target* target, const Options& options) { S2_DCHECK_EQ(options.max_results(), 1); FindClosestPointsInternal(target, options); return result_singleton_; } template void S2ClosestPointQueryBase::FindClosestPoints( Target* target, const Options& options, std::vector* results) { FindClosestPointsInternal(target, options); results->clear(); if (options.max_results() == 1) { if (!result_singleton_.is_empty()) { results->push_back(result_singleton_); } } else if (options.max_results() == Options::kMaxMaxResults) { std::sort(result_vector_.begin(), result_vector_.end()); std::unique_copy(result_vector_.begin(), result_vector_.end(), std::back_inserter(*results)); result_vector_.clear(); } else { results->reserve(result_set_.size()); for (; !result_set_.empty(); result_set_.pop()) { results->push_back(result_set_.top()); } // The priority queue returns the largest elements first. std::reverse(results->begin(), results->end()); S2_DCHECK(std::is_sorted(results->begin(), results->end())); } } template void S2ClosestPointQueryBase::FindClosestPointsInternal( Target* target, const Options& options) { target_ = target; options_ = &options; distance_limit_ = options.max_distance(); result_singleton_ = Result(); S2_DCHECK(result_vector_.empty()); S2_DCHECK(result_set_.empty()); S2_DCHECK_GE(target->max_brute_force_index_size(), 0); if (distance_limit_ == Distance::Zero()) return; if (options.max_results() == Options::kMaxMaxResults && options.max_distance() == Distance::Infinity() && options.region() == nullptr) { S2_LOG(WARNING) << "Returning all points " "(max_results/max_distance/region not set)"; } // If max_error() > 0 and the target takes advantage of this, then we may // need to adjust the distance estimates to the priority queue cells to // ensure that they are always a lower bound on the true distance. For // example, suppose max_distance == 100, max_error == 30, and we compute the // distance to the target from some cell C0 as d(C0) == 80. Then because // the target takes advantage of max_error(), the true distance could be as // low as 50. In order not to miss edges contained by such cells, we need // to subtract max_error() from the distance estimates. This behavior is // controlled by the use_conservative_cell_distance_ flag. // // However there is one important case where this adjustment is not // necessary, namely when max_distance() < max_error(). This is because // max_error() only affects the algorithm once at least max_results() edges // have been found that satisfy the given distance limit. At that point, // max_error() is subtracted from distance_limit_ in order to ensure that // any further matches are closer by at least that amount. But when // max_distance() < max_error(), this reduces the distance limit to 0, // i.e. all remaining candidate cells and edges can safely be discarded. // (Note that this is how IsDistanceLess() and friends are implemented.) // // Note that Distance::Delta only supports operator==. bool target_uses_max_error = (!(options.max_error() == Delta::Zero()) && target_->set_max_error(options.max_error())); // Note that we can't compare max_error() and distance_limit_ directly // because one is a Delta and one is a Distance. Instead we subtract them. use_conservative_cell_distance_ = target_uses_max_error && (distance_limit_ == Distance::Infinity() || Distance::Zero() < distance_limit_ - options.max_error()); // Note that given point is processed only once (unlike S2ClosestEdgeQuery), // and therefore we don't need to worry about the possibility of having // duplicate points in the results. if (options.use_brute_force() || index_->num_points() <= target_->max_brute_force_index_size()) { FindClosestPointsBruteForce(); } else { FindClosestPointsOptimized(); } } template void S2ClosestPointQueryBase::FindClosestPointsBruteForce() { for (iter_.Begin(); !iter_.done(); iter_.Next()) { MaybeAddResult(&iter_.point_data()); } } template void S2ClosestPointQueryBase::FindClosestPointsOptimized() { InitQueue(); while (!queue_.empty()) { // We need to copy the top entry before removing it, and we need to remove // it before adding any new entries to the queue. QueueEntry entry = queue_.top(); queue_.pop(); // Work around weird parse error in gcc 4.9 by using a local variable for // entry.distance. Distance distance = entry.distance; if (!(distance < distance_limit_)) { queue_ = CellQueue(); // Clear any remaining entries. break; } S2CellId child = entry.id.child_begin(); // We already know that it has too many points, so process its children. // Each child may either be processed directly or enqueued again. The // loop is optimized so that we don't seek unnecessarily. bool seek = true; for (int i = 0; i < 4; ++i, child = child.next()) { seek = ProcessOrEnqueue(child, &iter_, seek); } } } template void S2ClosestPointQueryBase::InitQueue() { S2_DCHECK(queue_.empty()); // Optimization: rather than starting with the entire index, see if we can // limit the search region to a small disc. Then we can find a covering for // that disc and intersect it with the covering for the index. This can // save a lot of work when the search region is small. S2Cap cap = target_->GetCapBound(); if (cap.is_empty()) return; // Empty target. if (options().max_results() == 1) { // If the user is searching for just the closest point, we can compute an // upper bound on search radius by seeking to the center of the target's // bounding cap and looking at the adjacent index points (in S2CellId // order). The minimum distance to either of these points is an upper // bound on the search radius. // // TODO(ericv): The same strategy would also work for small values of // max_results() > 1, e.g. max_results() == 20, except that we would need to // examine more neighbors (at least 20, and preferably 20 in each // direction). It's not clear whether this is a common case, though, and // also this would require extending MaybeAddResult() so that it can // remove duplicate entries. (The points added here may be re-added by // ProcessOrEnqueue(), but this is okay when max_results() == 1.) iter_.Seek(S2CellId(cap.center())); if (!iter_.done()) { MaybeAddResult(&iter_.point_data()); } if (iter_.Prev()) { MaybeAddResult(&iter_.point_data()); } // Skip the rest of the algorithm if we found a matching point. if (distance_limit_ == Distance::Zero()) return; } // We start with a covering of the set of indexed points, then intersect it // with the given region (if any) and maximum search radius disc (if any). if (index_covering_.empty()) InitCovering(); const std::vector* initial_cells = &index_covering_; if (options().region()) { S2RegionCoverer coverer; coverer.mutable_options()->set_max_cells(4); coverer.GetCovering(*options().region(), ®ion_covering_); S2CellUnion::GetIntersection(index_covering_, region_covering_, &intersection_with_region_); initial_cells = &intersection_with_region_; } if (distance_limit_ < Distance::Infinity()) { S2RegionCoverer coverer; coverer.mutable_options()->set_max_cells(4); S1ChordAngle radius = cap.radius() + distance_limit_.GetChordAngleBound(); S2Cap search_cap(cap.center(), radius); coverer.GetFastCovering(search_cap, &max_distance_covering_); S2CellUnion::GetIntersection(*initial_cells, max_distance_covering_, &intersection_with_max_distance_); initial_cells = &intersection_with_max_distance_; } iter_.Begin(); for (int i = 0; i < initial_cells->size() && !iter_.done(); ++i) { S2CellId id = (*initial_cells)[i]; ProcessOrEnqueue(id, &iter_, id.range_min() > iter_.id() /*seek*/); } } template void S2ClosestPointQueryBase::InitCovering() { // Compute the "index covering", which is a small number of S2CellIds that // cover the indexed points. There are two cases: // // - If the index spans more than one face, then there is one covering cell // per spanned face, just big enough to cover the index cells on that face. // // - If the index spans only one face, then we find the smallest cell "C" // that covers the index cells on that face (just like the case above). // Then for each of the 4 children of "C", if the child contains any index // cells then we create a covering cell that is big enough to just fit // those index cells (i.e., shrinking the child as much as possible to fit // its contents). This essentially replicates what would happen if we // started with "C" as the covering cell, since "C" would immediately be // split, except that we take the time to prune the children further since // this will save work on every subsequent query. index_covering_.reserve(6); iter_.Finish(); if (!iter_.Prev()) return; // Empty index. S2CellId index_last_id = iter_.id(); iter_.Begin(); if (iter_.id() != index_last_id) { // The index has at least two cells. Choose a level such that the entire // index can be spanned with at most 6 cells (if the index spans multiple // faces) or 4 cells (it the index spans a single face). int level = iter_.id().GetCommonAncestorLevel(index_last_id) + 1; // Visit each potential covering cell except the last (handled below). S2CellId last_id = index_last_id.parent(level); for (S2CellId id = iter_.id().parent(level); id != last_id; id = id.next()) { // Skip any covering cells that don't contain any index cells. if (id.range_max() < iter_.id()) continue; // Find the range of index cells contained by this covering cell and // then shrink the cell if necessary so that it just covers them. S2CellId cell_first_id = iter_.id(); iter_.Seek(id.range_max().next()); iter_.Prev(); S2CellId cell_last_id = iter_.id(); iter_.Next(); AddInitialRange(cell_first_id, cell_last_id); } } AddInitialRange(iter_.id(), index_last_id); } // Adds a cell to index_covering_ that covers the given inclusive range. // // REQUIRES: "first" and "last" have a common ancestor. template void S2ClosestPointQueryBase::AddInitialRange( S2CellId first_id, S2CellId last_id) { // Add the lowest common ancestor of the given range. int level = first_id.GetCommonAncestorLevel(last_id); S2_DCHECK_GE(level, 0); index_covering_.push_back(first_id.parent(level)); } template void S2ClosestPointQueryBase::MaybeAddResult( const PointData* point_data) { Distance distance = distance_limit_; if (!target_->UpdateMinDistance(point_data->point(), &distance)) return; const S2Region* region = options().region(); if (region && !region->Contains(point_data->point())) return; Result result(distance, point_data); if (options().max_results() == 1) { // Optimization for the common case where only the closest point is wanted. result_singleton_ = result; distance_limit_ = result.distance() - options().max_error(); } else if (options().max_results() == Options::kMaxMaxResults) { result_vector_.push_back(result); // Sort/unique at end. } else { // Add this point to result_set_. Note that with the current algorithm // each candidate point is considered at most once (except for one special // case where max_results() == 1, see InitQueue for details), so we don't // need to worry about possibly adding a duplicate entry here. if (result_set_.size() >= options().max_results()) { result_set_.pop(); // Replace the furthest result point. } result_set_.push(result); if (result_set_.size() >= options().max_results()) { distance_limit_ = result_set_.top().distance() - options().max_error(); } } } // Either process the contents of the given cell immediately, or add it to the // queue to be subdivided. If "seek" is false, then "iter" must already be // positioned at the first indexed point within or after this cell. // // Returns "true" if the cell was added to the queue, and "false" if it was // processed immediately, in which case "iter" is left positioned at the next // cell in S2CellId order. template bool S2ClosestPointQueryBase::ProcessOrEnqueue( S2CellId id, Iterator* iter, bool seek) { if (seek) iter->Seek(id.range_min()); if (id.is_leaf()) { // Leaf cells can't be subdivided. for (; !iter->done() && iter->id() == id; iter->Next()) { MaybeAddResult(&iter->point_data()); } return false; // No need to seek to next child. } S2CellId last = id.range_max(); int num_points = 0; for (; !iter->done() && iter->id() <= last; iter->Next()) { if (num_points == kMinPointsToEnqueue - 1) { // This cell has too many points (including this one), so enqueue it. S2Cell cell(id); Distance distance = distance_limit_; // We check "region_" second because it may be relatively expensive. if (target_->UpdateMinDistance(cell, &distance) && (!options().region() || options().region()->MayIntersect(cell))) { if (use_conservative_cell_distance_) { // Ensure that "distance" is a lower bound on distance to the cell. distance = distance - options().max_error(); } queue_.push(QueueEntry(distance, id)); } return true; // Seek to next child. } tmp_point_data_[num_points++] = &iter->point_data(); } // There were few enough points that we might as well process them now. for (int i = 0; i < num_points; ++i) { MaybeAddResult(tmp_point_data_[i]); } return false; // No need to seek to next child. } #endif // S2_S2CLOSEST_POINT_QUERY_BASE_H_ s2/src/s2/s2polyline_alignment.cc0000644000176200001440000004146614530411473016373 0ustar liggesusers// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "s2/s2polyline_alignment.h" #include "s2/s2polyline_alignment_internal.h" #include #include #include #include #include #include #include "s2/base/logging.h" #include "absl/memory/memory.h" #include "s2/util/math/mathutil.h" namespace s2polyline_alignment { Window::Window(const std::vector& strides) { S2_DCHECK(!strides.empty()) << "Cannot construct empty window."; S2_DCHECK(strides[0].start == 0) << "First element of start_cols is non-zero."; strides_ = strides; rows_ = strides.size(); cols_ = strides.back().end; S2_DCHECK(this->IsValid()) << "Constructor validity check fail."; } Window::Window(const WarpPath& warp_path) { S2_DCHECK(!warp_path.empty()) << "Cannot construct window from empty warp path."; S2_DCHECK(warp_path.front() == std::make_pair(0, 0)) << "Must start at (0, 0)."; rows_ = warp_path.back().first + 1; S2_DCHECK(rows_ > 0) << "Must have at least one row."; cols_ = warp_path.back().second + 1; S2_DCHECK(cols_ > 0) << "Must have at least one column."; strides_.resize(rows_); int prev_row = 0; int curr_row = 0; int stride_start = 0; int stride_stop = 0; for (const auto& pair : warp_path) { curr_row = pair.first; if (curr_row > prev_row) { strides_[prev_row] = {stride_start, stride_stop}; stride_start = pair.second; prev_row = curr_row; } stride_stop = pair.second + 1; } S2_DCHECK_EQ(curr_row, rows_ - 1); strides_[rows_ - 1] = {stride_start, stride_stop}; S2_DCHECK(this->IsValid()) << "Constructor validity check fail."; } Window Window::Upsample(const int new_rows, const int new_cols) const { S2_DCHECK(new_rows >= rows_) << "Upsampling: New_rows < current_rows"; S2_DCHECK(new_cols >= cols_) << "Upsampling: New_cols < current_cols"; const double row_scale = static_cast(new_rows) / rows_; const double col_scale = static_cast(new_cols) / cols_; std::vector new_strides(new_rows); ColumnStride from_stride; for (int row = 0; row < new_rows; ++row) { from_stride = strides_[static_cast((row + 0.5) / row_scale)]; new_strides[row] = {static_cast(col_scale * from_stride.start + 0.5), static_cast(col_scale * from_stride.end + 0.5)}; } return Window(new_strides); } // This code takes advantage of the fact that the dilation window is square to // ensure that we can compute the stride for each output row in constant time. // TODO (mrdmnd): a potential optimization might be to combine this method and // the Upsample method into a single "Expand" method. For the sake of // testing, I haven't done that here, but I think it would be fairly // straightforward to do so. This method generally isn't very expensive so it // feels unnecessary to combine them. Window Window::Dilate(const int radius) const { S2_DCHECK(radius >= 0) << "Negative dilation radius."; std::vector new_strides(rows_); int prev_row, next_row; for (int row = 0; row < rows_; ++row) { prev_row = std::max(0, row - radius); next_row = std::min(row + radius, rows_ - 1); new_strides[row] = {std::max(0, strides_[prev_row].start - radius), std::min(strides_[next_row].end + radius, cols_)}; } return Window(new_strides); } // Debug string implemented primarily for testing purposes. std::string Window::DebugString() const { std::stringstream buffer; for (int row = 0; row < rows_; ++row) { for (int col = 0; col < cols_; ++col) { buffer << (strides_[row].InRange(col) ? " *" : " ."); } buffer << std::endl; } return buffer.str(); } // Valid Windows require the following structural conditions to hold: // 1) All rows must consist of a single contiguous stride of `true` values. // 2) All strides are greater than zero length (i.e. no empty rows). // 3) The index of the first `true` column in a row must be at least as // large as the index of the first `true` column in the previous row. // 4) The index of the last `true` column in a row must be at least as large // as the index of the last `true` column in the previous row. // 5) strides[0].start = 0 (the first cell is always filled). // 6) strides[n_rows-1].end = n_cols (the last cell is filled). bool Window::IsValid() const { if (rows_ <= 0 || cols_ <= 0 || strides_.front().start != 0 || strides_.back().end != cols_) { return false; } ColumnStride prev = {-1, -1}; for (const auto& curr : strides_) { if (curr.end <= curr.start || curr.start < prev.start || curr.end < prev.end) { return false; } prev = curr; } return true; } inline double BoundsCheckedTableCost(const int row, const int col, const ColumnStride& stride, const CostTable& table) { if (row < 0 && col < 0) { return 0.0; } else if (row < 0 || col < 0 || !stride.InRange(col)) { return DOUBLE_MAX; } else { return table[row][col]; } } // Perform dynamic timewarping by filling in the DP table on cells that are // inside our search window. For an exact (all-squares) evaluation, this // incurs bounds checking overhead - we don't need to ensure that we're inside // the appropriate cells in the window, because it's guaranteed. Structuring // the program to reuse code for both the EXACT and WINDOWED cases by // abstracting EXACT as a window with full-covering strides is done for // maintainability reasons. One potential optimization here might be to overload // this function to skip bounds checking when the window is full. // // As a note of general interest, the Dynamic Timewarp algorithm as stated here // prefers shorter warp paths, when two warp paths might be equally costly. This // is because it favors progressing in the sequences simultaneously due to the // equal weighting of a diagonal step in the cost table with a horizontal or // vertical step. This may be counterintuitive, but represents the standard // implementation of this algorithm. TODO(user) - future implementations could // allow weights on the lookup costs to mitigate this. // // This is the hottest routine in the whole package, please be careful to // profile any future changes made here. // // This method takes time proportional to the number of cells in the window, // which can range from O(max(a, b)) cells (best) to O(a*b) cells (worst) VertexAlignment DynamicTimewarp(const S2Polyline& a, const S2Polyline& b, const Window& w) { const int rows = a.num_vertices(); const int cols = b.num_vertices(); auto costs = CostTable(rows, std::vector(cols)); ColumnStride curr; ColumnStride prev = ColumnStride::All(); for (int row = 0; row < rows; ++row) { curr = w.GetColumnStride(row); for (int col = curr.start; col < curr.end; ++col) { double d_cost = BoundsCheckedTableCost(row - 1, col - 1, prev, costs); double u_cost = BoundsCheckedTableCost(row - 1, col - 0, prev, costs); double l_cost = BoundsCheckedTableCost(row - 0, col - 1, curr, costs); costs[row][col] = std::min({d_cost, u_cost, l_cost}) + (a.vertex(row) - b.vertex(col)).Norm2(); } prev = curr; } // Now we walk back through the cost table and build up the warp path. // Somewhat surprisingly, it is faster to recover the path this way than it // is to save the comparisons from the computation we *already did* to get the // direction we came from. The author speculates that this behavior is // assignment-cost-related: to persist direction, we have to do extra // stores/loads of "directional" information, and the extra assignment cost // this incurs is larger than the cost to simply redo the comparisons. // It's probably worth revisiting this assumption in the future. // As it turns out, the following code ends up effectively free. WarpPath warp_path; warp_path.reserve(std::max(a.num_vertices(), b.num_vertices())); int row = a.num_vertices() - 1; int col = b.num_vertices() - 1; curr = w.GetCheckedColumnStride(row); prev = w.GetCheckedColumnStride(row - 1); while (row >= 0 && col >= 0) { warp_path.push_back({row, col}); double d_cost = BoundsCheckedTableCost(row - 1, col - 1, prev, costs); double u_cost = BoundsCheckedTableCost(row - 1, col - 0, prev, costs); double l_cost = BoundsCheckedTableCost(row - 0, col - 1, curr, costs); if (d_cost <= u_cost && d_cost <= l_cost) { row -= 1; col -= 1; curr = w.GetCheckedColumnStride(row); prev = w.GetCheckedColumnStride(row - 1); } else if (u_cost <= l_cost) { row -= 1; curr = w.GetCheckedColumnStride(row); prev = w.GetCheckedColumnStride(row - 1); } else { col -= 1; } } std::reverse(warp_path.begin(), warp_path.end()); return VertexAlignment(costs.back().back(), warp_path); } std::unique_ptr HalfResolution(const S2Polyline& in) { const int n = in.num_vertices(); std::vector vertices; vertices.reserve(n / 2); for (int i = 0; i < n; i += 2) { vertices.push_back(in.vertex(i)); } return absl::make_unique(vertices); } // Helper methods for GetMedoidPolyline and GetConsensusPolyline to auto-select // appropriate cost function / alignment functions. double CostFn(const S2Polyline& a, const S2Polyline& b, bool approx) { return approx ? GetApproxVertexAlignment(a, b).alignment_cost : GetExactVertexAlignmentCost(a, b); } VertexAlignment AlignmentFn(const S2Polyline& a, const S2Polyline& b, bool approx) { return approx ? GetApproxVertexAlignment(a, b) : GetExactVertexAlignment(a, b); } // PUBLIC API IMPLEMENTATION DETAILS // This is the constant-space implementation of Dynamic Timewarp that can // compute the alignment cost, but not the warp path. double GetExactVertexAlignmentCost(const S2Polyline& a, const S2Polyline& b) { const int a_n = a.num_vertices(); const int b_n = b.num_vertices(); S2_CHECK(a_n > 0) << "A is empty polyline."; S2_CHECK(b_n > 0) << "B is empty polyline."; std::vector cost(b_n, DOUBLE_MAX); double left_diag_min_cost = 0; for (int row = 0; row < a_n; ++row) { for (int col = 0; col < b_n; ++col) { double up_cost = cost[col]; cost[col] = std::min(left_diag_min_cost, up_cost) + (a.vertex(row) - b.vertex(col)).Norm2(); left_diag_min_cost = std::min(cost[col], up_cost); } left_diag_min_cost = DOUBLE_MAX; } return cost.back(); } VertexAlignment GetExactVertexAlignment(const S2Polyline& a, const S2Polyline& b) { const int a_n = a.num_vertices(); const int b_n = b.num_vertices(); S2_CHECK(a_n > 0) << "A is empty polyline."; S2_CHECK(b_n > 0) << "B is empty polyline."; const auto w = Window(std::vector(a_n, {0, b_n})); return DynamicTimewarp(a, b, w); } VertexAlignment GetApproxVertexAlignment(const S2Polyline& a, const S2Polyline& b, const int radius) { // Determined experimentally, through benchmarking, as about the points at // which ExactAlignment is faster than ApproxAlignment, so we use these as // our switchover points to exact computation mode. const int kSizeSwitchover = 32; const double kDensitySwitchover = 0.85; const int a_n = a.num_vertices(); const int b_n = b.num_vertices(); S2_CHECK(a_n > 0) << "A is empty polyline."; S2_CHECK(b_n > 0) << "B is empty polyline."; S2_CHECK(radius >= 0) << "Radius is negative."; // If we've hit the point where doing a full, direct solve is guaranteed to // be faster, then terminate the recursion and do that. if (a_n - radius < kSizeSwitchover || b_n - radius < kSizeSwitchover) { return GetExactVertexAlignment(a, b); } // If we've hit the point where the window will be probably be so full that we // might as well compute an exact solution, then terminate recursion to do so. if (std::max(a_n, b_n) * (2 * radius + 1) > a_n * b_n * kDensitySwitchover) { return GetExactVertexAlignment(a, b); } // Otherwise, shrink the input polylines, recursively compute the vertex // alignment using this method, and then compute the final alignment using // the projected alignment `proj` on an upsampled, dilated window. const auto a_half = HalfResolution(a); const auto b_half = HalfResolution(b); const auto proj = GetApproxVertexAlignment(*a_half, *b_half, radius); const auto w = Window(proj.warp_path).Upsample(a_n, b_n).Dilate(radius); return DynamicTimewarp(a, b, w); } // This method calls the approx method with a reasonable default for radius. VertexAlignment GetApproxVertexAlignment(const S2Polyline& a, const S2Polyline& b) { const int max_length = std::max(a.num_vertices(), b.num_vertices()); const int radius = static_cast(std::pow(max_length, 0.25)); return GetApproxVertexAlignment(a, b, radius); } // We use some of the symmetry of our metric to avoid computing all N^2 // alignments. Specifically, because cost_fn(a, b) = cost_fn(b, a), and // cost_fn(a, a) = 0, we can compute only the lower triangle of cost matrix // and then mirror it across the diagonal to save on cost_fn invocations. int GetMedoidPolyline(const std::vector>& polylines, const MedoidOptions options) { const int num_polylines = polylines.size(); const bool approx = options.approx(); S2_CHECK_GT(num_polylines, 0); // costs[i] stores total cost of aligning [i] with all other polylines. std::vector costs(num_polylines, 0.0); for (int i = 0; i < num_polylines; ++i) { for (int j = i + 1; j < num_polylines; ++j) { double cost = CostFn(*polylines[i], *polylines[j], approx); costs[i] += cost; costs[j] += cost; } } return std::min_element(costs.begin(), costs.end()) - costs.begin(); } // Implements Iterative Dynamic Timewarp Barycenter Averaging algorithm from // // https://pdfs.semanticscholar.org/a596/8ca9488199291ffe5473643142862293d69d.pdf // // Algorithm: // Initialize consensus sequence with either the medoid or an arbitrary // element (chosen here to be the first element in the input collection). // While the consensus polyline `consensus` hasn't converged and we haven't // exceeded our iteration cap: // For each polyline `p` in the input, // Compute vertex alignment from the current consensus to `p`. // For each (c_index, p_index) pair in the warp path, // Add the S2Point pts->vertex(p_index) to S2Point consensus[c_index] // Normalize (compute centroid) of each consensus point. // Determine if consensus is converging; if no vertex has moved or we've hit // the iteration cap, halt. // // This algorithm takes O(iteration_cap * num_polylines) pairwise alignments. std::unique_ptr GetConsensusPolyline( const std::vector>& polylines, const ConsensusOptions options) { const int num_polylines = polylines.size(); S2_CHECK_GT(num_polylines, 0); const bool approx = options.approx(); // Seed a consensus polyline, either arbitrarily with first element, or with // the medoid. If seeding with medoid, inherit approx parameter from options. int seed_index = 0; if (options.seed_medoid()) { MedoidOptions medoid_options; medoid_options.set_approx(approx); seed_index = GetMedoidPolyline(polylines, medoid_options); } auto consensus = std::unique_ptr(polylines[seed_index]->Clone()); const int num_consensus_vertices = consensus->num_vertices(); S2_DCHECK_GT(num_consensus_vertices, 1); bool converged = false; int iterations = 0; while (!converged && iterations < options.iteration_cap()) { std::vector points(num_consensus_vertices, S2Point()); for (const auto& polyline : polylines) { const auto alignment = AlignmentFn(*consensus, *polyline, approx); for (const auto& pair : alignment.warp_path) { points[pair.first] += polyline->vertex(pair.second); } } for (S2Point& p : points) { p = p.Normalize(); } ++iterations; auto new_consensus = absl::make_unique(points); converged = new_consensus->ApproxEquals(*consensus); consensus = std::move(new_consensus); } return consensus; } } // namespace s2polyline_alignment s2/src/s2/s2shapeutil_range_iterator.h0000644000176200001440000000426514530411473017423 0ustar liggesusers// Copyright 2013 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #ifndef S2_S2SHAPEUTIL_RANGE_ITERATOR_H_ #define S2_S2SHAPEUTIL_RANGE_ITERATOR_H_ #include "s2/s2cell_id.h" #include "s2/s2shape_index.h" class S2Loop; class S2Error; namespace s2shapeutil { // RangeIterator is a wrapper over S2ShapeIndex::Iterator with extra methods // that are useful for merging the contents of two or more S2ShapeIndexes. class RangeIterator { public: // Construct a new RangeIterator positioned at the first cell of the index. explicit RangeIterator(const S2ShapeIndex& index); // The current S2CellId and cell contents. S2CellId id() const { return it_.id(); } const S2ShapeIndexCell& cell() const { return it_.cell(); } // The min and max leaf cell ids covered by the current cell. If done() is // true, these methods return a value larger than any valid cell id. S2CellId range_min() const { return range_min_; } S2CellId range_max() const { return range_max_; } void Next(); bool done() { return it_.done(); } // Position the iterator at the first cell that overlaps or follows // "target", i.e. such that range_max() >= target.range_min(). void SeekTo(const RangeIterator& target); // Position the iterator at the first cell that follows "target", i.e. the // first cell such that range_min() > target.range_max(). void SeekBeyond(const RangeIterator& target); private: // Updates internal state after the iterator has been repositioned. void Refresh(); S2ShapeIndex::Iterator it_; S2CellId range_min_, range_max_; }; } // namespace s2shapeutil #endif // S2_S2SHAPEUTIL_RANGE_ITERATOR_H_ s2/src/s2/s2polygon.cc0000644000176200001440000016217714530411473014174 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2polygon.h" #include #include #include #include #include #include #include #include #include "s2/base/casts.h" #include "s2/base/commandlineflags.h" #include "s2/base/logging.h" #include "s2/mutable_s2shape_index.h" #include "s2/s1angle.h" #include "s2/s1interval.h" #include "s2/s2boolean_operation.h" #include "s2/s2builder.h" #include "s2/s2builderutil_s2polygon_layer.h" #include "s2/s2builderutil_s2polyline_layer.h" #include "s2/s2builderutil_s2polyline_vector_layer.h" #include "s2/s2builderutil_snap_functions.h" #include "s2/s2cap.h" #include "s2/s2cell.h" #include "s2/s2cell_id.h" #include "s2/s2cell_union.h" #include "s2/s2closest_edge_query.h" #include "s2/s2contains_point_query.h" #include "s2/s2coords.h" #include "s2/s2crossing_edge_query.h" #include "s2/s2debug.h" #include "s2/s2edge_clipping.h" #include "s2/s2edge_crosser.h" #include "s2/s2edge_crossings.h" #include "s2/s2error.h" #include "s2/s2latlng.h" #include "s2/s2latlng_rect.h" #include "s2/s2latlng_rect_bounder.h" #include "s2/s2loop.h" #include "s2/s2measures.h" #include "s2/s2metrics.h" #include "s2/s2point_compression.h" #include "s2/s2polyline.h" #include "s2/s2predicates.h" #include "s2/s2shape_index.h" #include "s2/s2shape_index_region.h" #include "s2/s2shapeutil_visit_crossing_edge_pairs.h" #include "absl/container/fixed_array.h" #include "absl/container/inlined_vector.h" #include "absl/memory/memory.h" #include "s2/util/coding/coder.h" using absl::make_unique; using s2builderutil::IdentitySnapFunction; using s2builderutil::S2PolygonLayer; using s2builderutil::S2PolylineLayer; using s2builderutil::S2PolylineVectorLayer; using s2builderutil::S2CellIdSnapFunction; using std::fabs; using std::max; using std::min; using std::pair; using std::set; using std::sqrt; using std::unique_ptr; using std::vector; DEFINE_bool( s2polygon_lazy_indexing, true, "Build the S2ShapeIndex only when it is first needed. This can save " "significant amounts of memory and time when geometry is constructed but " "never queried, for example when converting from one format to another."); // The maximum number of loops we'll allow when decoding a polygon. // The default value of 10 million is 200x bigger than the number of DEFINE_int32( s2polygon_decode_max_num_loops, 10000000, "The upper limit on the number of loops that are allowed by the " "S2Polygon::Decode method."); // When adding a new encoding, be aware that old binaries will not // be able to decode it. static const unsigned char kCurrentUncompressedEncodingVersionNumber = 1; static const unsigned char kCurrentCompressedEncodingVersionNumber = 4; S2Polygon::S2Polygon() : s2debug_override_(S2Debug::ALLOW), error_inconsistent_loop_orientations_(false), num_vertices_(0), unindexed_contains_calls_(0) { } S2Polygon::S2Polygon(vector> loops, S2Debug override) : s2debug_override_(override) { InitNested(std::move(loops)); } S2Polygon::S2Polygon(unique_ptr loop, S2Debug override) : s2debug_override_(override) { Init(std::move(loop)); } S2Polygon::S2Polygon(const S2Cell& cell) : s2debug_override_(S2Debug::ALLOW) { Init(make_unique(cell)); } void S2Polygon::set_s2debug_override(S2Debug override) { s2debug_override_ = override; } S2Debug S2Polygon::s2debug_override() const { return s2debug_override_; } void S2Polygon::Copy(const S2Polygon* src) { ClearLoops(); for (int i = 0; i < src->num_loops(); ++i) { loops_.emplace_back(src->loop(i)->Clone()); } s2debug_override_ = src->s2debug_override_; // Don't copy error_inconsistent_loop_orientations_, since this is not a // property of the polygon but only of the way the polygon was constructed. num_vertices_ = src->num_vertices(); unindexed_contains_calls_.store(0, std::memory_order_relaxed); bound_ = src->bound_; subregion_bound_ = src->subregion_bound_; InitIndex(); // TODO(ericv): Copy the index efficiently. } S2Polygon* S2Polygon::Clone() const { S2Polygon* result = new S2Polygon; result->Copy(this); return result; } vector> S2Polygon::Release() { // Reset the polygon to be empty. vector> loops; loops.swap(loops_); ClearLoops(); num_vertices_ = 0; bound_ = S2LatLngRect::Empty(); subregion_bound_ = S2LatLngRect::Empty(); return loops; } void S2Polygon::ClearLoops() { ClearIndex(); loops_.clear(); error_inconsistent_loop_orientations_ = false; } S2Polygon::~S2Polygon() { ClearLoops(); } bool S2Polygon::IsValid() const { S2Error error; if (FindValidationError(&error)) { S2_LOG_IF(ERROR, FLAGS_s2debug) << error; return false; } return true; } bool S2Polygon::FindValidationError(S2Error* error) const { for (int i = 0; i < num_loops(); ++i) { // Check for loop errors that don't require building an S2ShapeIndex. if (loop(i)->FindValidationErrorNoIndex(error)) { error->Init(error->code(), "Loop %d: %s", i, error->text().c_str()); return true; } // Check that no loop is empty, and that the full loop only appears in the // full polygon. if (loop(i)->is_empty()) { error->Init(S2Error::POLYGON_EMPTY_LOOP, "Loop %d: empty loops are not allowed", i); return true; } if (loop(i)->is_full() && num_loops() > 1) { error->Init(S2Error::POLYGON_EXCESS_FULL_LOOP, "Loop %d: full loop appears in non-full polygon", i); return true; } } // Check for loop self-intersections and loop pairs that cross // (including duplicate edges and vertices). if (s2shapeutil::FindSelfIntersection(index_, error)) return true; // Check whether InitOriented detected inconsistent loop orientations. if (error_inconsistent_loop_orientations_) { error->Init(S2Error::POLYGON_INCONSISTENT_LOOP_ORIENTATIONS, "Inconsistent loop orientations detected"); return true; } // Finally, verify the loop nesting hierarchy. return FindLoopNestingError(error); } bool S2Polygon::FindLoopNestingError(S2Error* error) const { // First check that the loop depths make sense. for (int last_depth = -1, i = 0; i < num_loops(); ++i) { int depth = loop(i)->depth(); if (depth < 0 || depth > last_depth + 1) { error->Init(S2Error::POLYGON_INVALID_LOOP_DEPTH, "Loop %d: invalid loop depth (%d)", i, depth); return true; } last_depth = depth; } // Then check that they correspond to the actual loop nesting. This test // is quadratic in the number of loops but the cost per iteration is small. for (int i = 0; i < num_loops(); ++i) { int last = GetLastDescendant(i); for (int j = 0; j < num_loops(); ++j) { if (i == j) continue; bool nested = (j >= i + 1) && (j <= last); const bool reverse_b = false; if (loop(i)->ContainsNonCrossingBoundary(loop(j), reverse_b) != nested) { error->Init(S2Error::POLYGON_INVALID_LOOP_NESTING, "Invalid nesting: loop %d should %scontain loop %d", i, nested ? "" : "not ", j); return true; } } } return false; } void S2Polygon::InsertLoop(S2Loop* new_loop, S2Loop* parent, LoopMap* loop_map) { vector* children; for (bool done = false; !done; ) { children = &(*loop_map)[parent]; done = true; for (S2Loop* child : *children) { if (child->ContainsNested(new_loop)) { parent = child; done = false; break; } } } // Some of the children of the parent loop may now be children of // the new loop. vector* new_children = &(*loop_map)[new_loop]; for (int i = 0; i < children->size();) { S2Loop* child = (*children)[i]; if (new_loop->ContainsNested(child)) { new_children->push_back(child); children->erase(children->begin() + i); } else { ++i; } } children->push_back(new_loop); } void S2Polygon::InitLoops(LoopMap* loop_map) { std::stack loop_stack({nullptr}); int depth = -1; while (!loop_stack.empty()) { S2Loop* loop = loop_stack.top(); loop_stack.pop(); if (loop != nullptr) { depth = loop->depth(); loops_.emplace_back(loop); } const vector& children = (*loop_map)[loop]; for (int i = children.size() - 1; i >= 0; --i) { S2Loop* child = children[i]; S2_DCHECK(child != nullptr); child->set_depth(depth + 1); loop_stack.push(child); } } } void S2Polygon::InitIndex() { S2_DCHECK_EQ(0, index_.num_shape_ids()); index_.Add(make_unique(this)); if (!FLAGS_s2polygon_lazy_indexing) { index_.ForceBuild(); } if (FLAGS_s2debug && s2debug_override_ == S2Debug::ALLOW) { // Note that FLAGS_s2debug is false in optimized builds (by default). S2_CHECK(IsValid()); } } void S2Polygon::ClearIndex() { unindexed_contains_calls_.store(0, std::memory_order_relaxed); index_.Clear(); } void S2Polygon::InitNested(vector> loops) { ClearLoops(); loops_.swap(loops); if (num_loops() == 1) { InitOneLoop(); return; } LoopMap loop_map; for (int i = 0; i < num_loops(); ++i) { InsertLoop(loop(i), nullptr, &loop_map); } // Reorder the loops in depth-first traversal order. // Loops are now owned by loop_map, don't let them be // deleted by clear(). for (auto& loop : loops_) loop.release(); loops_.clear(); InitLoops(&loop_map); // Compute num_vertices_, bound_, subregion_bound_. InitLoopProperties(); } void S2Polygon::Init(unique_ptr loop) { // We don't allow empty loops in the other Init() methods because deleting // them changes the number of loops, which is awkward to handle. ClearLoops(); if (loop->is_empty()) { InitLoopProperties(); } else { loops_.push_back(std::move(loop)); InitOneLoop(); } } // This is an internal method that expects that loops_ has already been // initialized with a single non-empty loop. void S2Polygon::InitOneLoop() { S2_DCHECK_EQ(1, num_loops()); S2Loop* loop = loops_[0].get(); loop->set_depth(0); error_inconsistent_loop_orientations_ = false; num_vertices_ = loop->num_vertices(); bound_ = loop->GetRectBound(); subregion_bound_ = S2LatLngRectBounder::ExpandForSubregions(bound_); InitIndex(); } void S2Polygon::InitOriented(vector> loops) { // Here is the algorithm: // // 1. Remember which of the given loops contain S2::Origin(). // // 2. Invert loops as necessary to ensure that they are nestable (i.e., no // loop contains the complement of any other loop). This may result in a // set of loops corresponding to the complement of the given polygon, but // we will fix that problem later. // // We make the loops nestable by first normalizing all the loops (i.e., // inverting any loops whose curvature is negative). This handles // all loops except those whose curvature is very close to zero // (within the maximum error tolerance). Any such loops are inverted if // and only if they contain S2::Origin(). (In theory this step is only // necessary if there are at least two such loops.) The resulting set of // loops is guaranteed to be nestable. // // 3. Build the polygon. This yields either the desired polygon or its // complement. // // 4. If there is at least one loop, we find a loop L that is adjacent to // S2::Origin() (where "adjacent" means that there exists a path // connecting S2::Origin() to some vertex of L such that the path does // not cross any loop). There may be a single such adjacent loop, or // there may be several (in which case they should all have the same // contains_origin() value). We choose L to be the loop containing the // origin whose depth is greatest, or loop(0) (a top-level shell) if no // such loop exists. // // 5. If (L originally contained origin) != (polygon contains origin), we // invert the polygon. This is done by inverting a top-level shell whose // curvature is minimal and then fixing the nesting hierarchy. Note // that because we normalized all the loops initially, this step is only // necessary if the polygon requires at least one non-normalized loop to // represent it. set contained_origin; for (int i = 0; i < loops.size(); ++i) { S2Loop* loop = loops[i].get(); if (loop->contains_origin()) { contained_origin.insert(loop); } double angle = loop->GetCurvature(); if (fabs(angle) > loop->GetCurvatureMaxError()) { // Normalize the loop. if (angle < 0) loop->Invert(); } else { // Ensure that the loop does not contain the origin. if (loop->contains_origin()) loop->Invert(); } } InitNested(std::move(loops)); if (num_loops() > 0) { S2Loop* origin_loop = loop(0); bool polygon_contains_origin = false; for (int i = 0; i < num_loops(); ++i) { if (loop(i)->contains_origin()) { polygon_contains_origin ^= true; origin_loop = loop(i); } } if (contained_origin.count(origin_loop) != polygon_contains_origin) { Invert(); } } // Verify that the original loops had consistent shell/hole orientations. // Each original loop L should have been inverted if and only if it now // represents a hole. for (int i = 0; i < loops_.size(); ++i) { if ((contained_origin.count(loop(i)) != loop(i)->contains_origin()) != loop(i)->is_hole()) { // There is no point in saving the loop index, because the error is a // property of the entire set of loops. In general there is no way to // determine which ones are incorrect. error_inconsistent_loop_orientations_ = true; if (FLAGS_s2debug && s2debug_override_ == S2Debug::ALLOW) { // The FLAGS_s2debug validity checking usually happens in InitIndex(), // but this error is detected too late for that. S2_CHECK(IsValid()); // Always fails. } } } } void S2Polygon::InitLoopProperties() { num_vertices_ = 0; bound_ = S2LatLngRect::Empty(); for (int i = 0; i < num_loops(); ++i) { if (loop(i)->depth() == 0) { bound_ = bound_.Union(loop(i)->GetRectBound()); } num_vertices_ += loop(i)->num_vertices(); } subregion_bound_ = S2LatLngRectBounder::ExpandForSubregions(bound_); InitIndex(); } int S2Polygon::GetParent(int k) const { int depth = loop(k)->depth(); if (depth == 0) return -1; // Optimization. while (--k >= 0 && loop(k)->depth() >= depth) continue; return k; } int S2Polygon::GetLastDescendant(int k) const { if (k < 0) return num_loops() - 1; int depth = loop(k)->depth(); while (++k < num_loops() && loop(k)->depth() > depth) continue; return k - 1; } double S2Polygon::GetArea() const { double area = 0; for (int i = 0; i < num_loops(); ++i) { area += loop(i)->sign() * loop(i)->GetArea(); } return area; } S2Point S2Polygon::GetCentroid() const { S2Point centroid; for (int i = 0; i < num_loops(); ++i) { centroid += loop(i)->sign() * loop(i)->GetCentroid(); } return centroid; } int S2Polygon::GetSnapLevel() const { int snap_level = -1; for (const unique_ptr& child : loops_) { for (int j = 0; j < child->num_vertices(); ++j) { int face; unsigned int si, ti; int level = S2::XYZtoFaceSiTi(child->vertex(j), &face, &si, &ti); if (level < 0) return level; // Vertex is not a cell center. if (level != snap_level) { if (snap_level < 0) { snap_level = level; // First vertex. } else { return -1; // Vertices at more than one cell level. } } } } return snap_level; } S1Angle S2Polygon::GetDistance(const S2Point& x) const { // Note that S2Polygon::Contains(S2Point) is slightly more efficient than // the generic version used by S2ClosestEdgeQuery. if (Contains(x)) return S1Angle::Zero(); return GetDistanceToBoundary(x); } S1Angle S2Polygon::GetDistanceToBoundary(const S2Point& x) const { S2ClosestEdgeQuery::Options options; options.set_include_interiors(false); S2ClosestEdgeQuery::PointTarget t(x); return S2ClosestEdgeQuery(&index_, options).GetDistance(&t).ToAngle(); } /*static*/ pair S2Polygon::GetOverlapFractions( const S2Polygon* a, const S2Polygon* b) { S2Polygon intersection; intersection.InitToIntersection(a, b); double intersection_area = intersection.GetArea(); double a_area = a->GetArea(); double b_area = b->GetArea(); return std::make_pair( intersection_area >= a_area ? 1 : intersection_area / a_area, intersection_area >= b_area ? 1 : intersection_area / b_area); } S2Point S2Polygon::Project(const S2Point& x) const { if (Contains(x)) return x; return ProjectToBoundary(x); } S2Point S2Polygon::ProjectToBoundary(const S2Point& x) const { S2ClosestEdgeQuery::Options options; options.set_include_interiors(false); S2ClosestEdgeQuery q(&index_, options); S2ClosestEdgeQuery::PointTarget target(x); S2ClosestEdgeQuery::Result edge = q.FindClosestEdge(&target); return q.Project(x, edge); } bool S2Polygon::Contains(const S2Polygon* b) const { // It's worth checking bounding rectangles, since they are precomputed. // Note that the first bound has been expanded to account for possible // numerical errors in the second bound. if (!subregion_bound_.Contains(b->bound_)) { // It is possible that A contains B even though Bound(A) does not contain // Bound(B). This can only happen when polygon B has at least two outer // shells and the union of the two bounds spans all longitudes. For // example, suppose that B consists of two shells with a longitude gap // between them, while A consists of one shell that surrounds both shells // of B but goes the other way around the sphere (so that it does not // intersect the longitude gap). // // For convenience we just check whether B has at least two loops rather // than two outer shells. if (b->num_loops() == 1 || !bound_.lng().Union(b->bound_.lng()).is_full()) { return false; } } // The following case is not handled by S2BooleanOperation because it only // determines whether the boundary of the result is empty (which does not // distinguish between the full and empty polygons). if (is_empty() && b->is_full()) return false; return S2BooleanOperation::Contains(index_, b->index_); } bool S2Polygon::Intersects(const S2Polygon* b) const { // It's worth checking bounding rectangles, since they are precomputed. if (!bound_.Intersects(b->bound_)) return false; // The following case is not handled by S2BooleanOperation because it only // determines whether the boundary of the result is empty (which does not // distinguish between the full and empty polygons). if (is_full() && b->is_full()) return true; return S2BooleanOperation::Intersects(index_, b->index_); } S2Cap S2Polygon::GetCapBound() const { return bound_.GetCapBound(); } void S2Polygon::GetCellUnionBound(vector *cell_ids) const { return MakeS2ShapeIndexRegion(&index_).GetCellUnionBound(cell_ids); } bool S2Polygon::Contains(const S2Cell& target) const { return MakeS2ShapeIndexRegion(&index_).Contains(target); } bool S2Polygon::ApproxContains(const S2Polygon* b, S1Angle tolerance) const { S2Polygon difference; difference.InitToApproxDifference(b, this, tolerance); return difference.is_empty(); } bool S2Polygon::ApproxDisjoint(const S2Polygon* b, S1Angle tolerance) const { S2Polygon intersection; intersection.InitToApproxIntersection(b, this, tolerance); return intersection.is_empty(); } bool S2Polygon::ApproxEquals(const S2Polygon* b, S1Angle tolerance) const { // TODO(ericv): This can be implemented more cheaply with S2Builder, by // simply adding all the edges from one polygon, adding the reversed edge // from the other polygon, and turning on the options to split edges and // discard sibling pairs. Then the polygons are approximately equal if the // output graph has no edges. S2Polygon symmetric_difference; symmetric_difference.InitToApproxSymmetricDifference(b, this, tolerance); return symmetric_difference.is_empty(); } bool S2Polygon::MayIntersect(const S2Cell& target) const { return MakeS2ShapeIndexRegion(&index_).MayIntersect(target); } bool S2Polygon::Contains(const S2Point& p) const { // NOTE(ericv): A bounds check slows down this function by about 50%. It is // worthwhile only when it might allow us to delay building the index. if (!index_.is_fresh() && !bound_.Contains(p)) return false; // For small polygons it is faster to just check all the crossings. // Otherwise we keep track of the number of calls to Contains() and only // build the index once enough calls have been made so that we think it is // worth the effort. See S2Loop::Contains(S2Point) for detailed comments. static const int kMaxBruteForceVertices = 32; static const int kMaxUnindexedContainsCalls = 20; if (num_vertices() <= kMaxBruteForceVertices || (!index_.is_fresh() && ++unindexed_contains_calls_ != kMaxUnindexedContainsCalls)) { bool inside = false; for (int i = 0; i < num_loops(); ++i) { // Use brute force to avoid building the loop's S2ShapeIndex. inside ^= loop(i)->BruteForceContains(p); } return inside; } // Otherwise we look up the S2ShapeIndex cell containing this point. return MakeS2ContainsPointQuery(&index_).Contains(p); } void S2Polygon::Encode(Encoder* const encoder) const { if (num_vertices_ == 0) { EncodeCompressed(encoder, nullptr, S2::kMaxCellLevel); return; } // Converts all the polygon vertices to S2XYZFaceSiTi format. absl::FixedArray all_vertices(num_vertices_); S2XYZFaceSiTi* current_loop_vertices = all_vertices.data(); for (const unique_ptr& loop : loops_) { loop->GetXYZFaceSiTiVertices(current_loop_vertices); current_loop_vertices += loop->num_vertices(); } // Computes a histogram of the cell levels at which the vertices are snapped. // cell_level is -1 for unsnapped, or 0 through kMaxCellLevel if snapped, // so we add one to it to get a non-negative index. (histogram[0] is the // number of unsnapped vertices, histogram[i] the number of vertices // snapped at level i-1). std::array histogram; histogram.fill(0); for (const auto& v : all_vertices) { histogram[v.cell_level + 1] += 1; } // Compute the level at which most of the vertices are snapped. // If multiple levels have the same maximum number of vertices // snapped to it, the first one (lowest level number / largest // area / smallest encoding length) will be chosen, so this // is desired. Start with histogram[1] since histogram[0] is // the number of unsnapped vertices, which we don't care about. const auto max_iter = std::max_element(histogram.begin() + 1, histogram.end()); // snap_level will be at position histogram[snap_level + 1], see above. const int snap_level = max_iter - (histogram.begin() + 1); const int num_snapped = *max_iter; // Choose an encoding format based on the number of unsnapped vertices and a // rough estimate of the encoded sizes. // The compressed encoding requires approximately 4 bytes per vertex plus // "exact_point_size" for each unsnapped vertex (encoded as an S2Point plus // the index at which it is located). int exact_point_size = sizeof(S2Point) + 2; int num_unsnapped = num_vertices_ - num_snapped; int compressed_size = 4 * num_vertices_ + exact_point_size * num_unsnapped; int lossless_size = sizeof(S2Point) * num_vertices_; if (compressed_size < lossless_size) { EncodeCompressed(encoder, all_vertices.data(), snap_level); } else { EncodeUncompressed(encoder); } } void S2Polygon::EncodeUncompressed(Encoder* const encoder) const { encoder->Ensure(10); // Sufficient encoder->put8(kCurrentUncompressedEncodingVersionNumber); // This code used to write "owns_loops_", so write "true" for compatibility. encoder->put8(true); // Encode obsolete "has_holes_" field for backwards compatibility. bool has_holes = false; for (int i = 0; i < num_loops(); ++i) { if (loop(i)->is_hole()) has_holes = true; } encoder->put8(has_holes); encoder->put32(loops_.size()); S2_DCHECK_GE(encoder->avail(), 0); for (int i = 0; i < num_loops(); ++i) { loop(i)->Encode(encoder); } bound_.Encode(encoder); } bool S2Polygon::Decode(Decoder* const decoder) { if (decoder->avail() < sizeof(unsigned char)) return false; unsigned char version = decoder->get8(); switch (version) { case kCurrentUncompressedEncodingVersionNumber: return DecodeUncompressed(decoder, false); case kCurrentCompressedEncodingVersionNumber: return DecodeCompressed(decoder); } return false; } bool S2Polygon::DecodeWithinScope(Decoder* const decoder) { if (decoder->avail() < sizeof(unsigned char)) return false; unsigned char version = decoder->get8(); switch (version) { case kCurrentUncompressedEncodingVersionNumber: return DecodeUncompressed(decoder, true); case kCurrentCompressedEncodingVersionNumber: return DecodeCompressed(decoder); } return false; } bool S2Polygon::DecodeUncompressed(Decoder* const decoder, bool within_scope) { if (decoder->avail() < 2 * sizeof(uint8) + sizeof(uint32)) return false; ClearLoops(); decoder->get8(); // Ignore irrelevant serialized owns_loops_ value. decoder->get8(); // Ignore irrelevant serialized has_holes_ value. // Polygons with no loops are explicitly allowed here: a newly created // polygon has zero loops and such polygons encode and decode properly. const uint32 num_loops = decoder->get32(); if (num_loops > FLAGS_s2polygon_decode_max_num_loops) return false; loops_.reserve(num_loops); num_vertices_ = 0; for (int i = 0; i < num_loops; ++i) { loops_.push_back(make_unique()); loops_.back()->set_s2debug_override(s2debug_override()); if (within_scope) { if (!loops_.back()->DecodeWithinScope(decoder)) return false; } else { if (!loops_.back()->Decode(decoder)) return false; } num_vertices_ += loops_.back()->num_vertices(); } if (!bound_.Decode(decoder)) return false; subregion_bound_ = S2LatLngRectBounder::ExpandForSubregions(bound_); InitIndex(); return true; } // TODO(ericv): Consider adding this to the S2Loop API. (May also want an // undirected version (CompareDirected vs CompareUndirected); should they // return a sign, or have separate "<" and "==" methods?) int S2Polygon::CompareLoops(const S2Loop* a, const S2Loop* b) { if (a->num_vertices() != b->num_vertices()) { return a->num_vertices() - b->num_vertices(); } S2::LoopOrder ao = a->GetCanonicalLoopOrder(); S2::LoopOrder bo = b->GetCanonicalLoopOrder(); if (ao.dir != bo.dir) return ao.dir - bo.dir; for (int n = a->num_vertices(), ai = ao.first, bi = bo.first; --n >= 0; ai += ao.dir, bi += bo.dir) { if (a->vertex(ai) < b->vertex(bi)) return -1; if (a->vertex(ai) > b->vertex(bi)) return 1; } return 0; } void S2Polygon::Invert() { // Inverting any one loop will invert the polygon. The best loop to invert // is the one whose area is largest, since this yields the smallest area // after inversion. The loop with the largest area is always at depth 0. // The descendents of this loop all have their depth reduced by 1, while the // former siblings of this loop all have their depth increased by 1. // The empty and full polygons are handled specially. if (is_empty()) { loops_.push_back(make_unique(S2Loop::kFull())); } else if (is_full()) { ClearLoops(); } else { // Find the loop whose area is largest (i.e., whose curvature is // smallest), minimizing calls to GetCurvature(). In particular, for // polygons with a single shell at level 0 there is not need to call // GetCurvature() at all. (This method is relatively expensive.) int best = 0; const double kNone = 10.0; // Flag that means "not computed yet" double best_angle = kNone; for (int i = 1; i < num_loops(); ++i) { if (loop(i)->depth() == 0) { // We defer computing the curvature of loop 0 until we discover // that the polygon has another top-level shell. if (best_angle == kNone) best_angle = loop(best)->GetCurvature(); double angle = loop(i)->GetCurvature(); // We break ties deterministically in order to avoid having the output // depend on the input order of the loops. if (angle < best_angle || (angle == best_angle && CompareLoops(loop(i), loop(best)) < 0)) { best = i; best_angle = angle; } } } // Build the new loops vector, starting with the inverted loop. loop(best)->Invert(); vector> new_loops; new_loops.reserve(num_loops()); // Add the former siblings of this loop as descendants. int last_best = GetLastDescendant(best); new_loops.push_back(std::move(loops_[best])); for (int i = 0; i < num_loops(); ++i) { if (i < best || i > last_best) { loop(i)->set_depth(loop(i)->depth() + 1); new_loops.push_back(std::move(loops_[i])); } } // Add the former children of this loop as siblings. for (int i = 0; i < num_loops(); ++i) { if (i > best && i <= last_best) { loop(i)->set_depth(loop(i)->depth() - 1); new_loops.push_back(std::move(loops_[i])); } } loops_.swap(new_loops); S2_DCHECK_EQ(new_loops.size(), num_loops()); } ClearIndex(); InitLoopProperties(); } void S2Polygon::InitToComplement(const S2Polygon* a) { Copy(a); Invert(); } bool S2Polygon::InitToOperation(S2BooleanOperation::OpType op_type, const S2Builder::SnapFunction& snap_function, const S2Polygon& a, const S2Polygon& b, S2Error* error) { S2BooleanOperation::Options options; options.set_snap_function(snap_function); S2BooleanOperation op(op_type, make_unique(this), options); return op.Build(a.index_, b.index_, error); } void S2Polygon::InitToOperation(S2BooleanOperation::OpType op_type, const S2Builder::SnapFunction& snap_function, const S2Polygon& a, const S2Polygon& b) { S2Error error; if (!InitToOperation(op_type, snap_function, a, b, &error)) { S2_LOG(DFATAL) << S2BooleanOperation::OpTypeToString(op_type) << " operation failed: " << error; } } void S2Polygon::InitToIntersection(const S2Polygon* a, const S2Polygon* b) { InitToApproxIntersection(a, b, S2::kIntersectionMergeRadius); } void S2Polygon::InitToApproxIntersection(const S2Polygon* a, const S2Polygon* b, S1Angle snap_radius) { InitToIntersection(*a, *b, IdentitySnapFunction(snap_radius)); } void S2Polygon::InitToIntersection( const S2Polygon& a, const S2Polygon& b, const S2Builder::SnapFunction& snap_function) { if (!a.bound_.Intersects(b.bound_)) return; InitToOperation(S2BooleanOperation::OpType::INTERSECTION, snap_function, a, b); } bool S2Polygon::InitToIntersection( const S2Polygon& a, const S2Polygon& b, const S2Builder::SnapFunction& snap_function, S2Error* error) { if (!a.bound_.Intersects(b.bound_)) return true; // Success. return InitToOperation(S2BooleanOperation::OpType::INTERSECTION, snap_function, a, b, error); } void S2Polygon::InitToUnion(const S2Polygon* a, const S2Polygon* b) { InitToApproxUnion(a, b, S2::kIntersectionMergeRadius); } void S2Polygon::InitToApproxUnion(const S2Polygon* a, const S2Polygon* b, S1Angle snap_radius) { InitToUnion(*a, *b, IdentitySnapFunction(snap_radius)); } void S2Polygon::InitToUnion( const S2Polygon& a, const S2Polygon& b, const S2Builder::SnapFunction& snap_function) { InitToOperation(S2BooleanOperation::OpType::UNION, snap_function, a, b); } bool S2Polygon::InitToUnion( const S2Polygon& a, const S2Polygon& b, const S2Builder::SnapFunction& snap_function, S2Error* error) { return InitToOperation(S2BooleanOperation::OpType::UNION, snap_function, a, b, error); } void S2Polygon::InitToDifference(const S2Polygon* a, const S2Polygon* b) { InitToApproxDifference(a, b, S2::kIntersectionMergeRadius); } void S2Polygon::InitToApproxDifference(const S2Polygon* a, const S2Polygon* b, S1Angle snap_radius) { InitToDifference(*a, *b, IdentitySnapFunction(snap_radius)); } void S2Polygon::InitToDifference( const S2Polygon& a, const S2Polygon& b, const S2Builder::SnapFunction& snap_function) { InitToOperation(S2BooleanOperation::OpType::DIFFERENCE, snap_function, a, b); } bool S2Polygon::InitToDifference( const S2Polygon& a, const S2Polygon& b, const S2Builder::SnapFunction& snap_function, S2Error* error) { return InitToOperation(S2BooleanOperation::OpType::DIFFERENCE, snap_function, a, b, error); } void S2Polygon::InitToSymmetricDifference(const S2Polygon* a, const S2Polygon* b) { InitToApproxSymmetricDifference(a, b, S2::kIntersectionMergeRadius); } void S2Polygon::InitToApproxSymmetricDifference(const S2Polygon* a, const S2Polygon* b, S1Angle snap_radius) { InitToSymmetricDifference(*a, *b, IdentitySnapFunction(snap_radius)); } void S2Polygon::InitToSymmetricDifference( const S2Polygon& a, const S2Polygon& b, const S2Builder::SnapFunction& snap_function) { InitToOperation(S2BooleanOperation::OpType::SYMMETRIC_DIFFERENCE, snap_function, a, b); } bool S2Polygon::InitToSymmetricDifference( const S2Polygon& a, const S2Polygon& b, const S2Builder::SnapFunction& snap_function, S2Error* error) { return InitToOperation(S2BooleanOperation::OpType::SYMMETRIC_DIFFERENCE, snap_function, a, b, error); } void S2Polygon::InitFromBuilder(const S2Polygon& a, S2Builder* builder) { builder->StartLayer(make_unique(this)); builder->AddPolygon(a); S2Error error; if (!builder->Build(&error)) { S2_LOG(DFATAL) << "Could not build polygon: " << error; } // If there are no loops, check whether the result should be the full // polygon rather than the empty one. (See InitToApproxIntersection.) if (num_loops() == 0) { if (a.bound_.Area() > 2 * M_PI && a.GetArea() > 2 * M_PI) Invert(); } } void S2Polygon::InitToSnapped(const S2Polygon* a, int snap_level) { S2Builder builder{S2Builder::Options(S2CellIdSnapFunction(snap_level))}; InitFromBuilder(*a, &builder); } void S2Polygon::InitToSimplified(const S2Polygon& a, const S2Builder::SnapFunction& snap_function) { S2Builder::Options options(snap_function); options.set_simplify_edge_chains(true); S2Builder builder(options); InitFromBuilder(a, &builder); } // Given a point "p" inside an S2Cell or on its boundary, return a mask // indicating which of the S2Cell edges the point lies on. All boundary // comparisons are to within a maximum "u" or "v" error of "tolerance_uv". // Bit "i" in the result is set if and only "p" is incident to the edge // corresponding to S2Cell::edge(i). uint8 GetCellEdgeIncidenceMask(const S2Cell& cell, const S2Point& p, double tolerance_uv) { uint8 mask = 0; R2Point uv; if (S2::FaceXYZtoUV(cell.face(), p, &uv)) { R2Rect bound = cell.GetBoundUV(); if (FLAGS_s2debug) S2_DCHECK(bound.Expanded(tolerance_uv).Contains(uv)); if (fabs(uv[1] - bound[1][0]) <= tolerance_uv) mask |= 1; if (fabs(uv[0] - bound[0][1]) <= tolerance_uv) mask |= 2; if (fabs(uv[1] - bound[1][1]) <= tolerance_uv) mask |= 4; if (fabs(uv[0] - bound[0][0]) <= tolerance_uv) mask |= 8; } return mask; } void S2Polygon::InitToSimplifiedInCell( const S2Polygon* a, const S2Cell& cell, S1Angle snap_radius, S1Angle boundary_tolerance) { // The polygon to be simplified consists of "boundary edges" that follow the // cell boundary and "interior edges" that do not. We want to simplify the // interior edges while leaving the boundary edges unchanged. It's not // sufficient to call S2Builder::ForceVertex() on all boundary vertices. // For example, suppose the polygon includes a triangle ABC where all three // vertices are on the cell boundary and B is a cell corner. Then if // interior edge AC snaps to vertex B, this loop would become degenerate and // be removed. Similarly, we don't want boundary edges to snap to interior // vertices, since this also would cause portions of the polygon along the // boundary to be removed. // // Instead we use a two-pass algorithm. In the first pass, we simplify // *only* the interior edges, using ForceVertex() to ensure that any edge // endpoints on the cell boundary do not move. In the second pass, we add // the boundary edges (which are guaranteed to still form loops with the // interior edges) and build the output polygon. // // Note that in theory, simplifying the interior edges could create an // intersection with one of the boundary edges, since if two interior edges // intersect very near the boundary then the intersection point could be // slightly outside the cell (by at most S2::kIntersectionError). // This is the *only* way that a self-intersection can be created, and it is // expected to be extremely rare. Nevertheless we use a small snap radius // in the second pass in order to eliminate any such self-intersections. // // We also want to preserve the cyclic vertex order of loops, so that the // original polygon can be reconstructed when no simplification is possible // (i.e., idempotency). In order to do this, we group the input edges into // a sequence of polylines. Each polyline contains only one type of edge // (interior or boundary). We use S2Builder to simplify the interior // polylines, while the boundary polylines are passed through unchanged. // Each interior polyline is in its own S2Builder layer in order to keep the // edges in sequence. This lets us ensure that in the second pass, the // edges are added in their original order so that S2PolygonLayer can // reconstruct the original loops. // We want an upper bound on how much "u" or "v" can change when a point on // the boundary of the S2Cell is moved away by up to "boundary_tolerance". // Inverting this, instead we could compute a lower bound on how far a point // can move away from an S2Cell edge when "u" or "v" is changed by a given // amount. The latter quantity is simply (S2::kMinWidth.deriv() / 2) // under the S2_LINEAR_PROJECTION model, where we divide by 2 because we // want the bound in terms of (u = 2 * s - 1) rather than "s" itself. // Consulting s2metrics.cc, this value is sqrt(2/3)/2 = sqrt(1/6). // Going back to the original problem, this gives: double boundary_tolerance_uv = sqrt(6.0) * boundary_tolerance.radians(); // The first pass yields a collection of simplified polylines that preserve // the original cyclic vertex order. auto polylines = SimplifyEdgesInCell(*a, cell, boundary_tolerance_uv, snap_radius); // The second pass eliminates any intersections between interior edges and // boundary edges, and then assembles the edges into a polygon. S2Builder::Options options( (IdentitySnapFunction(S2::kIntersectionError))); options.set_idempotent(false); // Force snapping up to the given radius S2Builder builder(options); builder.StartLayer(make_unique(this)); for (const auto& polyline : polylines) { builder.AddPolyline(*polyline); } S2Error error; if (!builder.Build(&error)) { S2_LOG(DFATAL) << "Could not build polygon: " << error; return; } // If there are no loops, check whether the result should be the full // polygon rather than the empty one. (See InitToApproxIntersection.) if (num_loops() == 0) { if (a->bound_.Area() > 2 * M_PI && a->GetArea() > 2 * M_PI) Invert(); } } // See comments in InitToSimplifiedInCell. vector> S2Polygon::SimplifyEdgesInCell( const S2Polygon& a, const S2Cell& cell, double tolerance_uv, S1Angle snap_radius) { S2Builder::Options options((IdentitySnapFunction(snap_radius))); options.set_simplify_edge_chains(true); S2Builder builder(options); // The output consists of a sequence of polylines. Polylines consisting of // interior edges are simplified using S2Builder, while polylines consisting // of boundary edges are returned unchanged. vector> polylines; for (int i = 0; i < a.num_loops(); ++i) { const S2Loop& a_loop = *a.loop(i); const S2Point* v0 = &a_loop.oriented_vertex(0); uint8 mask0 = GetCellEdgeIncidenceMask(cell, *v0, tolerance_uv); bool in_interior = false; // Was the last edge an interior edge? for (int j = 1; j <= a_loop.num_vertices(); ++j) { const S2Point* v1 = &a_loop.oriented_vertex(j); uint8 mask1 = GetCellEdgeIncidenceMask(cell, *v1, tolerance_uv); if ((mask0 & mask1) != 0) { // This is an edge along the cell boundary. Such edges do not get // simplified; we add them directly to the output. (We create a // separate polyline for each edge to keep things simple.) We call // ForceVertex on all boundary vertices to ensure that they don't // move, and so that nearby interior edges are snapped to them. S2_DCHECK(!in_interior); builder.ForceVertex(*v1); polylines.emplace_back(new S2Polyline(vector{*v0, *v1})); } else { // This is an interior edge. If this is the first edge of an interior // chain, then start a new S2Builder layer. Also ensure that any // polyline vertices on the boundary do not move, so that they will // still connect with any boundary edge(s) there. if (!in_interior) { S2Polyline* polyline = new S2Polyline; builder.StartLayer(make_unique(polyline)); polylines.emplace_back(polyline); in_interior = true; } builder.AddEdge(*v0, *v1); if (mask1 != 0) { builder.ForceVertex(*v1); in_interior = false; // Terminate this polyline. } } v0 = v1; mask0 = mask1; } } S2Error error; if (!builder.Build(&error)) { S2_LOG(DFATAL) << "InitToSimplifiedInCell failed: " << error; } return polylines; } vector> S2Polygon::OperationWithPolyline( S2BooleanOperation::OpType op_type, const S2Builder::SnapFunction& snap_function, const S2Polyline& a) const { S2BooleanOperation::Options options; options.set_snap_function(snap_function); vector> result; S2PolylineVectorLayer::Options layer_options; layer_options.set_polyline_type( S2PolylineVectorLayer::Options::PolylineType::WALK); S2BooleanOperation op( op_type, make_unique(&result, layer_options), options); MutableS2ShapeIndex a_index; a_index.Add(make_unique(&a)); S2Error error; if (!op.Build(a_index, index_, &error)) { S2_LOG(DFATAL) << "Polyline " << S2BooleanOperation::OpTypeToString(op_type) << " operation failed: " << error; } return result; } vector> S2Polygon::IntersectWithPolyline( const S2Polyline& a) const { return ApproxIntersectWithPolyline(a, S2::kIntersectionMergeRadius); } vector> S2Polygon::ApproxIntersectWithPolyline( const S2Polyline& a, S1Angle snap_radius) const { return IntersectWithPolyline(a, IdentitySnapFunction(snap_radius)); } vector> S2Polygon::IntersectWithPolyline( const S2Polyline& a, const S2Builder::SnapFunction& snap_function) const { return OperationWithPolyline(S2BooleanOperation::OpType::INTERSECTION, snap_function, a); } vector> S2Polygon::SubtractFromPolyline( const S2Polyline& a) const { return ApproxSubtractFromPolyline(a, S2::kIntersectionMergeRadius); } vector> S2Polygon::ApproxSubtractFromPolyline( const S2Polyline& a, S1Angle snap_radius) const { return SubtractFromPolyline(a, IdentitySnapFunction(snap_radius)); } vector> S2Polygon::SubtractFromPolyline( const S2Polyline& a, const S2Builder::SnapFunction& snap_function) const { return OperationWithPolyline(S2BooleanOperation::OpType::DIFFERENCE, snap_function, a); } bool S2Polygon::Contains(const S2Polyline& b) const { return ApproxContains(b, S2::kIntersectionMergeRadius); } bool S2Polygon::ApproxContains(const S2Polyline& b, S1Angle tolerance) const { auto difference = ApproxSubtractFromPolyline(b, tolerance); return difference.empty(); } bool S2Polygon::Intersects(const S2Polyline& b) const { return !ApproxDisjoint(b, S2::kIntersectionMergeRadius); } bool S2Polygon::ApproxDisjoint(const S2Polyline& b, S1Angle tolerance) const { auto intersection = ApproxIntersectWithPolyline(b, tolerance); return intersection.empty(); } unique_ptr S2Polygon::DestructiveUnion( vector> polygons) { return DestructiveApproxUnion(std::move(polygons), S2::kIntersectionMergeRadius); } unique_ptr S2Polygon::DestructiveApproxUnion( vector> polygons, S1Angle snap_radius) { // Effectively create a priority queue of polygons in order of number of // vertices. Repeatedly union the two smallest polygons and add the result // to the queue until we have a single polygon to return. using QueueType = std::multimap>; QueueType queue; // Map from # of vertices to polygon. for (auto& polygon : polygons) queue.insert(std::make_pair(polygon->num_vertices(), std::move(polygon))); while (queue.size() > 1) { // Pop two simplest polygons from queue. QueueType::iterator smallest_it = queue.begin(); int a_size = smallest_it->first; unique_ptr a_polygon(std::move(smallest_it->second)); queue.erase(smallest_it); smallest_it = queue.begin(); int b_size = smallest_it->first; unique_ptr b_polygon(std::move(smallest_it->second)); queue.erase(smallest_it); // Union and add result back to queue. auto union_polygon = make_unique(); union_polygon->InitToApproxUnion(a_polygon.get(), b_polygon.get(), snap_radius); queue.insert(std::make_pair(a_size + b_size, std::move(union_polygon))); // We assume that the number of vertices in the union polygon is the // sum of the number of vertices in the original polygons, which is not // always true, but will almost always be a decent approximation, and // faster than recomputing. } if (queue.empty()) return make_unique(); else return std::move(queue.begin()->second); } void S2Polygon::InitToCellUnionBorder(const S2CellUnion& cells) { // We use S2Builder to compute the union. Due to rounding errors, we can't // compute an exact union - when a small cell is adjacent to a larger cell, // the shared edges can fail to line up exactly. Two cell edges cannot come // closer then kMinWidth, so if we have S2Builder snap edges within half // that distance, then we should always merge shared edges without merging // different edges. double snap_radius = 0.5 * S2::kMinWidth.GetValue(S2CellId::kMaxLevel); S2Builder builder{S2Builder::Options( IdentitySnapFunction(S1Angle::Radians(snap_radius)))}; builder.StartLayer(make_unique(this)); for (S2CellId id : cells) { builder.AddLoop(S2Loop{S2Cell{id}}); } S2Error error; if (!builder.Build(&error)) { S2_LOG(DFATAL) << "InitToCellUnionBorder failed: " << error; } // If there are no loops, check whether the result should be the full // polygon rather than the empty one. There are only two ways that this can // happen: either the cell union is empty, or it consists of all six faces. if (num_loops() == 0) { if (cells.empty()) return; S2_DCHECK_EQ(uint64{6} << (2 * S2CellId::kMaxLevel), cells.LeafCellsCovered()); Invert(); } } bool S2Polygon::IsNormalized() const { // TODO(ericv): The condition tested here is insufficient. The correct // condition is that each *connected component* of child loops can share at // most one vertex with their parent loop. Example: suppose loop A has // children B, C, D, and the following pairs are connected: AB, BC, CD, DA. // Then the polygon is not normalized. set vertices; const S2Loop* last_parent = nullptr; for (int i = 0; i < num_loops(); ++i) { const S2Loop* child = loop(i); if (child->depth() == 0) continue; const S2Loop* parent = loop(GetParent(i)); if (parent != last_parent) { vertices.clear(); for (int j = 0; j < parent->num_vertices(); ++j) { vertices.insert(parent->vertex(j)); } last_parent = parent; } int count = 0; for (int j = 0; j < child->num_vertices(); ++j) { if (vertices.count(child->vertex(j)) > 0) ++count; } if (count > 1) return false; } return true; } bool S2Polygon::Equals(const S2Polygon* b) const { if (num_loops() != b->num_loops()) return false; for (int i = 0; i < num_loops(); ++i) { const S2Loop* a_loop = loop(i); const S2Loop* b_loop = b->loop(i); if ((b_loop->depth() != a_loop->depth()) || !b_loop->Equals(a_loop)) { return false; } } return true; } bool S2Polygon::BoundaryEquals(const S2Polygon* b) const { if (num_loops() != b->num_loops()) return false; for (int i = 0; i < num_loops(); ++i) { const S2Loop* a_loop = loop(i); bool success = false; for (int j = 0; j < num_loops(); ++j) { const S2Loop* b_loop = b->loop(j); if ((b_loop->depth() == a_loop->depth()) && b_loop->BoundaryEquals(a_loop)) { success = true; break; } } if (!success) return false; } return true; } bool S2Polygon::BoundaryApproxEquals(const S2Polygon& b, S1Angle max_error) const { if (num_loops() != b.num_loops()) return false; // For now, we assume that there is at most one candidate match for each // loop. (So far this method is just used for testing.) for (int i = 0; i < num_loops(); ++i) { const S2Loop& a_loop = *loop(i); bool success = false; for (int j = 0; j < num_loops(); ++j) { const S2Loop& b_loop = *b.loop(j); if (b_loop.depth() == a_loop.depth() && b_loop.BoundaryApproxEquals(a_loop, max_error)) { success = true; break; } } if (!success) return false; } return true; } bool S2Polygon::BoundaryNear(const S2Polygon& b, S1Angle max_error) const { if (num_loops() != b.num_loops()) return false; // For now, we assume that there is at most one candidate match for each // loop. (So far this method is just used for testing.) for (int i = 0; i < num_loops(); ++i) { const S2Loop& a_loop = *loop(i); bool success = false; for (int j = 0; j < num_loops(); ++j) { const S2Loop& b_loop = *b.loop(j); if (b_loop.depth() == a_loop.depth() && b_loop.BoundaryNear(a_loop, max_error)) { success = true; break; } } if (!success) return false; } return true; } void S2Polygon::EncodeCompressed(Encoder* encoder, const S2XYZFaceSiTi* all_vertices, int snap_level) const { S2_CHECK_GE(snap_level, 0); // Sufficient for what we write. Typically enough for a 4 vertex polygon. encoder->Ensure(40); encoder->put8(kCurrentCompressedEncodingVersionNumber); encoder->put8(snap_level); encoder->put_varint32(num_loops()); S2_DCHECK_GE(encoder->avail(), 0); const S2XYZFaceSiTi* current_loop_vertices = all_vertices; for (int i = 0; i < num_loops(); ++i) { loops_[i]->EncodeCompressed(encoder, current_loop_vertices, snap_level); current_loop_vertices += loops_[i]->num_vertices(); } // Do not write the bound or num_vertices as they can be cheaply recomputed // by DecodeCompressed. Microbenchmarks show the speed difference is // inconsequential. } bool S2Polygon::DecodeCompressed(Decoder* decoder) { if (decoder->avail() < sizeof(uint8)) return false; ClearLoops(); int snap_level = decoder->get8(); if (snap_level > S2CellId::kMaxLevel) return false; // Polygons with no loops are explicitly allowed here: a newly created // polygon has zero loops and such polygons encode and decode properly. uint32 num_loops; if (!decoder->get_varint32(&num_loops)) return false; if (num_loops > FLAGS_s2polygon_decode_max_num_loops) return false; loops_.reserve(num_loops); for (int i = 0; i < num_loops; ++i) { auto loop = make_unique(); loop->set_s2debug_override(s2debug_override()); if (!loop->DecodeCompressed(decoder, snap_level)) { return false; } loops_.push_back(std::move(loop)); } InitLoopProperties(); return true; } S2Polygon::Shape::Shape(const S2Polygon* polygon) : cumulative_edges_(nullptr) { Init(polygon); } void S2Polygon::Shape::Init(const S2Polygon* polygon) { polygon_ = polygon; delete[] cumulative_edges_; cumulative_edges_ = nullptr; num_edges_ = 0; if (!polygon->is_full()) { const int kMaxLinearSearchLoops = 12; // From benchmarks. int num_loops = polygon->num_loops(); if (num_loops > kMaxLinearSearchLoops) { cumulative_edges_ = new int[num_loops]; } for (int i = 0; i < num_loops; ++i) { if (cumulative_edges_) cumulative_edges_[i] = num_edges_; num_edges_ += polygon->loop(i)->num_vertices(); } } } S2Polygon::Shape::~Shape() { delete[] cumulative_edges_; } S2Shape::Edge S2Polygon::Shape::edge(int e) const { S2_DCHECK_LT(e, num_edges()); const S2Polygon* p = polygon(); int i; if (cumulative_edges_) { // "upper_bound" finds the loop just beyond the one we want. int* start = std::upper_bound(cumulative_edges_, cumulative_edges_ + p->num_loops(), e) - 1; i = start - cumulative_edges_; e -= *start; } else { // When the number of loops is small, linear search is faster. Most often // there is exactly one loop and the code below executes zero times. for (i = 0; e >= p->loop(i)->num_vertices(); ++i) { e -= p->loop(i)->num_vertices(); } } return Edge(p->loop(i)->oriented_vertex(e), p->loop(i)->oriented_vertex(e + 1)); } S2Shape::ReferencePoint S2Polygon::Shape::GetReferencePoint() const { const S2Polygon* p = polygon(); bool contains_origin = false; for (int i = 0; i < p->num_loops(); ++i) { contains_origin ^= p->loop(i)->contains_origin(); } return ReferencePoint(S2::Origin(), contains_origin); } int S2Polygon::Shape::num_chains() const { return polygon_->num_loops(); } S2Shape::Chain S2Polygon::Shape::chain(int i) const { S2_DCHECK_LT(i, Shape::num_chains()); if (cumulative_edges_) { return Chain(cumulative_edges_[i], polygon_->loop(i)->num_vertices()); } else { int e = 0; for (int j = 0; j < i; ++j) e += polygon_->loop(j)->num_vertices(); // S2Polygon represents a full loop as a loop with one vertex, while // S2Shape represents a full loop as a chain with no vertices. int num_vertices = polygon_->loop(i)->num_vertices(); return Chain(e, (num_vertices == 1) ? 0 : num_vertices); } } S2Shape::Edge S2Polygon::Shape::chain_edge(int i, int j) const { S2_DCHECK_LT(i, Shape::num_chains()); S2_DCHECK_LT(j, polygon_->loop(i)->num_vertices()); return Edge(polygon()->loop(i)->oriented_vertex(j), polygon()->loop(i)->oriented_vertex(j + 1)); } S2Shape::ChainPosition S2Polygon::Shape::chain_position(int e) const { // TODO(ericv): Make inline to remove code duplication with GetEdge. S2_DCHECK_LT(e, num_edges()); const S2Polygon* p = polygon(); int i; if (cumulative_edges_) { // "upper_bound" finds the loop just beyond the one we want. int* start = std::upper_bound(cumulative_edges_, cumulative_edges_ + p->num_loops(), e) - 1; i = start - cumulative_edges_; e -= *start; } else { // When the number of loops is small, linear search is faster. Most often // there is exactly one loop and the code below executes zero times. for (i = 0; e >= p->loop(i)->num_vertices(); ++i) { e -= p->loop(i)->num_vertices(); } } return ChainPosition(i, e); } size_t S2Polygon::SpaceUsed() const { size_t size = sizeof(*this); for (int i = 0; i < num_loops(); ++i) { size += loop(i)->SpaceUsed(); } size += index_.SpaceUsed() - sizeof(index_); return size; } s2/src/s2/s2cell_id.cc0000644000176200001440000005456414530411473014100 0ustar liggesusers// Copyright 2005 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2cell_id.h" #include #include #include #include #include #include #include #include "s2/base/integral_types.h" #include "s2/base/logging.h" #include "s2/r1interval.h" #include "s2/s2coords.h" #include "s2/s2latlng.h" #include "absl/base/casts.h" #include "absl/strings/str_cat.h" using absl::StrCat; using S2::internal::kSwapMask; using S2::internal::kInvertMask; using S2::internal::kPosToIJ; using S2::internal::kPosToOrientation; using std::fabs; using std::max; using std::min; using std::vector; // The following lookup tables are used to convert efficiently between an // (i,j) cell index and the corresponding position along the Hilbert curve. // "lookup_pos" maps 4 bits of "i", 4 bits of "j", and 2 bits representing the // orientation of the current cell into 8 bits representing the order in which // that subcell is visited by the Hilbert curve, plus 2 bits indicating the // new orientation of the Hilbert curve within that subcell. (Cell // orientations are represented as combination of kSwapMask and kInvertMask.) // // "lookup_ij" is an inverted table used for mapping in the opposite // direction. // // We also experimented with looking up 16 bits at a time (14 bits of position // plus 2 of orientation) but found that smaller lookup tables gave better // performance. (2KB fits easily in the primary cache.) // Values for these constants are *declared* in the *.h file. Even though // the declaration specifies a value for the constant, that declaration // is not a *definition* of storage for the value. Because the values are // supplied in the declaration, we don't need the values here. Failing to // define storage causes link errors for any code that tries to take the // address of one of these values. const int S2CellId::kFaceBits; const int S2CellId::kNumFaces; const int S2CellId::kMaxLevel; const int S2CellId::kPosBits; const int S2CellId::kMaxSize; static const int kLookupBits = 4; static uint16 lookup_pos[1 << (2 * kLookupBits + 2)]; static uint16 lookup_ij[1 << (2 * kLookupBits + 2)]; static void InitLookupCell(int level, int i, int j, int orig_orientation, int pos, int orientation) { if (level == kLookupBits) { int ij = (i << kLookupBits) + j; lookup_pos[(ij << 2) + orig_orientation] = (pos << 2) + orientation; lookup_ij[(pos << 2) + orig_orientation] = (ij << 2) + orientation; } else { level++; i <<= 1; j <<= 1; pos <<= 2; const int* r = kPosToIJ[orientation]; InitLookupCell(level, i + (r[0] >> 1), j + (r[0] & 1), orig_orientation, pos, orientation ^ kPosToOrientation[0]); InitLookupCell(level, i + (r[1] >> 1), j + (r[1] & 1), orig_orientation, pos + 1, orientation ^ kPosToOrientation[1]); InitLookupCell(level, i + (r[2] >> 1), j + (r[2] & 1), orig_orientation, pos + 2, orientation ^ kPosToOrientation[2]); InitLookupCell(level, i + (r[3] >> 1), j + (r[3] & 1), orig_orientation, pos + 3, orientation ^ kPosToOrientation[3]); } } static std::once_flag flag; inline static void MaybeInit() { std::call_once(flag, []{ InitLookupCell(0, 0, 0, 0, 0, 0); InitLookupCell(0, 0, 0, kSwapMask, 0, kSwapMask); InitLookupCell(0, 0, 0, kInvertMask, 0, kInvertMask); InitLookupCell(0, 0, 0, kSwapMask|kInvertMask, 0, kSwapMask|kInvertMask); }); } S2CellId S2CellId::advance(int64 steps) const { if (steps == 0) return *this; // We clamp the number of steps if necessary to ensure that we do not // advance past the End() or before the Begin() of this level. Note that // min_steps and max_steps always fit in a signed 64-bit integer. int step_shift = 2 * (kMaxLevel - level()) + 1; if (steps < 0) { int64 min_steps = -static_cast(id_ >> step_shift); if (steps < min_steps) steps = min_steps; } else { int64 max_steps = (kWrapOffset + lsb() - id_) >> step_shift; if (steps > max_steps) steps = max_steps; } // If steps is negative, then shifting it left has undefined behavior. // Cast to uint64 for a 2's complement answer. return S2CellId(id_ + (static_cast(steps) << step_shift)); } int64 S2CellId::distance_from_begin() const { const int step_shift = 2 * (kMaxLevel - level()) + 1; return id_ >> step_shift; } S2CellId S2CellId::advance_wrap(int64 steps) const { S2_DCHECK(is_valid()); if (steps == 0) return *this; int step_shift = 2 * (kMaxLevel - level()) + 1; if (steps < 0) { int64 min_steps = -static_cast(id_ >> step_shift); if (steps < min_steps) { int64 step_wrap = kWrapOffset >> step_shift; steps %= step_wrap; if (steps < min_steps) steps += step_wrap; } } else { // Unlike advance(), we don't want to return End(level). int64 max_steps = (kWrapOffset - id_) >> step_shift; if (steps > max_steps) { int64 step_wrap = kWrapOffset >> step_shift; steps %= step_wrap; if (steps > max_steps) steps -= step_wrap; } } return S2CellId(id_ + (static_cast(steps) << step_shift)); } S2CellId S2CellId::maximum_tile(const S2CellId limit) const { S2CellId id = *this; S2CellId start = id.range_min(); if (start >= limit.range_min()) return limit; if (id.range_max() >= limit) { // The cell is too large. Shrink it. Note that when generating coverings // of S2CellId ranges, this loop usually executes only once. Also because // id.range_min() < limit.range_min(), we will always exit the loop by the // time we reach a leaf cell. do { id = id.child(0); } while (id.range_max() >= limit); return id; } // The cell may be too small. Grow it if necessary. Note that generally // this loop only iterates once. while (!id.is_face()) { S2CellId parent = id.parent(); if (parent.range_min() != start || parent.range_max() >= limit) break; id = parent; } return id; } int S2CellId::GetCommonAncestorLevel(S2CellId other) const { // Basically we find the first bit position at which the two S2CellIds // differ and convert that to a level. The max() below is necessary for the // case where one S2CellId is a descendant of the other. uint64 bits = max(id() ^ other.id(), max(lsb(), other.lsb())); S2_DCHECK_NE(bits, 0); // Because lsb() is non-zero. // Compute the position of the most significant bit, and then map the bit // position as follows: // {0} -> 30, {1,2} -> 29, {3,4} -> 28, ... , {59,60} -> 0, {61,62,63} -> -1. return max(60 - Bits::FindMSBSetNonZero64(bits), -1) >> 1; } // Print the num_digits low order hex digits. static std::string HexFormatString(uint64 val, size_t num_digits) { std::string result(num_digits, ' '); for (; num_digits--; val >>= 4) result[num_digits] = "0123456789abcdef"[val & 0xF]; return result; } std::string S2CellId::ToToken() const { // Simple implementation: print the id in hex without trailing zeros. // Using hex has the advantage that the tokens are case-insensitive, all // characters are alphanumeric, no characters require any special escaping // in queries for most indexing systems, and it's easy to compare cell // tokens against the feature ids of the corresponding features. // // Using base 64 would produce slightly shorter tokens, but for typical cell // sizes used during indexing (up to level 15 or so) the average savings // would be less than 2 bytes per cell which doesn't seem worth it. // "0" with trailing 0s stripped is the empty string, which is not a // reasonable token. Encode as "X". if (id_ == 0) return "X"; const size_t num_zero_digits = Bits::FindLSBSetNonZero64(id_) / 4; return HexFormatString(id_ >> (4 * num_zero_digits), 16 - num_zero_digits); } S2CellId S2CellId::FromToken(const char* token, size_t length) { if (length > 16) return S2CellId::None(); uint64 id = 0; for (int i = 0, pos = 60; i < length; ++i, pos -= 4) { uint64 d; if ('0' <= token[i] && token[i] <= '9') { d = token[i] - '0'; } else if ('a' <= token[i] && token[i] <= 'f') { d = token[i] - 'a' + 10; } else if ('A' <= token[i] && token[i] <= 'F') { d = token[i] - 'A' + 10; } else { return S2CellId::None(); } id |= d << pos; } return S2CellId(id); } S2CellId S2CellId::FromToken(const std::string& token) { return FromToken(token.data(), token.size()); } void S2CellId::Encode(Encoder* const encoder) const { encoder->Ensure(sizeof(uint64)); // A single uint64. encoder->put64(id_); } bool S2CellId::Decode(Decoder* const decoder) { if (decoder->avail() < sizeof(uint64)) return false; id_ = decoder->get64(); return true; } S2CellId S2CellId::FromFaceIJ(int face, int i, int j) { // Initialization if not done yet MaybeInit(); // Optimization notes: // - Non-overlapping bit fields can be combined with either "+" or "|". // Generally "+" seems to produce better code, but not always. // Note that this value gets shifted one bit to the left at the end // of the function. uint64 n = absl::implicit_cast(face) << (kPosBits - 1); // Alternating faces have opposite Hilbert curve orientations; this // is necessary in order for all faces to have a right-handed // coordinate system. uint64 bits = (face & kSwapMask); // Each iteration maps 4 bits of "i" and "j" into 8 bits of the Hilbert // curve position. The lookup table transforms a 10-bit key of the form // "iiiijjjjoo" to a 10-bit value of the form "ppppppppoo", where the // letters [ijpo] denote bits of "i", "j", Hilbert curve position, and // Hilbert curve orientation respectively. #define GET_BITS(k) do { \ const int mask = (1 << kLookupBits) - 1; \ bits += ((i >> (k * kLookupBits)) & mask) << (kLookupBits + 2); \ bits += ((j >> (k * kLookupBits)) & mask) << 2; \ bits = lookup_pos[bits]; \ n |= (bits >> 2) << (k * 2 * kLookupBits); \ bits &= (kSwapMask | kInvertMask); \ } while (0) GET_BITS(7); GET_BITS(6); GET_BITS(5); GET_BITS(4); GET_BITS(3); GET_BITS(2); GET_BITS(1); GET_BITS(0); #undef GET_BITS return S2CellId(n * 2 + 1); } S2CellId::S2CellId(const S2Point& p) { double u, v; int face = S2::XYZtoFaceUV(p, &u, &v); int i = S2::STtoIJ(S2::UVtoST(u)); int j = S2::STtoIJ(S2::UVtoST(v)); id_ = FromFaceIJ(face, i, j).id(); } S2CellId::S2CellId(const S2LatLng& ll) : S2CellId(ll.ToPoint()) { } int S2CellId::ToFaceIJOrientation(int* pi, int* pj, int* orientation) const { // Initialization if not done yet MaybeInit(); int i = 0, j = 0; int face = this->face(); int bits = (face & kSwapMask); // Each iteration maps 8 bits of the Hilbert curve position into // 4 bits of "i" and "j". The lookup table transforms a key of the // form "ppppppppoo" to a value of the form "iiiijjjjoo", where the // letters [ijpo] represents bits of "i", "j", the Hilbert curve // position, and the Hilbert curve orientation respectively. // // On the first iteration we need to be careful to clear out the bits // representing the cube face. #define GET_BITS(k) do { \ const int nbits = (k == 7) ? (kMaxLevel - 7 * kLookupBits) : kLookupBits; \ bits += (static_cast(id_ >> (k * 2 * kLookupBits + 1)) \ & ((1 << (2 * nbits)) - 1)) << 2; \ bits = lookup_ij[bits]; \ i += (bits >> (kLookupBits + 2)) << (k * kLookupBits); \ j += ((bits >> 2) & ((1 << kLookupBits) - 1)) << (k * kLookupBits); \ bits &= (kSwapMask | kInvertMask); \ } while (0) GET_BITS(7); GET_BITS(6); GET_BITS(5); GET_BITS(4); GET_BITS(3); GET_BITS(2); GET_BITS(1); GET_BITS(0); #undef GET_BITS *pi = i; *pj = j; if (orientation != nullptr) { // The position of a non-leaf cell at level "n" consists of a prefix of // 2*n bits that identifies the cell, followed by a suffix of // 2*(kMaxLevel-n)+1 bits of the form 10*. If n==kMaxLevel, the suffix is // just "1" and has no effect. Otherwise, it consists of "10", followed // by (kMaxLevel-n-1) repetitions of "00", followed by "0". The "10" has // no effect, while each occurrence of "00" has the effect of reversing // the kSwapMask bit. S2_DCHECK_EQ(0, kPosToOrientation[2]); S2_DCHECK_EQ(kSwapMask, kPosToOrientation[0]); if (lsb() & 0x1111111111111110ULL) { bits ^= kSwapMask; } *orientation = bits; } return face; } S2Point S2CellId::ToPointRaw() const { int si, ti; int face = GetCenterSiTi(&si, &ti); return S2::FaceSiTitoXYZ(face, si, ti); } S2LatLng S2CellId::ToLatLng() const { return S2LatLng(ToPointRaw()); } R2Point S2CellId::GetCenterST() const { int si, ti; GetCenterSiTi(&si, &ti); return R2Point(S2::SiTitoST(si), S2::SiTitoST(ti)); } R2Point S2CellId::GetCenterUV() const { int si, ti; GetCenterSiTi(&si, &ti); return R2Point(S2::STtoUV(S2::SiTitoST(si)), S2::STtoUV(S2::SiTitoST(ti))); } R2Rect S2CellId::IJLevelToBoundUV(int ij[2], int level) { R2Rect bound; int cell_size = GetSizeIJ(level); for (int d = 0; d < 2; ++d) { int ij_lo = ij[d] & -cell_size; int ij_hi = ij_lo + cell_size; bound[d][0] = S2::STtoUV(S2::IJtoSTMin(ij_lo)); bound[d][1] = S2::STtoUV(S2::IJtoSTMin(ij_hi)); } return bound; } R2Rect S2CellId::GetBoundST() const { double size = GetSizeST(); return R2Rect::FromCenterSize(GetCenterST(), R2Point(size, size)); } R2Rect S2CellId::GetBoundUV() const { int ij[2]; ToFaceIJOrientation(&ij[0], &ij[1], nullptr); return IJLevelToBoundUV(ij, level()); } // This is a helper function for ExpandedByDistanceUV(). // // Given an edge of the form (u,v0)-(u,v1), let max_v = max(abs(v0), abs(v1)). // This method returns a new u-coordinate u' such that the distance from the // line u=u' to the given edge (u,v0)-(u,v1) is exactly the given distance // (which is specified as the sine of the angle corresponding to the distance). static double ExpandEndpoint(double u, double max_v, double sin_dist) { // This is based on solving a spherical right triangle, similar to the // calculation in S2Cap::GetRectBound. double sin_u_shift = sin_dist * sqrt((1 + u * u + max_v * max_v) / (1 + u * u)); double cos_u_shift = sqrt(1 - sin_u_shift * sin_u_shift); // The following is an expansion of tan(atan(u) + asin(sin_u_shift)). return (cos_u_shift * u + sin_u_shift) / (cos_u_shift - sin_u_shift * u); } /* static */ R2Rect S2CellId::ExpandedByDistanceUV(const R2Rect& uv, S1Angle distance) { // Expand each of the four sides of the rectangle just enough to include all // points within the given distance of that side. (The rectangle may be // expanded by a different amount in (u,v)-space on each side.) double u0 = uv[0][0], u1 = uv[0][1], v0 = uv[1][0], v1 = uv[1][1]; double max_u = std::max(fabs(u0), fabs(u1)); double max_v = std::max(fabs(v0), fabs(v1)); double sin_dist = sin(distance); return R2Rect(R1Interval(ExpandEndpoint(u0, max_v, -sin_dist), ExpandEndpoint(u1, max_v, sin_dist)), R1Interval(ExpandEndpoint(v0, max_u, -sin_dist), ExpandEndpoint(v1, max_u, sin_dist))); } S2CellId S2CellId::FromFaceIJWrap(int face, int i, int j) { // Convert i and j to the coordinates of a leaf cell just beyond the // boundary of this face. This prevents 32-bit overflow in the case // of finding the neighbors of a face cell. i = max(-1, min(kMaxSize, i)); j = max(-1, min(kMaxSize, j)); // We want to wrap these coordinates onto the appropriate adjacent face. // The easiest way to do this is to convert the (i,j) coordinates to (x,y,z) // (which yields a point outside the normal face boundary), and then call // S2::XYZtoFaceUV() to project back onto the correct face. // // The code below converts (i,j) to (si,ti), and then (si,ti) to (u,v) using // the linear projection (u=2*s-1 and v=2*t-1). (The code further below // converts back using the inverse projection, s=0.5*(u+1) and t=0.5*(v+1). // Any projection would work here, so we use the simplest.) We also clamp // the (u,v) coordinates so that the point is barely outside the // [-1,1]x[-1,1] face rectangle, since otherwise the reprojection step // (which divides by the new z coordinate) might change the other // coordinates enough so that we end up in the wrong leaf cell. static const double kScale = 1.0 / kMaxSize; static const double kLimit = 1.0 + DBL_EPSILON; // The arithmetic below is designed to avoid 32-bit integer overflows. S2_DCHECK_EQ(0, kMaxSize % 2); double u = max(-kLimit, min(kLimit, kScale * (2 * (i - kMaxSize / 2) + 1))); double v = max(-kLimit, min(kLimit, kScale * (2 * (j - kMaxSize / 2) + 1))); // Find the leaf cell coordinates on the adjacent face, and convert // them to a cell id at the appropriate level. face = S2::XYZtoFaceUV(S2::FaceUVtoXYZ(face, u, v), &u, &v); return FromFaceIJ(face, S2::STtoIJ(0.5*(u+1)), S2::STtoIJ(0.5*(v+1))); } inline S2CellId S2CellId::FromFaceIJSame(int face, int i, int j, bool same_face) { if (same_face) return S2CellId::FromFaceIJ(face, i, j); else return S2CellId::FromFaceIJWrap(face, i, j); } void S2CellId::GetEdgeNeighbors(S2CellId neighbors[4]) const { int i, j; int level = this->level(); int size = GetSizeIJ(level); int face = ToFaceIJOrientation(&i, &j, nullptr); // Edges 0, 1, 2, 3 are in the down, right, up, left directions. neighbors[0] = FromFaceIJSame(face, i, j - size, j - size >= 0) .parent(level); neighbors[1] = FromFaceIJSame(face, i + size, j, i + size < kMaxSize) .parent(level); neighbors[2] = FromFaceIJSame(face, i, j + size, j + size < kMaxSize) .parent(level); neighbors[3] = FromFaceIJSame(face, i - size, j, i - size >= 0) .parent(level); } void S2CellId::AppendVertexNeighbors(int level, vector* output) const { // "level" must be strictly less than this cell's level so that we can // determine which vertex this cell is closest to. S2_DCHECK_LT(level, this->level()); int i, j; int face = ToFaceIJOrientation(&i, &j, nullptr); // Determine the i- and j-offsets to the closest neighboring cell in each // direction. This involves looking at the next bit of "i" and "j" to // determine which quadrant of this->parent(level) this cell lies in. int halfsize = GetSizeIJ(level + 1); int size = halfsize << 1; bool isame, jsame; int ioffset, joffset; if (i & halfsize) { ioffset = size; isame = (i + size) < kMaxSize; } else { ioffset = -size; isame = (i - size) >= 0; } if (j & halfsize) { joffset = size; jsame = (j + size) < kMaxSize; } else { joffset = -size; jsame = (j - size) >= 0; } output->push_back(parent(level)); output->push_back(FromFaceIJSame(face, i + ioffset, j, isame).parent(level)); output->push_back(FromFaceIJSame(face, i, j + joffset, jsame).parent(level)); // If i- and j- edge neighbors are *both* on a different face, then this // vertex only has three neighbors (it is one of the 8 cube vertices). if (isame || jsame) { output->push_back(FromFaceIJSame(face, i + ioffset, j + joffset, isame && jsame).parent(level)); } } void S2CellId::AppendAllNeighbors(int nbr_level, vector* output) const { S2_DCHECK_GE(nbr_level, level()); int i, j; int face = ToFaceIJOrientation(&i, &j, nullptr); // Find the coordinates of the lower left-hand leaf cell. We need to // normalize (i,j) to a known position within the cell because nbr_level // may be larger than this cell's level. int size = GetSizeIJ(); i &= -size; j &= -size; int nbr_size = GetSizeIJ(nbr_level); S2_DCHECK_LE(nbr_size, size); // We compute the top-bottom, left-right, and diagonal neighbors in one // pass. The loop test is at the end of the loop to avoid 32-bit overflow. for (int k = -nbr_size; ; k += nbr_size) { bool same_face; if (k < 0) { same_face = (j + k >= 0); } else if (k >= size) { same_face = (j + k < kMaxSize); } else { same_face = true; // Top and bottom neighbors. output->push_back(FromFaceIJSame(face, i + k, j - nbr_size, j - size >= 0).parent(nbr_level)); output->push_back(FromFaceIJSame(face, i + k, j + size, j + size < kMaxSize).parent(nbr_level)); } // Left, right, and diagonal neighbors. output->push_back(FromFaceIJSame(face, i - nbr_size, j + k, same_face && i - size >= 0) .parent(nbr_level)); output->push_back(FromFaceIJSame(face, i + size, j + k, same_face && i + size < kMaxSize) .parent(nbr_level)); if (k >= size) break; } } std::string S2CellId::ToString() const { if (!is_valid()) { return StrCat("Invalid: ", absl::Hex(id(), absl::kZeroPad16)); } std::string out = StrCat(face(), "/"); for (int current_level = 1; current_level <= level(); ++current_level) { // Avoid dependencies of SimpleItoA, and slowness of StrAppend & // std::to_string. out += "0123"[child_position(current_level)]; } return out; } std::ostream& operator<<(std::ostream& os, S2CellId id) { return os << id.ToString(); } S2CellId S2CellId::FromDebugString(absl::string_view str) { // This function is reasonably efficient, but is only intended for use in // tests. int level = static_cast(str.size() - 2); if (level < 0 || level > S2CellId::kMaxLevel) return S2CellId::None(); int face = str[0] - '0'; if (face < 0 || face > 5 || str[1] != '/') return S2CellId::None(); S2CellId id = S2CellId::FromFace(face); for (int i = 2; i < str.size(); ++i) { int child_pos = str[i] - '0'; if (child_pos < 0 || child_pos > 3) return S2CellId::None(); id = id.child(child_pos); } return id; } s2/src/s2/s2builderutil_closed_set_normalizer.cc0000644000176200001440000003035614530411473021470 0ustar liggesusers// Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2builderutil_closed_set_normalizer.h" #include #include "absl/memory/memory.h" #include "s2/s2builder_layer.h" using absl::make_unique; using std::shared_ptr; using std::unique_ptr; using std::vector; using EdgeType = S2Builder::EdgeType; using Graph = S2Builder::Graph; using GraphOptions = S2Builder::GraphOptions; using DegenerateEdges = GraphOptions::DegenerateEdges; using SiblingPairs = GraphOptions::SiblingPairs; using Edge = Graph::Edge; using EdgeId = Graph::EdgeId; using VertexId = Graph::VertexId; namespace s2builderutil { ClosedSetNormalizer::ClosedSetNormalizer( const Options& options, const vector& graph_options_out) : options_(options), graph_options_out_(graph_options_out), graph_options_in_(graph_options_out_), sentinel_(std::numeric_limits::max(), std::numeric_limits::max()) { S2_DCHECK_EQ(graph_options_out_.size(), 3); S2_DCHECK(graph_options_out_[0].edge_type() == EdgeType::DIRECTED); S2_DCHECK(graph_options_out_[2].edge_type() == EdgeType::DIRECTED); // NOTE(ericv): Supporting these options would require some extra code in // order to handle undirected edges, and they are not useful for building // polylines anyway (they are intended for polygon meshes). S2_DCHECK(graph_options_out_[1].sibling_pairs() != SiblingPairs::CREATE); S2_DCHECK(graph_options_out_[1].sibling_pairs() != SiblingPairs::REQUIRE); // Set the GraphOptions for the input graphs to ensure that (1) they share a // common set of vertices, (2) degenerate edges are kept only if they are // isolated, and (3) multiple copies of siblings pairs are discarded. (Note // that there may be multiple copies of isolated degenerate edges; clients // can eliminate them if desired using DuplicateEdges::MERGE.) for (int dim = 0; dim < 3; ++dim) { graph_options_in_[dim].set_allow_vertex_filtering(false); } graph_options_in_[1].set_degenerate_edges(DegenerateEdges::DISCARD_EXCESS); graph_options_in_[2].set_degenerate_edges(DegenerateEdges::DISCARD_EXCESS); graph_options_in_[2].set_sibling_pairs(SiblingPairs::DISCARD_EXCESS); } const vector& ClosedSetNormalizer::Run( const vector& g, S2Error* error) { // Ensure that the input graphs were built with our requested options. for (int dim = 0; dim < 3; ++dim) { S2_DCHECK(g[dim].options() == graph_options_in_[dim]); } if (options_.suppress_lower_dimensions()) { // Build the auxiliary data needed to suppress lower-dimensional edges. in_edges2_ = g[2].GetInEdgeIds(); is_suppressed_.resize(g[0].vertices().size()); for (int dim = 1; dim <= 2; ++dim) { for (int e = 0; e < g[dim].num_edges(); ++e) { Edge edge = g[dim].edge(e); if (edge.first != edge.second) { is_suppressed_[edge.first] = true; is_suppressed_[edge.second] = true; } } } } // Compute the edges that belong in the output graphs. NormalizeEdges(g, error); // If any edges were added or removed, we need to run Graph::ProcessEdges to // ensure that the edges satisfy the requested GraphOptions. Note that // since edges are never added to dimension 2, we can use the edge count to // test whether any edges were removed. If no edges were removed from // dimension 2, then no edges were added to dimension 1, and so we can again // use the edge count to test whether any edges were removed, etc. bool modified[3]; bool any_modified = false; for (int dim = 2; dim >= 0; --dim) { if (new_edges_[dim].size() != g[dim].num_edges()) any_modified = true; modified[dim] = any_modified; } if (!any_modified) { for (int dim = 0; dim < 3; ++dim) { // Copy the graphs to ensure that they have the GraphOptions that were // originally requested. new_graphs_.push_back(Graph( graph_options_out_[dim], &g[dim].vertices(), &g[dim].edges(), &g[dim].input_edge_id_set_ids(), &g[dim].input_edge_id_set_lexicon(), &g[dim].label_set_ids(), &g[dim].label_set_lexicon(), g[dim].is_full_polygon_predicate())); } } else { // Make a copy of input_edge_id_set_lexicon() so that ProcessEdges can // merge edges if necessary. new_input_edge_id_set_lexicon_ = g[0].input_edge_id_set_lexicon(); for (int dim = 0; dim < 3; ++dim) { if (modified[dim]) { Graph::ProcessEdges(&graph_options_out_[dim], &new_edges_[dim], &new_input_edge_ids_[dim], &new_input_edge_id_set_lexicon_, error); } new_graphs_.push_back(Graph( graph_options_out_[dim], &g[dim].vertices(), &new_edges_[dim], &new_input_edge_ids_[dim], &new_input_edge_id_set_lexicon_, &g[dim].label_set_ids(), &g[dim].label_set_lexicon(), g[dim].is_full_polygon_predicate())); } } return new_graphs_; } // Helper function that advances to the next edge in the given graph, // returning a sentinel value once all edges are exhausted. inline Edge ClosedSetNormalizer::Advance(const Graph& g, EdgeId* e) const { return (++*e == g.num_edges()) ? sentinel_ : g.edge(*e); } // Helper function that advances to the next incoming edge in the given graph, // returning a sentinel value once all edges are exhausted. inline Edge ClosedSetNormalizer::AdvanceIncoming( const Graph& g, const vector& in_edges, int* i) const { return ((++*i == in_edges.size()) ? sentinel_ : Graph::reverse(g.edge(in_edges[*i]))); } void ClosedSetNormalizer::NormalizeEdges(const vector& g, S2Error* error) { // Find the degenerate polygon edges and sibling pairs, and classify each // edge as belonging to either a shell or a hole. auto degeneracies = FindPolygonDegeneracies(g[2], error); auto degeneracy = degeneracies.begin(); // Walk through the three edge vectors performing a merge join. We also // maintain positions in two other auxiliary vectors: the vector of sorted // polygon degeneracies (degeneracies), and the vector of incoming polygon // edges (if we are suppressing lower-dimensional duplicate edges). EdgeId e0 = -1, e1 = -1, e2 = -1; // Current position in g[dim].edges() int in_e2 = -1; // Current position in in_edges2_ Edge edge0 = Advance(g[0], &e0); Edge edge1 = Advance(g[1], &e1); Edge edge2 = Advance(g[2], &e2); Edge in_edge2 = AdvanceIncoming(g[2], in_edges2_, &in_e2); for (;;) { if (edge2 <= edge1 && edge2 <= edge0) { if (edge2 == sentinel_) break; if (degeneracy == degeneracies.end() || degeneracy->edge_id != e2) { // Normal polygon edge (not part of a degeneracy). AddEdge(2, g[2], e2); while (options_.suppress_lower_dimensions() && edge1 == edge2) { edge1 = Advance(g[1], &e1); } } else if (!(degeneracy++)->is_hole) { // Edge belongs to a degenerate shell. if (edge2.first != edge2.second) { AddEdge(1, g[2], e2); // Since this edge was demoted, make sure that it does not suppress // any coincident polyline edge(s). while (edge1 == edge2) { AddEdge(1, g[1], e1); edge1 = Advance(g[1], &e1); } } else { // The test below is necessary because a single-vertex polygon shell // can be discarded by a polyline edge incident to that vertex. if (!is_suppressed(edge2.first)) AddEdge(0, g[2], e2); } } edge2 = Advance(g[2], &e2); } else if (edge1 <= edge0) { if (edge1.first != edge1.second) { // Non-degenerate polyline edge. (Note that in_edges2_ is empty // whenever "suppress_lower_dimensions" is false.) while (in_edge2 < edge1) { in_edge2 = AdvanceIncoming(g[2], in_edges2_, &in_e2); } if (edge1 != in_edge2) AddEdge(1, g[1], e1); } else { // Degenerate polyline edge. if (!is_suppressed(edge1.first)) AddEdge(0, g[1], e1); if (g[1].options().edge_type() == EdgeType::UNDIRECTED) ++e1; } edge1 = Advance(g[1], &e1); } else { // Input point. if (!is_suppressed(edge0.first)) AddEdge(0, g[0], e0); edge0 = Advance(g[0], &e0); } } } inline void ClosedSetNormalizer::AddEdge(int new_dim, const Graph& g, EdgeId e) { new_edges_[new_dim].push_back(g.edge(e)); new_input_edge_ids_[new_dim].push_back(g.input_edge_id_set_id(e)); } inline bool ClosedSetNormalizer::is_suppressed(VertexId v) const { return options_.suppress_lower_dimensions() && is_suppressed_[v]; } // This method implements the NormalizeClosedSet function. The Create() // method allocates a single object of this class whose ownership is shared // (using shared_ptr) among the three returned S2Builder::Layers. Here is how // the process works: // // - The returned layers are passed to a class (such as S2Builder or // S2BooleanOperation) that calls their Build methods. We call these the // "input layers" because they provide the input to ClosedSetNormalizer. // // - When Build() is called on the first two layers, pointers to the // corresponding Graph arguments are saved. // // - When Build() is called on the third layer, ClosedSetNormalizer is used // to normalize the graphs, and then the Build() method of each of the // three output layers is called. // // TODO(ericv): Consider generalizing this technique as a public class. class NormalizeClosedSetImpl { public: static LayerVector Create(LayerVector output_layers, const ClosedSetNormalizer::Options& options) { using Impl = NormalizeClosedSetImpl; shared_ptr impl(new Impl(std::move(output_layers), options)); LayerVector result; for (int dim = 0; dim < 3; ++dim) { result.push_back(make_unique( dim, impl->normalizer_.graph_options()[dim], impl)); } return result; } private: NormalizeClosedSetImpl(LayerVector output_layers, const ClosedSetNormalizer::Options& options) : output_layers_(std::move(output_layers)), normalizer_(options, vector{ output_layers_[0]->graph_options(), output_layers_[1]->graph_options(), output_layers_[2]->graph_options()}), graphs_(3), graphs_left_(3) { S2_DCHECK_EQ(3, output_layers_.size()); } class DimensionLayer : public S2Builder::Layer { public: DimensionLayer(int dimension, const GraphOptions& graph_options, shared_ptr impl) : dimension_(dimension), graph_options_(graph_options), impl_(std::move(impl)) {} GraphOptions graph_options() const override { return graph_options_; } void Build(const Graph& g, S2Error* error) override { impl_->Build(dimension_, g, error); } private: int dimension_; GraphOptions graph_options_; shared_ptr impl_; }; void Build(int dimension, const Graph& g, S2Error* error) { // Errors are reported only on the last layer built. graphs_[dimension] = g; if (--graphs_left_ > 0) return; vector output = normalizer_.Run(graphs_, error); for (int dim = 0; dim < 3; ++dim) { output_layers_[dim]->Build(output[dim], error); } } private: vector> output_layers_; ClosedSetNormalizer normalizer_; vector graphs_; int graphs_left_; }; LayerVector NormalizeClosedSet(LayerVector output_layers, const ClosedSetNormalizer::Options& options) { return NormalizeClosedSetImpl::Create(std::move(output_layers), options); } } // namespace s2builderutil s2/src/s2/s2cell_index.cc0000644000176200001440000001333514530411473014602 0ustar liggesusers// Copyright 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: ericv@google.com (Eric Veach) #include "s2/s2cell_index.h" using std::vector; using Label = S2CellIndex::Label; void S2CellIndex::RangeIterator::Seek(S2CellId target) { S2_DCHECK(target.is_leaf()); it_ = std::upper_bound(range_nodes_->begin(), range_nodes_->end(), target) - 1; } void S2CellIndex::ContentsIterator::StartUnion(const RangeIterator& range) { if (range.start_id() < prev_start_id_) { node_cutoff_ = -1; // Can't automatically eliminate duplicates. } prev_start_id_ = range.start_id(); // TODO(ericv): Since RangeNode only uses 12 of its 16 bytes, we could add a // "label" field without using any extra space. Then we could store a leaf // node of cell_tree_ directly in each RangeNode, where the cell_id is // implicitly defined as the one that covers the current leaf cell range. // This would save quite a bit of space; e.g. if the given cells are // non-overlapping, then cell_tree_ would be empty (since every node is a // leaf node and could therefore be stored directly in a RangeNode). It // would also be faster because cell_tree_ would rarely be accessed. int contents = range.it_->contents; if (contents <= node_cutoff_) { set_done(); } else { node_ = (*cell_tree_)[contents]; } // When visiting ancestors, we can stop as soon as the node index is smaller // than any previously visited node index. Because indexes are assigned // using a preorder traversal, such nodes are guaranteed to have already // been reported. next_node_cutoff_ = contents; } S2CellIndex::S2CellIndex() { } void S2CellIndex::Add(const S2CellUnion& cell_ids, Label label) { for (S2CellId cell_id : cell_ids) { Add(cell_id, label); } } void S2CellIndex::Build() { // To build the cell tree and leaf cell ranges, we maintain a stack of // (cell_id, label) pairs that contain the current leaf cell. This class // represents an instruction to push or pop a (cell_id, label) pair. // // If label >= 0, the (cell_id, label) pair is pushed on the stack. // If cell_id == S2CellId::Sentinel(), a pair is popped from the stack. // Otherwise the stack is unchanged but a RangeNode is still emitted. struct Delta { S2CellId start_id, cell_id; Label label; Delta(S2CellId _start_id, S2CellId _cell_id, Label _label) : start_id(_start_id), cell_id(_cell_id), label(_label) {} // Deltas are sorted first by start_id, then in reverse order by cell_id, // and then by label. This is necessary to ensure that (1) larger cells // are pushed on the stack before smaller cells, and (2) cells are popped // off the stack before any new cells are added. bool operator<(const Delta& y) const { if (start_id < y.start_id) return true; if (y.start_id < start_id) return false; if (y.cell_id < cell_id) return true; if (cell_id < y.cell_id) return false; return label < y.label; } }; vector deltas; deltas.reserve(2 * cell_tree_.size() + 2); // Create two deltas for each (cell_id, label) pair: one to add the pair to // the stack (at the start of its leaf cell range), and one to remove it from // the stack (at the end of its leaf cell range). for (const CellNode& node : cell_tree_) { deltas.push_back(Delta(node.cell_id.range_min(), node.cell_id, node.label)); deltas.push_back(Delta(node.cell_id.range_max().next(), S2CellId::Sentinel(), -1)); } // We also create two special deltas to ensure that a RangeNode is emitted at // the beginning and end of the S2CellId range. deltas.push_back( Delta(S2CellId::Begin(S2CellId::kMaxLevel), S2CellId::None(), -1)); deltas.push_back( Delta(S2CellId::End(S2CellId::kMaxLevel), S2CellId::None(), -1)); std::sort(deltas.begin(), deltas.end()); // Now walk through the deltas to build the leaf cell ranges and cell tree // (which is essentially a permanent form of the "stack" described above). cell_tree_.clear(); range_nodes_.reserve(deltas.size()); int contents = -1; for (int i = 0; i < deltas.size(); ) { S2CellId start_id = deltas[i].start_id; // Process all the deltas associated with the current start_id. for (; i < deltas.size() && deltas[i].start_id == start_id; ++i) { if (deltas[i].label >= 0) { cell_tree_.push_back({deltas[i].cell_id, deltas[i].label, contents}); contents = cell_tree_.size() - 1; } else if (deltas[i].cell_id == S2CellId::Sentinel()) { contents = cell_tree_[contents].parent; } } range_nodes_.push_back({start_id, contents}); } } vector