Fast contiguity checks for polygons in SF/R - r

For my dissertation, I'm running an algorithm that creates random congressional districts by flipping precincts from one district to an adjacent district thousands of times. One important part in the analysis is that the district that gives the precinct cannot be rendered non-contiguous (i.e., it can't give up a precinct that connects one portion of a district to a different part). To check this, I utilize the following script:
library(sf)
library(tidyverse)
subtracted_district <- main_df %>% #main_df has all of the precincts
filter(district %in% giver) %>% #this selects only precincts in "giver" district
filter(!index %in% proposed) %>% #this removes the proposed precinct to be flipped to the "taker" district
summarise()
foo <- st_cast(subtracted_district, "POLYGON")
d <- dim(foo)[1]
ifelse(d == 1, accepted <- 1, adjoining_giver <- adjoining_giver[-a]) #if contiguous, it's accepted, otherwise, we start over without this precinct available.
In short, this pulls the precincts from the shapefiles that are assigned to the "giver" district, filters out the "proposed" donor precinct, reassembles the "giver" district without the proposed donor precinct, then checks to see if the reassembled district would be comprised of multiple polygons.
This works, but the problem is that even on my very fast desktop, recreating the district is prohibitively slow, especially if it has thousands of precincts in it. It ends up taking about an hour to run through.
I'm wondering if there is a way to do this without recreating the district. If you know the adjacency matrix for a group of polygons, is there a fast, reliable way to check to see if the group of polygons is contiguous?
Edit to address a comment: To be clear, the slow part comes with the sf/tidy command "summarise," which takes all of the precinct polygons and merges them into a district. This takes a huge amount of time, which is why I'm wondering if I can do it without having to actually create the district just by looking at the adjacency matrix.
Below is a sample main_df file, in sf format. The way it works now, a vector of indexes for the precincts in the "giver" district that are adjacent to the "taker" precinct are selected (not shown, but done through the adjacency matrix), and then a random precinct is selected from that vector as a proposed donation ("proposed").
If you were to create this shapefile and run the full code, 9, 11, 12 and 15 would be listed as possible precincts to flip from 17 to 13. It would then randomly select from that list a precinct to flip.
Let's say it proposes precinct 9 to donate. To test whether it was an acceptable proposal, the script takes main_df, filters out the precincts currently in district 17, then filters out precinct 9.
This is where things slow down: It then will merge together all of the precincts into a single district. It will be fast with this data, but very slow if you have, say, 2000 precincts in a district to merge.
It then uses st_cast to see if we are left with a multipolygon. If the resulting dataframe has more than 1 row, it does, the proposal is rejected, and it selects from the remaining data. Here it would break contiguity, so the proposal would be rejected and it would choose from 12, 11 and 15. If it chose 12, the proposal would fail, and it would select from 11 and 15. Whichever it chose, the proposal would be accepted, because it would not break contiguity.
Since creating the district slows things down so much, I'm wondering there's a fast way to do it via the adjacency matrix.
Sample adjacency matrix:
structure(list(12L, c(10L, 11L, 13L, 15L), c(8L, 10L, 12L, 15L
), c(5L, 13L), c(4L, 11L, 14L), c(9L, 11L, 14L), 14L, c(3L, 12L
), c(6L, 10L, 11L, 12L), c(2L, 3L, 9L, 11L, 12L, 15L), c(2L,
5L, 6L, 9L, 10L, 13L), c(1L, 3L, 8L, 9L, 10L), c(2L, 4L, 11L,
15L), 5:7, c(2L, 3L, 10L, 13L)), predicate = "relate_pattern", region.id = c("1",
"2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13",
"14", "15"), remove_self = FALSE, retain_unique = FALSE, ncol = 15L, class = c("sgbp",
"list"))
Sample shapefile:
structure(list(index = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L,
10L, 11L, 12L, 15L, 18L, 19L), Total_pop = c(3951.064118, 37401.9916269,
1989.623112, 2679.000014, 2934.782304, 2193.129252, 3967.176508,
2916.376886, 1237.112553, 14039.899499, 9486.059285, 2805.5047867,
3240.0551608, 2230.911935, 11792.6263111), district = c(17, 13,
13, 17, 17, 17, 17, 13, 17, 13, 17, 17, 17, 17, 13), geometry = structure(list(
structure(list(structure(c(-84.320765, -84.314011, -84.309424,
-84.329215, -84.333127, -84.340251, -84.340531, -84.333975,
-84.329623, -84.323183, -84.320765, 40.859333, 40.859414,
40.847182, 40.841154, 40.829147, 40.828847, 40.859099, 40.859198,
40.859295, 40.859405, 40.859333), .Dim = c(11L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-84.108857,
-84.093944, -84.096841, -84.094248, -84.079782, -84.075252,
-84.070914, -84.094946, -84.090253, -84.075439, -84.075766,
-84.089857, -84.089814, -84.100991, -84.108197, -84.108219,
-84.108304, -84.113273, -84.123183, -84.143812, -84.152296,
-84.16154, -84.146775, -84.160499, -84.122984, -84.127923,
-84.121899, -84.10871, -84.108857, 40.795642, 40.788453,
40.77236, 40.752697, 40.745239, 40.730615, 40.717104, 40.716549,
40.709565, 40.709664, 40.702659, 40.699769, 40.688383, 40.687675,
40.695077, 40.698505, 40.709503, 40.724139, 40.7303, 40.73043,
40.726295, 40.730179, 40.737101, 40.754921, 40.751592, 40.764579,
40.769752, 40.770137, 40.795642), .Dim = c(29L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-84.340004,
-84.223661, -84.223143, -84.222799, -84.318213, -84.318511,
-84.339536, -84.340004, 40.772111, 40.773446, 40.729461,
40.685957, 40.685658, 40.714662, 40.714542, 40.772111), .Dim = c(8L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-83.994079, -83.994156, -83.879834, -83.880194,
-83.994079, 40.644132, 40.731146, 40.732443, 40.64469,
40.644132), .Dim = c(5L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-83.879834, -83.994156,
-83.994343, -83.880063, -83.879834, 40.732443, 40.731146,
40.81805, 40.819919, 40.732443), .Dim = c(5L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-83.995165,
-83.994343, -84.109248, -84.109586, -84.109516, -83.995165,
40.905066, 40.81805, 40.817277, 40.860994, 40.90473, 40.905066
), .Dim = c(6L, 2L))), class = c("XY", "POLYGON", "sfg")),
structure(list(structure(c(-83.880301, -83.890429, -83.904255,
-83.904832, -83.917586, -83.900224, -83.880402, -83.880393,
-83.880383, -83.880301, 40.881537, 40.877166, 40.879403,
40.892264, 40.898383, 40.905847, 40.905907, 40.901276, 40.898756,
40.881537), .Dim = c(10L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-84.340004, -84.339536,
-84.318511, -84.318213, -84.396778, -84.397189, -84.340016,
-84.340004, 40.772111, 40.714542, 40.714662, 40.685658, 40.684926,
40.786584, 40.786948, 40.772111), .Dim = c(8L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-84.22485,
-84.130459, -84.109586, -84.109248, -84.108904, -84.148307,
-84.22407, -84.22485, 40.859307, 40.860182, 40.860994, 40.817277,
40.802748, 40.801737, 40.801247, 40.859307), .Dim = c(8L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-84.223143, -84.223661, -84.22407, -84.148307,
-84.108904, -84.108857, -84.10871, -84.121899, -84.127923,
-84.122984, -84.160499, -84.146775, -84.16154, -84.223143,
40.729461, 40.773446, 40.801247, 40.801737, 40.802748,
40.795642, 40.770137, 40.769752, 40.764579, 40.751592,
40.754921, 40.737101, 40.730179, 40.729461), .Dim = c(14L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-84.108857, -84.108904, -84.109248, -83.994343,
-83.994156, -84.075252, -84.079782, -84.094248, -84.096841,
-84.093944, -84.108857, 40.795642, 40.802748, 40.817277,
40.81805, 40.731146, 40.730615, 40.745239, 40.752697,
40.77236, 40.788453, 40.795642), .Dim = c(11L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-84.340004,
-84.340016, -84.397189, -84.397374, -84.340101, -84.340251,
-84.333127, -84.329215, -84.309424, -84.314011, -84.22485,
-84.22407, -84.223661, -84.340004, 40.772111, 40.786948,
40.786584, 40.815941, 40.816143, 40.828847, 40.829147, 40.841154,
40.847182, 40.859414, 40.859307, 40.801247, 40.773446, 40.772111
), .Dim = c(14L, 2L))), class = c("XY", "POLYGON", "sfg")),
structure(list(structure(c(-83.994079, -84.107787, -84.107908,
-84.108197, -84.100991, -84.089814, -84.089857, -84.075766,
-84.075439, -84.090253, -84.094946, -84.070914, -84.075252,
-83.994156, -83.994079, 40.644132, 40.643069, 40.657938,
40.695077, 40.687675, 40.688383, 40.699769, 40.702659, 40.709664,
40.709565, 40.716549, 40.717104, 40.730615, 40.731146, 40.644132
), .Dim = c(15L, 2L))), class = c("XY", "POLYGON", "sfg")),
structure(list(structure(c(-83.880402, -83.900224, -83.917586,
-83.904832, -83.904255, -83.890429, -83.880301, -83.880063,
-83.994343, -83.995165, -83.995228, -83.880423, -83.880402,
40.905907, 40.905847, 40.898383, 40.892264, 40.879403, 40.877166,
40.881537, 40.819919, 40.81805, 40.905066, 40.919843, 40.920429,
40.905907), .Dim = c(13L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-84.222799, -84.223143,
-84.16154, -84.152296, -84.143812, -84.123183, -84.113273,
-84.108304, -84.108219, -84.108197, -84.107908, -84.222749,
-84.222799, 40.685957, 40.729461, 40.730179, 40.726295, 40.73043,
40.7303, 40.724139, 40.709503, 40.698505, 40.695077, 40.657938,
40.656948, 40.685957), .Dim = c(13L, 2L))), class = c("XY",
"POLYGON", "sfg"))), n_empty = 0L, crs = structure(list(input = "NAD83",
wkt = "GEOGCRS[\"NAD83\",\n DATUM[\"North American Datum 1983\",\n ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n LENGTHUNIT[\"metre\",1]]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n CS[ellipsoidal,2],\n AXIS[\"latitude\",north,\n ORDER[1],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n AXIS[\"longitude\",east,\n ORDER[2],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n ID[\"EPSG\",4269]]"), class = "crs"), class = c("sfc_POLYGON",
"sfc"), precision = 0, bbox = structure(c(xmin = -84.397374,
ymin = 40.643069, xmax = -83.879834, ymax = 40.920429), class = "bbox"))), row.names = c(NA,
15L), sf_column = "geometry", agr = structure(c(index = NA_integer_,
Total_pop = NA_integer_, district = NA_integer_), .Label = c("constant",
"aggregate", "identity"), class = "factor"), class = c("sf",
"data.frame"))

This is an interesting problem and I believe it can be solved by via adjacency matrix.
I will illustrate a possible approach on the well known and much loved NC shapefile that ships with {sf}.
First I construct a subset of North Carolina that makes a contiguous polygon:
library(sf)
library(dplyr)
library(sfdep)
# the one & only NC shapefile...
nc <- st_read(system.file("shape/nc.shp", package="sf")) %>%
# round up the usual suspects...
filter(!NAME %in% c('Dare', 'Currituck', 'Carteret', 'Hyde'))
plot(st_geometry(nc))
Then I construct a adjacency matrix; I personally prefer sfdep::st_contiguity() but other approaches are possible.
The next step is iterating over the matrix, looking for matches between second degree neighbors (neighbors of a given neighbor) and first degree neighbors (neighbors of district / county being evaluated). Should you find a case of no common neighbor - you have your troublemaker!
You will have to first eliminate counties/districts that have only one neighbor. These can not cause discontinuity and are safely ignored (consider county Tyrell on what is left of the Albemarle-Pamlico Peninsula).
Once you have iterated over the entire adjacency matrix you are done.
neighbours <- sfdep::st_contiguity(nc)
troublemakers <- rep(FALSE, times = length(neighbours)) # init of an resultset
for (i in seq_along(neighbours)) {
first_degree <- neighbours[[i]]
# edge case of single neighbor districts needs to be handled;
# it can not create discontinuity and is safe to be ignored
if (length(first_degree) > 1) {
for (j in seq_along(first_degree)) {
# j-th second degree neighbors vs first degree neighbors
wrk_diff <- intersect(unlist(neighbours[first_degree[j]]),
first_degree)
# if no common neighbor >> discontinuity!
if (length(wrk_diff) == 0) troublemakers[i] <- T
}
}
}
And because a picture is worth 1000 of words - let us check the troublemakers on a plot (first all counties, and then overlay with a subset of troublemakers in red)
plot(st_geometry(nc))
plot(st_geometry(nc[troublemakers, ]), col = "red", add = T)

Related

R error: Must request at least one colour from a hue palette

I am trying to create demographic distribution maps, and I keep getting this error: Must request at least one colour from a hue palette. This issue has been raised many times on SO, and the common theme seems to be that there are NAs in whatever is being supplied to the fill/color argument (e.g., see this question, this one, and this one). I have removed NAs and still get the error:
> sum(is.na(test_data$analysis_group))
[1] 0
> sum(is.na(test_data$census_block_id))
[1] 0
> sum(is.na(test_data$race))
[1] 0
> sum(is.na(test_data$dem_count))
[1] 0
> sum(is.na(test_data$dem_perc))
[1] 0
> sum(is.na(test_data$dist_miles))
[1] 0
> sum(is.na(test_data$geog))
[1] 0
I also tried setting the na.value parameter as suggested here, but I still get the error.
Here is a glimpse of the data:
> test_data %>% glimpse()
Rows: 529,980
Columns: 7
$ analysis_group <fct> DMV and Hospital, DMV and Hospital, DMV and Hospital, DMV and Hospit…
$ census_block_id <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,…
$ race <fct> hisp_lat_white, total_non_white, v3_American_Indian_and_Alaska_Nativ…
$ dem_count <int> 6, 57, 2, 0, 47, 0, 2, 0, 57, 0, 12, 62, 0, 4, 32, 0, 9, 5, 72, 10, …
$ dem_perc <dbl> 0.1, 1.0, 0.0, 0.0, 0.8, 0.0, 0.0, 0.0, 1.0, 0.0, 0.2, 0.9, 0.0,
0.1…
$ dist_miles <dbl> 0.2262349, 0.2262349, 0.2262349, 0.2262349, 0.2262349, 0.2262349, 0.…
$ geog <POLYGON [°]> POLYGON ((-75.14961 40.0082..., POLYGON ((-75.14961 40.0082.…
The above data (test_data) are being fed into this ggplot call:
test_data %>%
ggplot() +
geom_sf(aes(fill = dem_perc, color = NA)
Interestingly, another dataset that is formatted the same, but with different variables, maps just fine:
Rows: 52,998
Columns: 15
$ comparison <fct> DMV to both, DMV to both, DMV to both, DMV to both, D…
$ census_block_id <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16…
$ dist_change <dbl> -3.69399923, -4.03896796, -2.71867327, -0.39305133, -…
$ dist_initial <dbl> 3.920234, 6.234878, 3.582916, 1.474123, 2.520320, 1.8…
$ dist_final <dbl> 0.2262349, 2.1959098, 0.8642427, 1.0810720, 2.0617893…
$ dist_percchange <dbl> -94.229046, -64.780227, -75.878790, -26.663395, -18.1…
$ race <chr> "v3_TOTAL", "v3_TOTAL", "v3_TOTAL", "v3_TOTAL", "v3_T…
$ dem_count <int> 57, 72, 55, 144, 78, 0, 117, 23, 5, 35, 24, 44, 44, 6…
$ dmv_only_closest_regloc_id <int> 1, 3, 1, 1, 1, 1, 3, 3, 1, 4, 4, 3, 1, 3, 1, 3, 1, 1,…
$ dmv_only_dist_miles <dbl> 3.920234, 6.234878, 3.582916, 1.474123, 2.520320, 1.8…
$ hosp_only_closest_regloc_id <int> 16, 8, 16, 17, 10, 17, 16, 16, 10, 12, 7, 11, 10, 5, …
$ hosp_only_dist_miles <dbl> 0.2262349, 2.1959098, 0.8642427, 1.0810720, 2.0617893…
$ dmv_and_hosp_closest_regloc_id <int> 16, 8, 16, 17, 10, 1, 16, 16, 10, 12, 4, 11, 10, 5, 1…
$ dmv_and_hosp_dist_miles <dbl> 0.2262349, 2.1959098, 0.8642427, 1.0810720, 2.0617893…
$ geog <POLYGON [°]> POLYGON ((-75.14961 40.0082..., POLYGON ((-75…
Working data:
working_data %>%
ggplot() +
geom_sf(aes(fill = dist_change, color = NA)
Any thoughts on what could be going on?
Edit: Here is a sample of my data to use to test
> dput(test_data)
structure(list(analysis_group = structure(c(1L, 1L, 1L, 1L, 1L,
1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L,
3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L), .Label = c("DMV only", "Hospital only",
"DMV and Hospital"), class = "factor"), census_block_id = c(17769L,
5701L, 10343L, 13943L, 824L, 4016L, 3801L, 12914L, 7158L, 15080L,
2291L, 5303L, 16371L, 12133L, 12211L, 13467L, 14581L, 1653L,
10279L, 8308L, 7212L, 11968L, 14201L, 17306L, 17659L, 12431L,
17235L, 14820L, 12904L, 14179L), race = structure(c(5L, 1L, 8L,
3L, 8L, 5L, 3L, 2L, 8L, 8L, 3L, 2L, 8L, 5L, 7L, 4L, 6L, 6L, 7L,
7L, 2L, 5L, 6L, 5L, 6L, 7L, 6L, 1L, 2L, 4L), .Label = c("v3_American_Indian_and_Alaska_Native_alone",
"v3_Asian_alone", "v3_Black_or_African_American_alone", "v3_Native_Hawaiian_and_Other_Pacific_Islander_alone",
"hisp_lat_white", "v4_White_alone", "v3_Some_Other_Race_alone",
"v3_Population_of_Two_or_More_Races"), class = "factor"), dem_count = c(0L,
0L, 4L, 13L, 8L, 0L, 0L, 0L, 8L, 9L, 0L, 2L, 5L, 0L, 0L, 0L,
1L, 0L, 0L, 6L, 4L, 1L, 0L, 3L, 36L, 3L, 89L, 0L, 0L, 0L), dem_perc = c(0,
0, 0, 0.2, 0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0,
0, 0.1, 0.2, 0, 0, 0, 0.9, 0.2, 0.9, 0, 0, 0), dist_miles = c(0.7087559032,
10.28985799, 5.121841087, 7.719372249, 6.484858265, 3.936395296,
0.4214821506, 0.8466445796, 3.260201155, 4.382266929, 2.264230064,
2.641397031, 1.06549425, 2.059872559, 5.330657466, 1.680736152,
1.70476816, 0.5969231801, 2.425111842, 1.782450555, 0.6958109783,
3.188800277, 1.481713599, 0.9994898577, 0.8435133297, 1.183770724,
1.686901434, 0.3508031623, 3.448550804, 1.456636437), geog = structure(list(
structure(list(structure(c(-75.153757, -75.153233, -75.152965,
-75.153384, -75.153489, -75.153518, -75.153757, 39.961684,
39.961658, 39.962493, 39.962512, 39.962526, 39.96248, 39.961684
), .Dim = c(7L, 2L))), class = c("XY", "POLYGON", "sfg")),
structure(list(structure(c(-75.005356, -75.005303, -75.005211,
-75.002842, -74.998085, -74.998206, -74.998254, -74.998297,
-74.998323, -74.99834, -74.998373, -74.998452, -74.998499,
-74.998527, -74.99854, -74.998584, -74.998628, -74.99866,
-74.998708, -74.998851, -74.998919, -74.998937, -74.998923,
-74.998931, -74.998985, -74.999028, -74.999052, -74.999073,
-74.999094, -74.999095, -74.99911, -74.999133, -74.999139,
-74.999145, -74.999146, -74.999155, -74.99917, -74.999145,
-74.999128, -74.999147, -74.999167, -74.999213, -74.999239,
-74.999257, -74.999265, -74.999277, -74.999284, -74.999262,
-74.999246, -74.999271, -74.9993, -74.999343, -74.999391,
-74.999417, -74.999407, -74.999402, -74.999417, -74.999426,
-74.99943, -74.99943, -74.999427, -74.999449, -74.999501,
-75.005173, -75.005292, -75.00532, -75.005351, -75.005356,
40.096443, 40.096306, 40.096207, 40.094156, 40.097378, 40.097513,
40.097545, 40.097589, 40.097642, 40.097702, 40.097761, 40.097829,
40.097867, 40.097923, 40.097975, 40.098036, 40.098089, 40.098135,
40.09817, 40.098253, 40.098322, 40.098378, 40.098417, 40.098462,
40.098485, 40.098543, 40.098613, 40.098659, 40.098721, 40.098768,
40.09881, 40.098868, 40.098942, 40.098997, 40.099055, 40.099118,
40.099177, 40.099219, 40.099285, 40.099325, 40.099361, 40.099417,
40.099472, 40.099538, 40.099619, 40.099697, 40.099736, 40.099779,
40.099825, 40.099866, 40.099925, 40.099972, 40.099992, 40.100018,
40.10006, 40.100121, 40.100211, 40.100295, 40.100368, 40.100449,
40.100507, 40.100581, 40.100695, 40.096848, 40.096727, 40.096666,
40.096594, 40.096443), .Dim = c(68L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-75.07509,
-75.074772, -75.074505, -75.074206, -75.073945, -75.073654,
-75.073256, -75.074095, -75.075032, -75.076902, -75.07509,
40.053364, 40.053707, 40.053977, 40.054306, 40.054577, 40.054892,
40.05533, 40.055819, 40.056359, 40.054387, 40.053364), .Dim = c(11L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-75.037313, -75.036217, -75.036672, -75.037769,
-75.037313, 40.028262, 40.028828, 40.029345, 40.028778,
40.028262), .Dim = c(5L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-75.066853, -75.066239,
-75.066575, -75.067188, -75.066853, 40.012999, 40.013143,
40.014024, 40.01389, 40.012999), .Dim = c(5L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-75.171034,
-75.170237, -75.169897, -75.170679, -75.171034, 40.002302,
40.002205, 40.003781, 40.003887, 40.002302), .Dim = c(5L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-75.161626, -75.160608, -75.160498, -75.160846,
-75.161382, -75.161525, -75.161626, 39.914979, 39.914851,
39.915361, 39.915404, 39.915473, 39.915485, 39.914979
), .Dim = c(7L, 2L))), class = c("XY", "POLYGON", "sfg"
)), structure(list(structure(c(-75.145802, -75.144996, -75.144962,
-75.145768, -75.145802, 39.961027, 39.960938, 39.961088,
39.961166, 39.961027), .Dim = c(5L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-75.139029,
-75.138588, -75.138269, -75.138693, -75.139029, 39.995099,
39.995047, 39.996537, 39.996594, 39.995099), .Dim = c(5L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-75.25705, -75.256992, -75.256913, -75.256727,
-75.256581, -75.256599, -75.256673, -75.256735, -75.256775,
-75.256761, -75.25672, -75.256684, -75.256762, -75.256741,
-75.255884, -75.255546, -75.255166, -75.254867, -75.254566,
-75.254415, -75.254266, -75.254223, -75.253918, -75.253813,
-75.253661, -75.253567, -75.253522, -75.253348, -75.253289,
-75.253197, -75.253177, -75.253152, -75.253007, -75.252959,
-75.252916, -75.252787, -75.252745, -75.252662, -75.251826,
-75.250954, -75.250273, -75.250101, -75.249686, -75.249354,
-75.249081, -75.248682, -75.248271, -75.248151, -75.247965,
-75.247688, -75.247642, -75.247604, -75.247489, -75.247418,
-75.247399, -75.247405, -75.247434, -75.247479, -75.247564,
-75.247685, -75.247778, -75.247903, -75.248056, -75.248615,
-75.248967, -75.249465, -75.249728, -75.249848, -75.249912,
-75.24999, -75.250054, -75.250133, -75.250221, -75.250313,
-75.250447, -75.250606, -75.250757, -75.250913, -75.251048,
-75.251102, -75.251201, -75.251283, -75.251342, -75.251336,
-75.251341, -75.251353, -75.251368, -75.251427, -75.251496,
-75.251603, -75.251707, -75.251811, -75.251923, -75.252094,
-75.252334, -75.252636, -75.252843, -75.252972, -75.253077,
-75.253222, -75.253314, -75.253407, -75.253582, -75.253712,
-75.253936, -75.254046, -75.25414, -75.254223, -75.254302,
-75.254337, -75.254381, -75.254456, -75.254543, -75.254632,
-75.254738, -75.254937, -75.255112, -75.255267, -75.255409,
-75.255543, -75.255686, -75.255812, -75.255852, -75.255892,
-75.255966, -75.256083, -75.256193, -75.256281, -75.256383,
-75.256586, -75.256686, -75.256746, -75.256816, -75.2569,
-75.256981, -75.25711, -75.25705, 39.963474, 39.963217,
39.962892, 39.962105, 39.96213, 39.962192, 39.962447,
39.962731, 39.962913, 39.962935, 39.962966, 39.962974,
39.963109, 39.963195, 39.963701, 39.963904, 39.964105,
39.964239, 39.964341, 39.964379, 39.964413, 39.964423,
39.964459, 39.964463, 39.96447, 39.964467, 39.964466,
39.96445, 39.964442, 39.96443, 39.964378, 39.964254,
39.963728, 39.963553, 39.963376, 39.962846, 39.96267,
39.962681, 39.962797, 39.962924, 39.963039, 39.96306,
39.963088, 39.963088, 39.963055, 39.962995, 39.962869,
39.962846, 39.962827, 39.962817, 39.96295, 39.962992,
39.963119, 39.963191, 39.963249, 39.963327, 39.963417,
39.963528, 39.963638, 39.963777, 39.963876, 39.963979,
39.964074, 39.964352, 39.964494, 39.964721, 39.96485,
39.964908, 39.964947, 39.965001, 39.965042, 39.965067,
39.965076, 39.965069, 39.965018, 39.964948, 39.964879,
39.964837, 39.964833, 39.964858, 39.964912, 39.965003,
39.965072, 39.965138, 39.965192, 39.965219, 39.965249,
39.965302, 39.965337, 39.965356, 39.965346, 39.96533,
39.965318, 39.965305, 39.96528, 39.965251, 39.965244,
39.965235, 39.965214, 39.965184, 39.965226, 39.965261,
39.9653, 39.965329, 39.96539, 39.965402, 39.965404, 39.965337,
39.965273, 39.965211, 39.965127, 39.965046, 39.964992,
39.964925, 39.96485, 39.964763, 39.964691, 39.964655,
39.96463, 39.964595, 39.964546, 39.964471, 39.964437,
39.964372, 39.964312, 39.964242, 39.964155, 39.964079,
39.963974, 39.963802, 39.963719, 39.963688, 39.963677,
39.963672, 39.963688, 39.963719, 39.963474), .Dim = c(137L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-75.118611, -75.118248, -75.117545, -75.117564,
-75.117871, -75.11847, -75.118611, 39.978879, 39.978466,
39.97884, 39.978868, 39.979208, 39.978901, 39.978879), .Dim = c(7L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-75.100476, -75.099292, -75.0995, -75.100685,
-75.100476, 39.983952, 39.984571, 39.984817, 39.984196,
39.983952), .Dim = c(5L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-75.118762, -75.116823,
-75.116722, -75.117288, -75.117738, -75.118181, -75.118685,
-75.118762, 39.99995, 39.999697, 40.000088, 40.000164, 40.000223,
40.000273, 40.000336, 39.99995), .Dim = c(8L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-75.177303,
-75.176606, -75.175885, -75.17575, -75.175714, -75.177048,
-75.177133, -75.177218, -75.177303, 39.995239, 39.995149,
39.995064, 39.995669, 39.995832, 39.996, 39.99602, 39.995649,
39.995239), .Dim = c(9L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-75.175345, -75.174294,
-75.173967, -75.173796, -75.172868, -75.172266, -75.171724,
-75.170976, -75.170902, -75.170488, -75.170101, -75.169649,
-75.169241, -75.168843, -75.16856, -75.168298, -75.16795,
-75.167612, -75.16728, -75.167073, -75.166888, -75.166817,
-75.166647, -75.166607, -75.163985, -75.162736, -75.156134,
-75.149395, -75.145874, -75.145235, -75.145137, -75.14514,
-75.145151, -75.144888, -75.144531, -75.144291, -75.144019,
-75.143964, -75.143888, -75.143801, -75.143558, -75.14341,
-75.143329, -75.143274, -75.143232, -75.143132, -75.143035,
-75.142945, -75.142843, -75.14274, -75.142639, -75.142539,
-75.142427, -75.142329, -75.142252, -75.14217, -75.14201,
-75.141843, -75.141769, -75.141629, -75.141586, -75.141538,
-75.141419, -75.141354, -75.141296, -75.141258, -75.141223,
-75.141175, -75.14111, -75.141058, -75.140977, -75.140904,
-75.140851, -75.140717, -75.140673, -75.14064, -75.140528,
-75.14044, -75.140368, -75.140307, -75.140194, -75.140166,
-75.140158, -75.140117, -75.140078, -75.140049, -75.140048,
-75.14005, -75.140086, -75.140075, -75.140037, -75.140022,
-75.140046, -75.140092, -75.140147, -75.140197, -75.140266,
-75.14035, -75.140442, -75.140545, -75.140631, -75.140718,
-75.140823, -75.140936, -75.141076, -75.141212, -75.141329,
-75.141437, -75.141576, -75.141735, -75.141923, -75.142139,
-75.142247, -75.142388, -75.142533, -75.142567, -75.142641,
-75.142712, -75.14274, -75.142741, -75.142754, -75.142786,
-75.142819, -75.142823, -75.142801, -75.14278, -75.142759,
-75.142751, -75.142644, -75.142247, -75.141533, -75.144184,
-75.144225, -75.14425, -75.144277, -75.145587, -75.145924,
-75.147111, -75.151526, -75.153669, -75.154006, -75.154342,
-75.15468, -75.15502, -75.155362, -75.156728, -75.157071,
-75.158436, -75.158778, -75.160827, -75.161169, -75.165609,
-75.16595, -75.166975, -75.167315, -75.167658, -75.167998,
-75.16834, -75.169705, -75.170046, -75.170095, -75.170389,
-75.170726, -75.17106, -75.17139, -75.17171, -75.172028,
-75.172343, -75.172657, -75.172972, -75.174063, -75.174862,
-75.174959, -75.175022, -75.175203, -75.175345, 39.897472,
39.897328, 39.897287, 39.897262, 39.897137, 39.897067, 39.896966,
39.896774, 39.896752, 39.896631, 39.896462, 39.89626, 39.896037,
39.895781, 39.895574, 39.895367, 39.895062, 39.894686, 39.894272,
39.893935, 39.893586, 39.89333, 39.892551, 39.892209, 39.892335,
39.891235, 39.891723, 39.892218, 39.892506, 39.892552, 39.891786,
39.891686, 39.891619, 39.889009, 39.889055, 39.889104, 39.889175,
39.889188, 39.889209, 39.889231, 39.889278, 39.889328, 39.889346,
39.889364, 39.88939, 39.889423, 39.889452, 39.889484, 39.889516,
39.889545, 39.889576, 39.88962, 39.889686, 39.889764, 39.889835,
39.889918, 39.890093, 39.890327, 39.890451, 39.890708, 39.890785,
39.890854, 39.89103, 39.891086, 39.891134, 39.89119, 39.891237,
39.891293, 39.891354, 39.891422, 39.891502, 39.891583, 39.89166,
39.891826, 39.891883, 39.891929, 39.892092, 39.892224, 39.892349,
39.892446, 39.892589, 39.892634, 39.892675, 39.892749, 39.892813,
39.892891, 39.89297, 39.893033, 39.89321, 39.893275, 39.893333,
39.893387, 39.893423, 39.893436, 39.893433, 39.893439, 39.893461,
39.893497, 39.893541, 39.893582, 39.893607, 39.893619, 39.893642,
39.89366, 39.893688, 39.89371, 39.893726, 39.893731, 39.89373,
39.893727, 39.893717, 39.893709, 39.893706, 39.893724, 39.893717,
39.893723, 39.893741, 39.893787, 39.893852, 39.893936, 39.894025,
39.894113, 39.894219, 39.89434, 39.894446, 39.894495, 39.894519,
39.894547, 39.895081, 39.895149, 39.895062, 39.895633, 39.895648,
39.895662, 39.895682, 39.895857, 39.895901, 39.89606, 39.896647,
39.896932, 39.896976, 39.897024, 39.897064, 39.897076, 39.897082,
39.897122, 39.897131, 39.897171, 39.89718, 39.897239, 39.897248,
39.897377, 39.897388, 39.897418, 39.897429, 39.897439, 39.89745,
39.89746, 39.897504, 39.897521, 39.89752, 39.897542, 39.897583,
39.897639, 39.897711, 39.897799, 39.897896, 39.897998, 39.898102,
39.898205, 39.89836, 39.898474, 39.898488, 39.898312, 39.897809,
39.897472), .Dim = c(176L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-75.171997, -75.171453,
-75.171112, -75.171442, -75.171691, -75.171997, 39.997826,
39.997752, 39.999242, 39.99929, 39.999321, 39.997826), .Dim = c(6L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-75.121438, -75.121043, -75.120842, -75.120186,
-75.119747, -75.120203, -75.120809, -75.121046, -75.121347,
-75.121438, 39.987859, 39.987567, 39.987678, 39.988022,
39.988251, 39.988761, 39.988431, 39.988226, 39.987945,
39.987859), .Dim = c(10L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-75.175652, -75.175085,
-75.174937, -75.175537, -75.175568, -75.175617, -75.175652,
39.958546, 39.958481, 39.959113, 39.959192, 39.958971, 39.958729,
39.958546), .Dim = c(7L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-75.220548, -75.219485,
-75.219434, -75.218843, -75.21973, -75.220588, -75.220548,
39.929, 39.928116, 39.928082, 39.928903, 39.929647, 39.929047,
39.929), .Dim = c(7L, 2L))), class = c("XY", "POLYGON", "sfg"
)), structure(list(structure(c(-75.017837, -75.017781, -75.017623,
-75.017498, -75.017334, -75.017075, -75.016815, -75.016526,
-75.016184, -75.015875, -75.015392, -75.01493, -75.013629,
-75.012747, -75.012932, -75.015485, -75.015661, -75.015771,
-75.016322, -75.016769, -75.017135, -75.017571, -75.017727,
-75.017813, -75.017845, -75.017837, 40.061101, 40.060973,
40.060816, 40.060741, 40.060871, 40.061051, 40.061211, 40.06136,
40.061514, 40.061623, 40.061758, 40.061848, 40.062047, 40.062185,
40.062866, 40.062466, 40.062422, 40.062395, 40.062227, 40.062051,
40.061879, 40.061633, 40.061515, 40.061379, 40.06124, 40.061101
), .Dim = c(26L, 2L))), class = c("XY", "POLYGON", "sfg")),
structure(list(structure(c(-75.158352, -75.157127, -75.156982,
-75.156943, -75.156896, -75.15684, -75.156737, -75.157587,
-75.158311, -75.15841, -75.158352, 39.958639, 39.958483,
39.958464, 39.95846, 39.958453, 39.958452, 39.95891, 39.959019,
39.959112, 39.95865, 39.958639), .Dim = c(11L, 2L))), class = c("XY",
"POLYGON", "sfg")), structure(list(structure(c(-75.248333,
-75.248093, -75.247306, -75.247206, -75.247213, -75.247494,
-75.247797, -75.24823, -75.248333, 39.975017, 39.973722,
39.973608, 39.973563, 39.973596, 39.975113, 39.975079, 39.97503,
39.975017), .Dim = c(9L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-75.220153, -75.220048,
-75.219574, -75.219293, -75.219212, -75.219202, -75.219123,
-75.220081, -75.220153, 39.953532, 39.953497, 39.953439,
39.953399, 39.953366, 39.953414, 39.953732, 39.953849, 39.953532
), .Dim = c(9L, 2L))), class = c("XY", "POLYGON", "sfg")),
structure(list(structure(c(-75.180099, -75.179083, -75.178994,
-75.180021, -75.180099, 39.948509, 39.94839, 39.94875, 39.948872,
39.948509), .Dim = c(5L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-75.213962, -75.213327,
-75.213262, -75.212895, -75.212948, -75.213066, -75.213162,
-75.213509, -75.213449, -75.213571, -75.213598, -75.213962,
40.020998, 40.020116, 40.020146, 40.020315, 40.020394, 40.02034,
40.020464, 40.020921, 40.020949, 40.021112, 40.021156, 40.020998
), .Dim = c(12L, 2L))), class = c("XY", "POLYGON", "sfg")),
structure(list(structure(c(-75.240955, -75.240362, -75.239583,
-75.239122, -75.238843, -75.238864, -75.239238, -75.239548,
-75.239665, -75.239945, -75.241166, -75.240955, 39.924857,
39.924363, 39.924903, 39.925225, 39.925429, 39.925493, 39.925825,
39.925896, 39.925835, 39.925686, 39.92504, 39.924857), .Dim = c(12L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-75.180314, -75.179761, -75.179724, -75.17949,
-75.180045, -75.180283, -75.180314, 39.92062, 39.920549,
39.920711, 39.92179, 39.921872, 39.92078, 39.92062), .Dim = c(7L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-75.150719, -75.148872, -75.148766, -75.150611,
-75.150719, 40.066367, 40.066131, 40.066619, 40.066858,
40.066367), .Dim = c(5L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(-75.236191, -75.236143,
-75.236029, -75.234329, -75.236178, -75.236191, 39.991419,
39.99102, 39.991094, 39.991891, 39.991943, 39.991419), .Dim = c(6L,
2L))), class = c("XY", "POLYGON", "sfg")), structure(list(
structure(c(-75.215684, -75.215628, -75.214676, -75.214732,
-75.214776, -75.215121, -75.215106, -75.215422, -75.215434,
-75.215726, -75.215684, 39.968504, 39.968061, 39.968133,
39.968588, 39.968939, 39.9689, 39.968815, 39.96878, 39.968847,
39.968823, 39.968504), .Dim = c(11L, 2L))), class = c("XY",
"POLYGON", "sfg"))), class = c("sfc_POLYGON", "sfc"), precision = 0, bbox = structure(c(xmin = -75.25711,
ymin = 39.889009, xmax = -74.998085, ymax = 40.100695), class = "bbox"), crs = structure(list(
input = "EPSG:4326", wkt = "GEOGCRS[\"WGS 84\",\n ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n MEMBER[\"World Geodetic System 1984 (Transit)\"],\n MEMBER[\"World Geodetic System 1984 (G730)\"],\n MEMBER[\"World Geodetic System 1984 (G873)\"],\n MEMBER[\"World Geodetic System 1984 (G1150)\"],\n MEMBER[\"World Geodetic System 1984 (G1674)\"],\n MEMBER[\"World Geodetic System 1984 (G1762)\"],\n ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n LENGTHUNIT[\"metre\",1]],\n ENSEMBLEACCURACY[2.0]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n CS[ellipsoidal,2],\n AXIS[\"geodetic latitude (Lat)\",north,\n ORDER[1],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n AXIS[\"geodetic longitude (Lon)\",east,\n ORDER[2],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n USAGE[\n SCOPE[\"Horizontal component of 3D system.\"],\n AREA[\"World.\"],\n BBOX[-90,-180,90,180]],\n ID[\"EPSG\",4326]]"), class = "crs"), n_empty = 0L)), class = c("sf",
"grouped_df", "tbl_df", "tbl", "data.frame"), row.names = c(NA,
30L), groups = structure(list(analysis_group = structure(1:3, .Label = c("DMV only",
"Hospital only", "DMV and Hospital"), class = "factor"), .rows = structure(list(
1:10, 11:20, 21:30), ptype = integer(0), class = c("vctrs_list_of",
"vctrs_vctr", "list"))), class = c("tbl_df", "tbl", "data.frame"
), row.names = c(NA, -3L), .drop = TRUE), sf_column = "geog", agr = structure(c(analysis_group = NA_integer_,
census_block_id = NA_integer_, race = NA_integer_, dem_count = NA_integer_,
dem_perc = NA_integer_, dist_miles = NA_integer_), class = "factor", .Label = c("constant",
"aggregate", "identity")))

R leaflet/shiny: display and offset overlapping polylines based on column value

I'm trying to stretch what I can do in R and have hit a wall and hope you can point me in the right direction on how best I could accomplish what I want to do. I am plotting a bunch of polylines from a shape file whose data looks like this:
placename,placetype,placebook,numbooks,row_number,placelinecoords
Main Street,street,"BOOKTDS",1,1,LINESTRING(-3.700559678237278 40.42098474661999,-3.698346475125229 40.42033268716025,-3.69731867182242 40.42003534594848,-3.697243299580215 40.42003534594848)
First Street,street,"BOOKESM",3,1,LINESTRING(-3.710546258545151 40.41308011176736,-3.710213664627304 40.41309440722183,-3.709234658336868 40.41341707524381,-3.708880606746902 40.4135232694443,-3.708711627578964 40.41372748858957)
First Street,street,"BOOKTDS",3,2,LINESTRING(-3.710546258545151 40.41308011176736,-3.710213664627304 40.41309440722183,-3.709234658336868 40.41341707524381,-3.708880606746902 40.4135232694443,-3.708711627578964 40.41372748858957)
First Street,street,"BOOKLDE",3,3,LINESTRING(-3.710546258545151 40.41308011176736,-3.710213664627304 40.41309440722183,-3.709234658336868 40.41341707524381,-3.708880606746902 40.4135232694443,-3.708711627578964 40.41372748858957)
Loughborough Street,street,"BOOKESM",2,1,LINESTRING(-3.707336328013795 40.42433623251054,-3.707014282978915 40.42429971916709,-3.706726498054129 40.42429971916709,-3.706281116622912 40.42409628731927,-3.705390353760477 40.42377288157678,-3.704602371228324 40.42316257940762,-3.70376642454204 40.42259400231908)
Loughborough Street,street,"BOOKTDS",2,2,LINESTRING(-3.707336328013795 40.42433623251054,-3.707014282978915 40.42429971916709,-3.706726498054129 40.42429971916709,-3.706281116622912 40.42409628731927,-3.705390353760477 40.42377288157678,-3.704602371228324 40.42316257940762,-3.70376642454204 40.42259400231908)
Oak Street,street,"BOOKLMI",2,1,LINESTRING(-3.700391803697817 40.41664973667679,-3.700384951675798 40.41673842198933,-3.699754565650076 40.4176044018386,-3.699549004989513 40.41782350340716)
Oak Street,street,"BOOKLBU",2,2,LINESTRING(-3.700391803697817 40.41664973667679,-3.700384951675798 40.41673842198933,-3.699754565650076 40.4176044018386,-3.699549004989513 40.41782350340716)
"placebook" is a unique code for a book where a particular street name appears. I have assigned each book with a color and load in the data:
books = c("OBRAESM", "OBRAHOR", "OBRAINS", "OBRALBU", "OBRALCT", "OBRALDB","OBRALDE","OBRALMI","OBRALPI","OBRATDS")
color = c("red", "orange", "yellow", "green", "blue", "pink","gray","purple","black","white")
df = cbind.data.frame(books,color)
colnames(df) = c("books","color")
placeographypaths <- readOGR("shapefiles/places_paths.shp")
placeographypathsstreets <- subset(placeographypaths, placetype %like% "street")
What I would like to do is plot these lines onto the map by book, with each appearing as a different color. When there is more than one book assigned to a particular line, I would need to offset the lines so they are all visible. These lines will overlap in their entirety and in most cases there will only be a few lines overlapping in the same location (the maximum is 5, but most are 1-3).
So "First Street" would display three lines: red, gray, and white. I see there's a PolylineOffset tool, but I can't find any examples that use column values as the criteria for offsetting--and it seems to mostly apply to more complex situations where only a part of the line overlaps)--perhaps there's a simpler solution that I'm missing.
I spent some time to think what I could do since I do not know how to use PolylineOffset. It seems that this feature will be coming in the leaflet package. I want to suggest an alternative for your visualization. Reading your question, you would have five types of lines. That is, each line type can represent how many times streets appear in your data set. You said the maximum overlapping is five times. I think you can create five levels in in a grouping variable and create colors in leaflet.
First, I summarized your data grouping by placename. For each placename, I counted how many data points (rows) exist. I also created a string containing book names. The string is arranged for popups. Then, I created a color palette for leaflet. Finally, I drew a map. If you want something fancier, I think you can use the htmlTable package, for example.
library(dplyr)
library(leaflet)
library(sf)
library(viridis)
# Aggregate the data by placename. Note your data is called mysf, which is an
# sf object.
group_by(mysf, placename) %>%
summarize(frequency = factor(n(), levels = 1:3, labels = c("1", "2", "3")),
books = paste0("<br/>", paste0(placebook, collapse = "<br/>"))) -> mysf2
# Create categorical colors
# I am checking colors here
previewColors(colorFactor(palette = "viridis", domain = mysf2$frequency),
values = unique(mysf2$frequency))
# Create my own palette
mypal <- colorFactor(palette = "viridis", domain = mysf2$frequency)
# Draw a leaflet map
leaflet() %>%
addProviderTiles("CartoDB.Positron") %>%
addPolylines(data = mysf2, color = ~mypal(mysf2$frequency),
popup = paste("Place: ", mysf2$placename, "<br>",
"Book(s): ", mysf2$books, "<br>")) %>%
addLegend(position = "bottomright", pal = mypal, values = mysf2$frequency,
title = "Frequency",
opacity = 1)
Finally one note. The way you provided your data does not unfortunately work for anybody to replicate your situation. (I invested some good amount of time to manually create your data. I would not do this if I do not have enough time. Perhapd you would not want to as well, right?) If your data is large, you want to consider uploading it somewhere else. Otherwise, you can use dput() which creates a copy of your data. If you carefully see questions in R, many users provide their data with dput(). I highly recommend you to use this function when you ask more questions in the future.
DATA
mysf <- structure(list(placename = structure(c(3L, 1L, 1L, 1L, 2L, 2L,
4L, 4L), .Label = c("First Street", "Loughborough Street", "Main Street",
"Oak Street"), class = "factor"), placetype = structure(c(1L,
1L, 1L, 1L, 1L, 1L, 1L, 1L), .Label = "placetype", class = "factor"),
placebook = structure(c(5L, 1L, 5L, 3L, 1L, 5L, 4L, 2L), .Label = c("BOOKESM",
"BOOKLBU", "BOOKLDE", "BOOKLMI", "BOOKTDS"), class = "factor"),
geometry = structure(list(structure(c(-3.70055967823728,
-3.69834647512523, -3.69731867182242, -3.69724329958022,
40.42098474662, 40.4203326871602, 40.4200353459485, 40.4200353459485
), .Dim = c(4L, 2L), class = c("XY", "LINESTRING", "sfg")),
structure(c(-3.71054625854515, -3.7102136646273, -3.70923465833687,
-3.7088806067469, -3.70871162757896, 40.4130801117674,
40.4130944072218, 40.4134170752438, 40.4135232694443,
40.4137274885896), .Dim = c(5L, 2L), class = c("XY",
"LINESTRING", "sfg")), structure(c(-3.71054625854515,
-3.7102136646273, -3.70923465833687, -3.7088806067469,
-3.70871162757896, 40.4130801117674, 40.4130944072218,
40.4134170752438, 40.4135232694443, 40.4137274885896), .Dim = c(5L,
2L), class = c("XY", "LINESTRING", "sfg")), structure(c(-3.71054625854515,
-3.7102136646273, -3.70923465833687, -3.7088806067469,
-3.70871162757896, 40.4130801117674, 40.4130944072218,
40.4134170752438, 40.4135232694443, 40.4137274885896), .Dim = c(5L,
2L), class = c("XY", "LINESTRING", "sfg")), structure(c(-3.70733632801379,
-3.70701428297891, -3.70672649805413, -3.70628111662291,
-3.70539035376048, -3.70460237122832, -3.70376642454204,
40.4243362325105, 40.4242997191671, 40.4242997191671,
40.4240962873193, 40.4237728815768, 40.4231625794076,
40.4225940023191), .Dim = c(7L, 2L), class = c("XY",
"LINESTRING", "sfg")), structure(c(-3.70733632801379,
-3.70701428297891, -3.70672649805413, -3.70628111662291,
-3.70539035376048, -3.70460237122832, -3.70376642454204,
40.4243362325105, 40.4242997191671, 40.4242997191671,
40.4240962873193, 40.4237728815768, 40.4231625794076,
40.4225940023191), .Dim = c(7L, 2L), class = c("XY",
"LINESTRING", "sfg")), structure(c(-3.70039180369782,
-3.7003849516758, -3.69975456565008, -3.69954900498951,
40.4166497366768, 40.4167384219893, 40.4176044018386,
40.4178235034072), .Dim = c(4L, 2L), class = c("XY",
"LINESTRING", "sfg")), structure(c(-3.70039180369782,
-3.7003849516758, -3.69975456565008, -3.69954900498951,
40.4166497366768, 40.4167384219893, 40.4176044018386,
40.4178235034072), .Dim = c(4L, 2L), class = c("XY",
"LINESTRING", "sfg"))), class = c("sfc_LINESTRING", "sfc"
), precision = 0, bbox = structure(c(xmin = -3.71054625854515,
ymin = 40.4130801117674, xmax = -3.69724329958022, ymax = 40.4243362325105
), class = "bbox"), crs = structure(list(epsg = 4326L, proj4string = "+proj=longlat +datum=WGS84 +no_defs"), class = "crs"), n_empty = 0L)), row.names = c(NA,
8L), sf_column = "geometry", agr = structure(c(placename = NA_integer_,
placetype = NA_integer_, placebook = NA_integer_), class = "factor", .Label = c("constant",
"aggregate", "identity")), class = c("sf", "data.frame"))

Add vertical polylines of specific length to contiguous polygons in R

I'm attempting to pro-grammatically add multiple vertical polylines of specific length to contiguous polygons in R. The number and length of the polylines should be specified by the user and can range from 1 to 8 polylines and 5000 to 10000 feet long per contiguous polygons. How can I achieve this in R?
I'm able to do this manually by the use of the mapedit package for a couple of polygons but I would like to automate the process for several thousand contiguous polygons.
# Load required libraries
library(mapedit)
library(mapview)
library(dplyr)
library(sp)
# Sample polygons and polylines
geometry = structure(list(structure(list(structure(c(8.769563, 8.769563,
8.770507, 8.770507, 8.769563, 50.815273, 50.815714, 50.815714,
50.815273, 50.815273), .Dim = c(5L, 2L))), class = c("XY", "POLYGON",
"sfg")), structure(list(structure(c(8.769568, 8.769568, 8.770507,
8.770507, 8.769568, 50.814852, 50.81527, 50.81527, 50.814852,
50.814852), .Dim = c(5L, 2L))), class = c("XY", "POLYGON", "sfg"
)), structure(list(structure(c(8.769568, 8.769568, 8.770502,
8.770502, 8.769568, 50.814412, 50.814849, 50.814849, 50.814412,
50.814412), .Dim = c(5L, 2L))), class = c("XY", "POLYGON", "sfg"
)), structure(list(structure(c(8.769568, 8.769568, 8.770502,
8.770502, 8.769568, 50.814005, 50.814408, 50.814408, 50.814005,
50.814005), .Dim = c(5L, 2L))), class = c("XY", "POLYGON", "sfg"
)), structure(list(structure(c(8.770502, 8.770502, 8.771301,
8.771301, 8.770502, 50.815273, 50.815717, 50.815717, 50.815273,
50.815273), .Dim = c(5L, 2L))), class = c("XY", "POLYGON", "sfg"
)), structure(list(structure(c(8.770518, 8.770518, 8.771301,
8.771301, 8.770518, 50.814852, 50.81527, 50.81527, 50.814852,
50.814852), .Dim = c(5L, 2L))), class = c("XY", "POLYGON", "sfg"
)), structure(list(structure(c(8.770507, 8.770507, 8.771301,
8.771301, 8.770507, 50.814408, 50.814849, 50.814849, 50.814408,
50.814408), .Dim = c(5L, 2L))), class = c("XY", "POLYGON", "sfg"
)), structure(list(structure(c(8.770507, 8.770507, 8.771296,
8.771296, 8.770507, 50.814005, 50.814405, 50.814405, 50.814005,
50.814005), .Dim = c(5L, 2L))), class = c("XY", "POLYGON", "sfg"
)), structure(c(8.769794, 8.769783, 50.814785, 50.814076), .Dim = c(2L,
2L), class = c("XY", "LINESTRING", "sfg")), structure(c(8.770051,
8.770035, 50.814785, 50.814069), .Dim = c(2L, 2L), class = c("XY",
"LINESTRING", "sfg")), structure(c(8.770271, 8.77026, 50.814781,
50.814076), .Dim = c(2L, 2L), class = c("XY", "LINESTRING", "sfg"
))), class = c("sfc_GEOMETRY", "sfc"), precision = 0, bbox = structure(c(xmin = 8.769563,
ymin = 50.814005, xmax = 8.771301, ymax = 50.815717), class = "bbox"), crs = structure(list(
epsg = 4326L, proj4string = "+proj=longlat +datum=WGS84 +no_defs"), class = "crs"), classes = c("POLYGON",
"POLYGON", "POLYGON", "POLYGON", "POLYGON", "POLYGON", "POLYGON",
"POLYGON", "LINESTRING", "LINESTRING", "LINESTRING"), n_empty = 0L)
# Visualize geometry
mapview(geometry)
I attempted to create regularly sampled points via the spsample function inside the polygons and connecting them by lines but was unsuccessful. appreciate any help I can get.
Here's one way to create lines given a polygon. It's probably not exactly what you want, since your request is quite specific, but hopefully the code is generic enough that you cn adapt it.
library(sf)
library(purrr)
polygon <- st_polygon(list(matrix(c(1,1,2,2,1,1,0,0,1,1), ncol = 2)))
# use polygon bounding box to o compute line parameters
bb <- st_bbox(polygon)
number_of_lines <- 5
line_length <- (bb[["ymax"]] - bb[["ymin"]]) / 1.2
y_offset <- bb[["ymin"]] + (bb[["ymax"]] - bb[["ymin"]] - line_length) / 2
# compute coordinates
xs <- seq(bb[["xmin"]], bb[["xmax"]], length.out = number_of_lines)
ys <- bb[["ymin"]] + line_length
# create a linestring
lines <- purrr::map2(xs, ys, ~st_linestring(matrix(c(.x, .x, .y,y_offset), ncol = 2))) %>% st_sfc(crs = st_crs(polygon))
# view
plot(polygon)
plot(lines, col = 2, add = TRUE)

What unit is the `dist` argument in `st_buffer` set to by default?

I have the following map of Mexico. It shows all of its municipalities and around 400 weather stations.
I want to create a 10km buffer around each station and eventually, associate each municipality to a station that is located within each radius.
The map and the stations are stored on separate sf objects. I tired the following:
buffers <- st_buffer(stations, dist = 1)
I thought the dist argument was set to kilometers, so I tried dist = 10. Unfortunately, this returned HUGE buffers for each station. That's why I am using dist = 1, but even these buffers are as big as a state! This question, suggests I transform my stations to Irish Grid, but I couldn't replicate the accepted answer. I am now wondering what unit the dist argument is set to.
From the aforementioned question, I assume it's set to degrees. How can I set a 10km buffer around each station?
Additional info:
My CRS is set to 4326 on both objects (the Mexican map and the stations).
This is my stations data:
> dput(head(stations))
structure(list(station_number = c(1004L, 1005L, 1008L, 1012L,
1017L, 1018L), station_alt = c(1925, 1844, 2323, 1589, 2172,
2053), month = c(9L, 9L, 9L, 9L, 9L, 9L), Mean_min = c(11.6,
12.75, 12.25, 13.9666666666667, 12.9, 12.6833333333333), Mean_max = c(26.9333333333333,
26.85, 24.0833333333333, 29.0333333333333, 24.8666666666667,
26.1333333333333), months_observed = c(5L, 5L, 5L, 5L, 5L, 5L
), geometry = structure(list(structure(c(-102.199, 22.001), class = c("XY",
"POINT", "sfg")), structure(c(-102.372, 21.781), class = c("XY",
"POINT", "sfg")), structure(c(-102.135, 22.203), class = c("XY",
"POINT", "sfg")), structure(c(-102.802, 21.794), class = c("XY",
"POINT", "sfg")), structure(c(-102.444, 22.233), class = c("XY",
"POINT", "sfg")), structure(c(-102.415, 22.141), class = c("XY",
"POINT", "sfg"))), class = c("sfc_POINT", "sfc"), precision = 0, bbox = structure(c(xmin = -102.802,
ymin = 21.781, xmax = -102.135, ymax = 22.233), class = "bbox"), crs = structure(list(
epsg = NA_integer_, proj4string = NA_character_), class = "crs"), n_empty = 0L)), sf_column = "geometry", agr = structure(c(station_number = NA_integer_,
station_alt = NA_integer_, month = NA_integer_, Mean_min = NA_integer_,
Mean_max = NA_integer_, months_observed = NA_integer_), .Label = c("constant",
"aggregate", "identity"), class = "factor"), row.names = c(NA,
6L), class = c("sf", "data.frame"))
Your coordinates are long/lat, so the distance will be in degrees. You should first project to a spatial reference in meter units and then take 10 000 meters.
The manual of st_buffer says this about the dist argument:
in case dist is a units object, it should be convertible to
arc_degree if x has geographic coordinates, and to st_crs(x)$units
otherwise
If you leave the coordinates in 4326 you should be able to take something like 0.1 which should be about 11 km for Mexico, but you will see a warning message:
In st_buffer.sfc(st_geometry(x), dist, nQuadSegs, endCapStyle =
endCapStyle, : st_buffer does not correctly buffer
longitude/latitude data
So first convert to another projection (in meter) and enter the distance in meters. This should work, which uses EPSG 7801:
library(sf)
pois <- st_as_sf(stations)
st_crs(pois) <- 4326
pois <- st_transform(pois, crs = 7801)
plot(st_geometry(pois))
buff <- st_buffer(pois, dist = 10000)
plot(st_geometry(buff), add = TRUE)
Control with leaflet and the measure tool:
buff <- st_transform(buff, crs = 4326)
library(leaflet)
leaflet() %>%
addTiles() %>%
addMeasure(primaryLengthUnit = "meters") %>%
addMarkers(data = pois) %>%
addPolygons(data = buff)

Is there a way to plot ggplots on leaflets without saving them on the disc?

I have a region with sub regions. For each sub region I have a simple ggplot, that I want to put into the center of each region.
I am using a leaflet package, so my code looks like this:
employees_spdf <- structure(list(ID = structure(c(7L, 8L, 4L, 3L, 10L, 1L, 9L,
6L, 2L, 5L), .Label = c("75006", "78280", "91370", "92110", "92420",
"93270", "93440", "95000", "95330", "95400"), class = "factor"),
n = c(10L, 79L, 99L, 16L, 55L, 94L, 25L, 40L, 51L, 44L),
geometry = structure(list(structure(c(2.423864, 48.95034085
), class = c("XY", "POINT", "sfg")), structure(c(2.05650642,
49.0277569), class = c("XY", "POINT", "sfg")), structure(c(2.30575224,
48.90353573), class = c("XY", "POINT", "sfg")), structure(c(2.25171264,
48.75044317), class = c("XY", "POINT", "sfg")), structure(c(2.4076232,
49.00203584), class = c("XY", "POINT", "sfg")), structure(c(2.33267081,
48.84896818), class = c("XY", "POINT", "sfg")), structure(c(2.32290084,
49.02966528), class = c("XY", "POINT", "sfg")), structure(c(2.53124065,
48.938607), class = c("XY", "POINT", "sfg")), structure(c(2.07605224,
48.77307843), class = c("XY", "POINT", "sfg")), structure(c(2.16026445,
48.84105162), class = c("XY", "POINT", "sfg"))), class = c("sfc_POINT",
"sfc"), precision = 0, bbox = structure(c(xmin = 2.05650642,
ymin = 48.75044317, xmax = 2.53124065, ymax = 49.02966528
), class = "bbox"), crs = structure(list(epsg = 4326L, proj4string = "+proj=longlat
+datum=WGS84 +no_defs"), class = "crs"), n_empty = 0L)), sf_column = "geometry", agr
= structure(c(ID = NA_integer_,
n = NA_integer_), .Label = c("constant", "aggregate", "identity"
), class = "factor"), row.names = c(380L, 433L, 312L, 257L, 464L,
6L, 457L, 364L, 156L, 341L), class = c("sf", "data.frame"))
getImage <- function(n, ncol=10, proba = 1) {
require(ggthemes)
require(ggplot2)
require(dplyr)
num <- 1:n
x <- num%%ncol
y <- num%/%ncol
df <- data.frame(x=x,y=y)
df[nrow(df),] <- c(0,0)
df <- df %>% arrange(y,x)
df$dispo <- as.factor(c(rep(1,round(n*proba)),rep(0,(n-round(n*proba)))))
ymax <- ifelse(n>ncol*10,n/ncol+1,ncol+1)
#if we have a few points, let's center them
if (n< ncol*10) df$y <- df$y + (ncol-(max(df$y)))/2
g<- ggplot(df,aes(x=x,y=y, color=dispo))+
# geom_point(shape="\UC6C3", colour="red",size=5)+
geom_point(size=10,show.legend = F)+
xlim(-1,ncol+1) + ylim(-1,ymax)+
theme_void()+
scale_fill_manual(values = c("green", "red"))
g
}
plots <- lapply(employees_spdf$n,function(x) getImage(x,proba = .66))
for (i in 1:nrow(employees_spdf)) {
filename <- paste("./tmp/",employees_spdf[i,]$ID,".png",sep="")
ggsave(filename = filename,
plot = plots[[i]],
device = "png",
width = 5, height = 5,
units = "in", bg="transparent")}
filenames <- unlist(lapply(employees_spdf$ID, function(x) paste(paste("./tmp/",x,".png",sep=""))))
empIcons <- icons(
iconUrl = filenames,
iconWidth = 128,
iconHeight = 128
)
leaflet() %>%
addTiles() %>%
addMarkers(data=employees_spdf,
icons=empIcons)
The bottleneck here is eventually a need to save each ggplot as a file, read it and then use it as an icon. For 500+ subregions it takes quite a while to load...
The core of the issue as far as I undesrtand is that a leaflet MakeIcon function can work only whith files and I cannot pass a list of ggplot objects to it. That way it would have worked much faster I believe...
The solution here could be saving a ggplot for each region before the application loads and read them on the fly, however I thought there might be a more elegant option. Do you know one?

Resources