From c805c0c43eb4f2be057b71b74ac46b177bbc1759 Mon Sep 17 00:00:00 2001 From: RichardPatterson Date: Mon, 4 Mar 2024 17:18:06 +0000 Subject: [PATCH 1/5] Remove dplyr Group lines using igraph not looping --- .Rbuildignore | 2 ++ .gitignore | 2 ++ DESCRIPTION | 6 ++-- R/midlines_functions.R | 6 ++-- R/midlines_polish.R | 15 ++++---- R/midlines_refine.R | 81 +++++++++++++----------------------------- vignettes/midlines.Rmd | 7 ++-- 7 files changed, 48 insertions(+), 71 deletions(-) diff --git a/.Rbuildignore b/.Rbuildignore index acc4601..c8ea19e 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -6,3 +6,5 @@ ^working/examples\.R$ ^README\.Rmd$ ^data-raw$ +^doc$ +^Meta$ diff --git a/.gitignore b/.gitignore index e67f93b..c48f0ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .Rproj.user inst/doc +/doc/ +/Meta/ diff --git a/DESCRIPTION b/DESCRIPTION index f273c54..1f1ea8e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -12,13 +12,13 @@ License: MIT + file LICENSE Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.1 +RoxygenNote: 7.2.3 Imports: sf, - dplyr, zoo, lwgeom, - stats + stats, + igraph Depends: R (>= 2.10) Suggests: diff --git a/R/midlines_functions.R b/R/midlines_functions.R index a77de8c..04930d2 100644 --- a/R/midlines_functions.R +++ b/R/midlines_functions.R @@ -168,7 +168,7 @@ midlines_clean = function(x, n_removed = 1, border_line = NULL){ }#for loop - removed_mid_points = dplyr::bind_rows(removed_mid_points) + removed_mid_points = do.call("rbind", removed_mid_points) removed_mid_points$removed_flag = factor(1) trimmed_mid_points$removed_flag = factor(0) @@ -179,7 +179,7 @@ midlines_clean = function(x, n_removed = 1, border_line = NULL){ sf::st_drop_geometry(trimmed_points), FUN = unique) - return = dplyr::inner_join(x, rem_flag, by = "line_id") + return = merge(x, rem_flag, by = "line_id") # Remove line_id var if it wasn't present in input (x) if(!line_id_present){ @@ -190,7 +190,7 @@ midlines_clean = function(x, n_removed = 1, border_line = NULL){ } -group_id = line_id = geometry = NULL +removed_flag = group_id = line_id = geometry = NULL diff --git a/R/midlines_polish.R b/R/midlines_polish.R index ecc6a68..02f1393 100644 --- a/R/midlines_polish.R +++ b/R/midlines_polish.R @@ -42,14 +42,16 @@ midlines_debit = function(x, length) { midlines_smooth = function(x, width = 3){ dat = midlines_group(x) - dat = dplyr::select(dat, geometry) # stop warning about repeating attributes - dat = sf::st_cast(dat,"LINESTRING") + dat = subset(dat, select = geometry) # stop warning about repeating attributes + #dat = sf::st_cast(dat,"LINESTRING") + dat = sf::st_sf(geometry = sf::st_cast(sf::st_line_merge(sf::st_union(x)), "LINESTRING")) + s = function(x){ l = length(dat$geometry[[x]]) - a = zoo::rollapply(dat$geometry[[x]][1:(l/2)], width = width, mean ) - b = zoo::rollapply(dat$geometry[[x]][(l/2+1):l], width = width, mean ) + a = zoo::rollmean(dat$geometry[[x]][1:(l/2)], k = width) + b = zoo::rollmean(dat$geometry[[x]][(l/2+1):l], k = width) sx = dat$geometry[[x]][1] ex = dat$geometry[[x]][(l/2)] @@ -106,8 +108,9 @@ midlines_smooth = function(x, width = 3){ midlines_dedensify = function(x, density){ x = midlines_group(x) - x = dplyr::select(x, geometry) - ls = sf::st_cast(x,"LINESTRING") + x = subset(x, select = geometry) + ls = sf::st_sf(geometry = sf::st_cast(sf::st_line_merge(sf::st_union(x)), "LINESTRING")) + ls$line_id = 1:nrow(ls) de_densified = sf::st_as_sf(sf::st_line_sample(ls, density = density)) diff --git a/R/midlines_refine.R b/R/midlines_refine.R index c0c5cd8..aad8f05 100644 --- a/R/midlines_refine.R +++ b/R/midlines_refine.R @@ -1,4 +1,5 @@ # An internal function +# Largely replicates the answer here: https://stackoverflow.com/questions/69175360/is-there-a-way-to-merge-contiguous-multilinestrings-sf-object-in-r #' Groups line segments into contiguous groups, returns these as multilinestrings. #' @@ -7,50 +8,31 @@ #' @export midlines_group = function(x) { - lines = sf::st_as_sf(sf::st_cast(sf::st_line_merge(sf::st_union(x)), "LINESTRING")) + touches = sf::st_touches(x) + graph = igraph::graph_from_adj_list(touches) - colnames(lines)[colnames(lines) == colnames(lines)] = "geometry" #this only works cos there is one column - sf::st_geometry(lines) <- "geometry" + groups <- igraph::components(graph)$membership - lines$group_id = NA - lines$n_ = 1:nrow(lines) + grouped = stats::aggregate(x, by = list(group_id = groups), FUN = unique) - inter = sf::st_intersects(lines$geometry, lines$geometry) - group_index = 1 + grouped$n_lines = lengths(grouped$line_id) + grouped$length = sf::st_length(grouped) - for(l in 1:nrow(lines)) { + #grouped = subset(grouped, select = -c(removed_flag)) + grouped = grouped[, names(grouped) != "removed_flag"] - lines$group_id[lines$n_ %in% - inter[[lines$n_[l]]]] = group_index - - lines = lines[order(lines$group_id, na.last = TRUE),] - - if(anyNA(lines$group_id[l+1])) group_index = group_index +1 - #print(group_index) - l = l+1 - } - - multilines = sf::st_cast( - dplyr::summarise( - dplyr::group_by(lines, group_id), - do_union = FALSE) - ,"MULTILINESTRING") - - multilines$n_lines =lengths(lapply(multilines$geometry, unlist))/4 - multilines$length = sf::st_length(multilines) - - #return(list(multilines,lines)) - #} - grouped_multilinestring = multilines - ############## - - - # this reokaces the variable from the group_lines_a as applying that to that grouped line does something odd. - grouped_multilinestring$n_lines = (lengths(lapply(grouped_multilinestring$geometry, unlist)) - 2)/ 2 - - return(grouped_multilinestring) + return(grouped) } +# This seems to be okay. It returns a df rather than a tibble, but that's fine because that's what it it passed. +# d = midlines_group(removed) +# e = midlines::midlines_group(removed) +# d +# e +# class(d) +# class(e) +# all.equal(d,e) +# class(removed) @@ -107,10 +89,8 @@ midlines_check = function(x, n_removed = NULL, length = NULL, border_line = NULL removed = x[x$removed_flag==1,] cleaned = x[x$removed_flag==0,] - #moving this within this cleaning function x_multilines = midlines_group(removed) - # using n_lines as the number of cycles of removing if(!(is.null(n_removed))){ add_back_groups1 = x_multilines$group_id[x_multilines$n_lines >= n_removed] @@ -127,7 +107,7 @@ midlines_check = function(x, n_removed = NULL, length = NULL, border_line = NULL #the first line finds lines touching border and then those removed groups that hit these if(!(is.null(border_line))) { - #touch_border = cleaned[sf::st_is_within_distance(cleaned, border_line, dist = border_distance, sparse = FALSE),] + touch_border = removed[sf::st_intersects(removed, border_line, sparse = FALSE),] add_back_groups3 = x_multilines$group_id[sf::st_intersects(x_multilines, sf::st_union(touch_border), sparse = FALSE)] } else { @@ -136,25 +116,14 @@ midlines_check = function(x, n_removed = NULL, length = NULL, border_line = NULL add_back_groups = unique(c(add_back_groups1, add_back_groups2, add_back_groups3)) - add_back = x_multilines[x_multilines$group_id %in% add_back_groups,"geometry"] - - # identify the lines to add back from the multilines - add_back_index = lengths(sf::st_covered_by(removed, add_back)) != 0 + add_back_line_ids = unlist(x_multilines$line_id[x_multilines$group_id %in% add_back_groups]) - add_back_lines = removed[add_back_index,] - still_removed = removed[!add_back_index,] + x$removed_flag2 = x$removed_flag + x$removed_flag2[x$line_id %in% add_back_line_ids] = 0 - cleaned$added_flag = factor(0) - still_removed$added_flag = factor(0) - add_back_lines$added_flag = factor(1) - - cleaned$removed_flag2 = factor(0) - add_back_lines$removed_flag2 = factor(0) - still_removed$removed_flag2 = factor(1) - - rbind(cleaned, add_back_lines, still_removed) + x$added_flag = factor(as.integer(x$removed_flag != x$removed_flag2)) # should probably remove this as its unnecessary + return(x) } - diff --git a/vignettes/midlines.Rmd b/vignettes/midlines.Rmd index 2054207..27daf43 100644 --- a/vignettes/midlines.Rmd +++ b/vignettes/midlines.Rmd @@ -4,7 +4,7 @@ output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{midlines} %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} + %\VignetteEncoding{UTF-8}{inputenc} --- ```{r, include = FALSE} @@ -25,7 +25,7 @@ The goal of `midlines` is to estimate the midline of one or more polygons, takin ## Installation -You can install the package from [GitHub](https://github.com/) with: +You can install the current version of the package from [GitHub](https://github.com/) with: ``` r # install.packages("devtools") @@ -167,7 +167,7 @@ The lack of lines is due to the narrowness of the channel, or more precisely, th plot(side1) plot(bbox_line_s, add = TRUE) -m_s1 = midlines_draw(side1, dfMaxLength = units::set_units(8,"m"), border_line = bbox_line_s) +m_s1 = midlines_draw(side1, dfMaxLength = set_units(8,"m"), border_line = bbox_line_s) #plot(ml2$geometry, add = TRUE, col = "BLUE") # using the border_line to prevent removal of lines of interest @@ -301,3 +301,4 @@ plot(pols) * The very comprehensive R package [`cmgo`](https://github.com/AntoniusGolly/cmgo) does many of the same things as this package and much more. If your aim to to estimate the midline of a river, you should probably start with [`cmgo`](https://github.com/AntoniusGolly/cmgo) * [centerline](https://centerline.readthedocs.io/en/latest/) is a Python library with a similar aim + From a261db7752c9d4d453bf1e4f2682684220f92a6d Mon Sep 17 00:00:00 2001 From: RichardPatterson Date: Thu, 7 Mar 2024 17:28:31 +0000 Subject: [PATCH 2/5] Update readme plots Also a small simplification of midlines_smooth --- R/midlines_polish.R | 25 ++++++--------- README.Rmd | 24 +++++++------- README.md | 45 ++++++++++++++------------- man/figures/README-clean1-1.png | Bin 3424 -> 3413 bytes man/figures/README-device_off1-1.png | Bin 5986 -> 5978 bytes man/figures/README-draw-1.png | Bin 5986 -> 5978 bytes man/figures/README-gaps3-1.png | Bin 4298 -> 4315 bytes man/figures/README-smooth-1.png | Bin 3535 -> 3526 bytes 8 files changed, 47 insertions(+), 47 deletions(-) diff --git a/R/midlines_polish.R b/R/midlines_polish.R index 02f1393..91d5e81 100644 --- a/R/midlines_polish.R +++ b/R/midlines_polish.R @@ -62,25 +62,20 @@ midlines_smooth = function(x, width = 3){ } - nrow = nrow(dat) + smoothed = sf::st_sfc(lapply(seq_len(nrow(dat)), s)) - smoothed = sf::st_as_sf(sf::st_as_sfc(lapply(1:nrow, s))) + smoothed = sf::st_sf(geometry = + sf::st_collection_extract( + lwgeom::st_split(smoothed, + sf::st_union( + sf::st_cast(smoothed, "MULTIPOINT"))), type = "LINESTRING"), + crs = sf::st_crs(x)) - smoothed = sf::st_as_sf( - sf::st_collection_extract( - lwgeom::st_split(smoothed$x, - sf::st_union( - sf::st_cast(smoothed, "MULTIPOINT"))), type = "LINESTRING")) - - colnames(smoothed)[colnames(smoothed) == "x"] = "geometry" - sf::st_geometry(smoothed) <- "geometry" - - smoothed$line_id = 1:nrow(smoothed) - - sf::st_crs(smoothed) = sf::st_crs(dat) + smoothed$line_id = seq_len(nrow(smoothed)) return(smoothed) -} + + } diff --git a/README.Rmd b/README.Rmd index 7ebddd6..6b5d78d 100644 --- a/README.Rmd +++ b/README.Rmd @@ -50,12 +50,14 @@ library(midlines) library(sf) library(units) -plot(thames) +plot(thames$geometry) + ``` To estimate the midline of this stretch of the Thames, use the `midline_draw` function. ```{r draw} + # create a sf collection for the midline m1 = midlines_draw(thames) @@ -115,7 +117,7 @@ bbox_line = st_cast(st_as_sfc(st_bbox(c(xmin = 535070, ymin = 177800, xmax = 542 m1 = midlines_draw(thames, border_line = bbox_line) -plot(thames) +plot(thames$geometry) plot(m1$geometry, col = "BLUE", add = TRUE) plot(bbox_line, add = TRUE) ``` @@ -142,7 +144,7 @@ bbox_poly = st_as_sfc(st_bbox(c(xmin = 536070, ymin = 180700, xmax = 537800, yma # a slightly smaller area to use as a border line bbox_line_s = st_cast(st_as_sfc(st_bbox(c(xmin = 536120, ymin = 180760, xmax = 537750, ymax = 181800), crs = st_crs(thames))),"LINESTRING") -plot(thames) +plot(thames$geometry) plot(bbox_poly, add = TRUE) ``` @@ -151,7 +153,7 @@ plot(bbox_poly, add = TRUE) # crop the larger thames polygon to the area we want side1 = st_intersection(thames, bbox_poly) -plot(side1) +plot(side1$geometry) plot(bbox_line_s, add = TRUE) # estimating midline of the original polygon @@ -163,7 +165,7 @@ The lack of lines is due to the narrowness of the channel, or more precisely, th ```{r gaps3} # some trial and error revealed 8 meters to be the optimal max length between points -plot(side1) +plot(side1$geometry) plot(bbox_line_s, add = TRUE) m_s1 = midlines_draw(side1, dfMaxLength = set_units(8,"m"), border_line = bbox_line_s) @@ -174,7 +176,7 @@ m_s2 = midlines_clean(m_s1, n_removed = 20, border_line = bbox_line_s) plot(m_s2$geometry, col = c("BLUE", "RED")[m_s2$removed_flag], add = TRUE) ``` -Conversely, if the points on the perimeter are too dense and result in a very complicated tessellation or slow computations times then sf::st_line_sample might also be useful. +Conversely, if the points on the perimeter are too dense and result in a very complicated tessellation or slow computation times, then sf::st_line_sample might also be useful. ## Zig-zagging @@ -227,12 +229,12 @@ bbox_poly = st_as_sfc(st_bbox(c(xmin = 538700, ymin = 180750, xmax = 539800, yma # a slightly smaller area to use as a border line bbox_line_s = st_cast(st_as_sfc(st_bbox(c(xmin = 538750, ymin = 180800, xmax = 539750, ymax = 181800), crs = st_crs(thames))),"LINESTRING") -plot(thames) +plot(thames$geometry) plot(bbox_poly, add = TRUE) # crop the larger thames polygon to the area we want side2 = st_intersection(thames,bbox_poly) -plot(side2) +plot(side2$geometry) plot(bbox_line_s, add = TRUE) @@ -245,7 +247,7 @@ plot(ml1$geometry, add = TRUE, col = "RED") One option would be to use the dfMaxLength option with midlines_draw like we did on the left side channel. ```{r smooth} -plot(side2) +plot(side2$geometry) m_s1a = midlines_draw(side2, border_line = bbox_line_s, dfMaxLength = set_units(8,"m")) # clean this to remove extraneous lines @@ -260,7 +262,7 @@ An alternative is to smooth the midline using a rolling average - basically a wr # smooth with a rolling average m_s2b = midlines_smooth(m_s1) -plot(side2) +plot(side2$geometry) plot(m_s2b$geometry, add = TRUE, col = "BLUE") ``` @@ -283,7 +285,7 @@ plot(m2$geometry) ### De-densifying midlines -In addition to manipulating the density of points on the perimeter of the initial polygon, it is sometimes desirable to manipulate the density of points the form the midline. This results in fewer longer line segments, rather than many shorter line segments. This can be done using `midlines_dedensify` either before or after cleaning. Like with the `midlines_smooth` function, the points where lines join are not affected but between line joins the number of points can be reduced. This function calls sf::st_line_sample so see this for the relevant options. +In addition to manipulating the density of points on the perimeter of the initial polygon, it is sometimes desirable to manipulate the density of points that form the midline. This results in fewer longer line segments, rather than many shorter line segments. This can be done using `midlines_dedensify` either before or after cleaning. Like with the `midlines_smooth` function, the points where lines join are not affected but between line joins the number of points can be reduced. This function calls sf::st_line_sample so see this for the relevant options. ## Voronoi polygons diff --git a/README.md b/README.md index 5827f35..ec584c0 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,11 @@ copyright is retained by OpenStreetMap contributors. # Load libraries library(midlines) library(sf) +#> Warning: package 'sf' was built under R version 4.3.3 library(units) +#> Warning: package 'units' was built under R version 4.3.3 -plot(thames) +plot(thames$geometry) ``` @@ -53,6 +55,7 @@ To estimate the midline of this stretch of the Thames, use the `midline_draw` function. ``` r + # create a sf collection for the midline m1 = midlines_draw(thames) @@ -160,10 +163,10 @@ linestring, as in this example. bbox_line = st_cast(st_as_sfc(st_bbox(c(xmin = 535070, ymin = 177800, xmax = 542560, ymax = 181550), crs = st_crs(thames))), "LINESTRING") m1 = midlines_draw(thames, border_line = bbox_line) -#> Warning: attribute variables are assumed to be spatially constant throughout all -#> geometries +#> Warning: attribute variables are assumed to be spatially constant throughout +#> all geometries -plot(thames) +plot(thames$geometry) plot(m1$geometry, col = "BLUE", add = TRUE) plot(bbox_line, add = TRUE) ``` @@ -204,7 +207,7 @@ bbox_poly = st_as_sfc(st_bbox(c(xmin = 536070, ymin = 180700, xmax = 537800, yma # a slightly smaller area to use as a border line bbox_line_s = st_cast(st_as_sfc(st_bbox(c(xmin = 536120, ymin = 180760, xmax = 537750, ymax = 181800), crs = st_crs(thames))),"LINESTRING") -plot(thames) +plot(thames$geometry) plot(bbox_poly, add = TRUE) ``` @@ -214,13 +217,13 @@ plot(bbox_poly, add = TRUE) # crop the larger thames polygon to the area we want side1 = st_intersection(thames, bbox_poly) -plot(side1) +plot(side1$geometry) plot(bbox_line_s, add = TRUE) # estimating midline of the original polygon m_s1 = midlines_draw(side1, border_line = bbox_line_s) -#> Warning: attribute variables are assumed to be spatially constant throughout all -#> geometries +#> Warning: attribute variables are assumed to be spatially constant throughout +#> all geometries plot(m_s1$geometry, add = TRUE, col = "RED") ``` @@ -237,12 +240,12 @@ sf::st_segmentize so see that help file for more details. ``` r # some trial and error revealed 8 meters to be the optimal max length between points -plot(side1) +plot(side1$geometry) plot(bbox_line_s, add = TRUE) m_s1 = midlines_draw(side1, dfMaxLength = set_units(8,"m"), border_line = bbox_line_s) -#> Warning: attribute variables are assumed to be spatially constant throughout all -#> geometries +#> Warning: attribute variables are assumed to be spatially constant throughout +#> all geometries #plot(ml2$geometry, add = TRUE, col = "BLUE") # using the border_line to prevent removal of lines of interest @@ -253,7 +256,7 @@ plot(m_s2$geometry, col = c("BLUE", "RED")[m_s2$removed_flag], add = TRUE) Conversely, if the points on the perimeter are too dense and result in a -very complicated tessellation or slow computations times then +very complicated tessellation or slow computation times, then sf::st_line_sample might also be useful. ## Zig-zagging @@ -321,7 +324,7 @@ bbox_poly = st_as_sfc(st_bbox(c(xmin = 538700, ymin = 180750, xmax = 539800, yma # a slightly smaller area to use as a border line bbox_line_s = st_cast(st_as_sfc(st_bbox(c(xmin = 538750, ymin = 180800, xmax = 539750, ymax = 181800), crs = st_crs(thames))),"LINESTRING") -plot(thames) +plot(thames$geometry) plot(bbox_poly, add = TRUE) ``` @@ -331,15 +334,15 @@ plot(bbox_poly, add = TRUE) # crop the larger thames polygon to the area we want side2 = st_intersection(thames,bbox_poly) -plot(side2) +plot(side2$geometry) plot(bbox_line_s, add = TRUE) # estimating midline of the original polygon m_s1 = midlines_draw(side2, border_line = bbox_line_s) -#> Warning: attribute variables are assumed to be spatially constant throughout all -#> geometries +#> Warning: attribute variables are assumed to be spatially constant throughout +#> all geometries plot(ml1$geometry, add = TRUE, col = "RED") ``` @@ -349,10 +352,10 @@ One option would be to use the dfMaxLength option with midlines_draw like we did on the left side channel. ``` r -plot(side2) +plot(side2$geometry) m_s1a = midlines_draw(side2, border_line = bbox_line_s, dfMaxLength = set_units(8,"m")) -#> Warning: attribute variables are assumed to be spatially constant throughout all -#> geometries +#> Warning: attribute variables are assumed to be spatially constant throughout +#> all geometries # clean this to remove extraneous lines m_s2a = midlines_clean(m_s1a, border_line = bbox_line_s, n_removed = 5) @@ -373,7 +376,7 @@ basically a wrapper round the rollapply function in the excellent # smooth with a rolling average m_s2b = midlines_smooth(m_s1) -plot(side2) +plot(side2$geometry) plot(m_s2b$geometry, add = TRUE, col = "BLUE") ``` @@ -413,7 +416,7 @@ plot(m2$geometry) In addition to manipulating the density of points on the perimeter of the initial polygon, it is sometimes desirable to manipulate the density -of points the form the midline. This results in fewer longer line +of points that form the midline. This results in fewer longer line segments, rather than many shorter line segments. This can be done using `midlines_dedensify` either before or after cleaning. Like with the `midlines_smooth` function, the points where lines join are not affected diff --git a/man/figures/README-clean1-1.png b/man/figures/README-clean1-1.png index 9de81ccd455a5bbd26dfb03024fc0299e5aef15c..25e80673cce77f656411f04626da9924d4e3fee7 100644 GIT binary patch literal 3413 zcmeH~=|2<-7sux^nJI&cZe)-(vX0#tOxEm6mL}P^Nm(+=7M{s%Bs5QCNti)d5-Pit ztYc{yQF9rF#xk0{dwqOhr!&NpzIs zW)cp;=5yEzH$P_;;W%Zvx%+RazD_F9?iH@{dd&%?%A3>pUi}M9`|I;!Y}n^M-prbg z+*Y-{i)noWPP=Ce)u0z>hA~!c_+ed?XF|Kr-<|&XN;Qd0-DPhzPkR3>@+s`>g@J>L zOlilitJe{m6Q2ntjvda|g~VebWF85p@^V#mU%lsmmMvu@Ar)a~1SIUg^R{&g;21>q z_=V)+zbbUkQ(|qpO@4?M{vLsNYF7kJk?K*WuhA^>joW-LcCK8n`20lPSLq1EJAT8= z8A2E4e1GmhboUi`P;pu_#4Q2RkCpdRDG8|ocv!V{{fQA-+2JolDB8fDIT*28Sm4M< zB@_~*te zir8nf?srOT)(d+ZQ{MECKG0xYDOKU*bB}v@dnA+f_o@~;!G{}8C{~iM!oCtXGNL^Q z7C`upfAn&?Bv->h{K4S~!$%6-dbh`X)EbjM+R`%t$hAOyor2YGvWH*u34r;(1J{ z7PD7&zCO77E6Ls5jXtt_4L!$Cx33G)*SMxf8ICI=$3aW6=GJv}yAHgS@Zyd1CKA0RQd%cy_%VgTspMCX(*=R9JO;n9hYE|G5P(IuM7F^go zIOUf)B zh(==k1=u^TkH1~kj?=I%A~g(Yyo_?<{_*qh#SWK1=u}+PoIKx)y-D&1_bm)NZFd`F z>WN#I7D5wJXRjMQyXQ-*NtB5v7-R3o1Vwm!aE0>aP+|+O6Gw zqDIXDn>p%Inc&Ek2nTV(jl1rQF+N_;yYTQ2kJN>Qh=DjGcF4UYS*SHP@ziBkExJDDdy=GaeA zp5=~RmxeIM>0bGDiOz=x=P7R{NNWBdDW1hdv`n50D>^$R32B8#VHTkgr|VH74_XGjXG^^rw1^HR^^WB;U|eVnOa1t$ zRg-nR)Gcx-M(EtC6#?Kb?OJ_fdrsf> z*(vxkS=GqHt5EG0M89UtCB>+s&NVi}1F`(Eew0eZ8$^UkI0=ZM7nhbS#G8HGg6};% zEWSiHB#E`$K70C1q}SY%iQNHtE*=UGp8LrBk56T*q=P>2oFC=}uUK998p!c2L1wFk z32utesYtqEj$c(&(v%8ql7lTQX#!MuK5~TStv!xH}a9}%VWxG4h)Z5 zy_>bcepIM7Xsn5yD#mCZfv2#HT6l7AQ}l= zthiwhj%-+2JU0FpGKjV+<70RdZ8X!S#!&Yl0LRHgONui(HbkG-Z4<7R#40)d$j-2= z&PE&JsXidw-SF3`gv*g*S{Y*EaP9KO*zfr=tv+WA;VxL4%^Yi&Z zg_qn1)OB|5w5%Z=jzrtwL8tfHBBioV?- z?TpP8MAUYbh+aiA6dL;xEshUVo+VFQ+*fI2|~~q{jUkW73p<0S@@j|B8MNYcEvgf=vddy_tashw2AL|Ih*AXpcoax0;@N zMRWIYnY6+P+)4HA(6uyB`;QSY=H45dM+mZ7pO-7@Ntt9|a{!EQL$h1cOB0`jNQVu% zgB;R^ORD2;;GZSHw@x2)@NRmjjTZaWp9(U9+_m?>@ZR3;4&RAS$7>(>b$BhC zTNRk7&7QDs-~WYg2Vh-MMu`?hCMVHZ#_2#f5bLV&^>UH)N%R?CmNC%YZy2H literal 3424 zcmeH~`9BkmAIE1Dxkb)NKGbNmI>@4U^>Zr%m7OETpF`w09)^Z)C= zNx;R0DW5NnDRj4b&ka1cj$5{_9ps}7*Y^Y$^Ngp#h0u$Z_$oe%!^xi9l!}l8bDEqh zX5e%8$!M1MuFkB!4T*AQfEc&25a2Tq0ES%t!P1uFu>Ak}OIxL zq$@NpiMKRBN6#bCYs3Fr54aZdL;*)q=xQ>b-QM*X2-Z_c;G2`pU~gZP{(5_Yq|o*P zs=2sP{cvt;c*4YeXM>SwB9blt`^T=y74BYHKVG=x_0r4N%y7YL!x=?LjvDSh6Ra^(}?7i-IL z`5i3LSJ@gr)j*mf!Cwc{=xOZi?`SO;c_{1Tg@-%Eagbr+8%57fRWji*Z}_z-m=+^t zIS1Yupo=UQN9!J{7aXi-pV}?PW(F9Gq5?%EEUeL-S;am38&k>d50?rH;?NDuhYaUdR zE|F3rnHYO-C}%qjY;iZq=S1_%e3xUDr0*PcnYt2bZ~r_uuayR*Z@}04LS4HriJx}9 zN1I#=Shzpfxy(S$9G(PGxjD_bW#_U7=>rGHuCQzv#T5A6CK#jn6PC6~zY4MDzL5|W z!F1UJ87sEzaXso6P52kM!cu{ZDKMja8wrU@=w}GbomkXbBF;{uNpqf3ktZzD$(?iL zeNUXxuV?Q1O3S5?0uo@)&A#tW9#n4@=`ZlYk4TKyI2RCo8glp|3&7<1jNzqX1fI#(F)OJm zax&E6=ULRbCe=--K|cUmm}5B7=0CYG$qTULlSZOo2hK&swm5!lJ!dO_`K{~X5z2k0 zL~`?9SOa=#i}a$hN99K;Sz+p=V0`qiMG9nIDX3PLKdQK`*S7U$FucS(xXhtPOmK2- z8FlolNAgT`!f!hCXG?8qbVXEQAmg+)E>{kogQqRfHCP?9%CEw95F&@8y;Ky{0}u3=Bcee23`z%viHBg0H*&OA$cyiTqIeN_jKQ|d2t(m*(;M3x3G}B_TldL_dRflG z)QYaj?<~6_scVcZxJ76oOhl24DXJjN@kLJ8A6Yj9$`eC&pcr^c z$Ws{U1R3d3NxFQL=X-3){^HJ6vzCvdqZACx+brBm8G|#pg{QG9Hr1tep#5y}1}ppD zO$i%b z3P%5&tr-|(UqKg|c=YQUY(_OQ;STPLi>*WKj%&=x``w&RLbEO8Bq-^&acJ3=_G2h% z^}u`}P|&=HzO&~zxjRe~cxv)uvlDZr=W$>V{n7?K!KR=>Wwjm!%DoZ9BkS<$B{C5D zGT9vedtSD%y(cTovbsbp(6Hfv%qYnfc1BN6|KopAksO#UlmRdMtgn}$?Q2?h^MG6% z7P6hEeL|j|B>N{!^S+udDx{^cGR6_@FCk|jGv#NLKyn!K?68|!!9#|D0y0bHc#Qwk zq8?t#*&(WFOWV~oQ0w>j2>tAk+ncpZtEhmY+JbVdT=UOzKmCVM;n=a_sDYJXyU?HQ zBbg2E^v@Y?+jHS2^wS2HppBR$6Jm+J55N{au@_SWZo^4o@e+xS-NR&Uj%P`Jh@?VC-B@g1;7h kAq7m*`@jAd3A|5i_?ImEgNhSA_>KTpW_G64h->%$1HVFOr2qf` diff --git a/man/figures/README-device_off1-1.png b/man/figures/README-device_off1-1.png index a27fa792ca29ef9ae97f0c5427cac93a094e556c..90832b4a9809af53af70483c55d500339c98cd6b 100644 GIT binary patch literal 5978 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 DP>v); literal 5986 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 DP>v); literal 5986 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 literal 4298 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 TJkvz-Ph!6t&b8S2)Sv$WA+XVu diff --git a/man/figures/README-smooth-1.png b/man/figures/README-smooth-1.png index 821c2250ff7b846912ef64099374cbd633d439cb..9083bdb321822136df94f1758f1b8e2cbdead7a7 100644 GIT binary patch delta 2613 zcmV-53d;4*8^#-uP61=FPr3nrAQJjOR`W?NqHOch%%<~O?kn|0$Z;D^I-zb|co4hM zBope_hR5)m%t=E18u1K}5lzP9NFOS(h-z^j#(JA4qN1!vu`Zr{UX%AE78X%|b?$T6ETWuo z(qlh{O#@g&cW(C(Do8K>QYfB8=A(e1R|IZ+nTyU^i z5B)8Yi71O`D7srH7tud{D^_T3v3*2OAHhJhw!-BkfS~~(=93u69gD9;Xfi8|gLW9u6L4LP+5;_t+9Ojr%n%j>aj`P2lETIF@!g1K- z1JJ>N*yP8fgCmj9;b`DU1avqWI1~vTjsA`Gb0kkf$Dn;<9Y0Z`!(C|KU~KYZ(Y?_~ z=uk9oxWflZ^tc1f8;&J(BziX3+^&zR-sdr2);fpUHfp4Iv5xn(aQL`9K#! zlm+yn`OM~jO%r02T`O|gdxk>~Vhjo>)1d`1iq6xozxsNu|EhQLDW+&agpz=+?m_Yu z&e!W0Q^rFFA`}F4ozL~xcM-Lpk9`9|Tpz?A@8NXv1@{5D*B|3DpqYH`7g6&GnU^2r zDxj+`q>hc=B=m=O^+B%AGF|0!w}@KL$h!C_7XiJ0&gXj@QRg8!*B<8LAU<^ff1Cqr z=qKaSHZuUS-C& z@;JF2K9|q;F`}=`xqc6}g$L>g=y4$Kzqo8$c%0lxrptUD7EuC(+Au9UPSFlu_7TzbWzPmYtB#XfKo8;FKYsGPtd~&Fs^bh?Kv_aRSQZ^8 zd+bd>8(0<{Cl}D_d|qCe@4&I;ae9G73kak3GtKZZ}e$ZuSJ6~`IA!?VXbRvf43 z5Q%`+@vAsY*#b&H>-be1hJZ%!t2j*IF?`(-x&5hnzlx*ekGPu-;@^kV`;E)5;wS`v zG=yKpQSv9%31}m~ih~p`pw%A=ZGQ;$8<$_jK?)a80$Rzh;vfVxhF`@oEn;@`py<)C!xPX#eia93*x{QlwsRbp zU&R3kXdJ(a$Gbj~X*@<^hqo+wI072Tu;kGS_TkQe8t?FqC688cu9AQ@Gc0+uuz<3| zzddUnt7MrYpv@d>9;@Wj$U}&$0_wcOTh=^O+X6~JOF7m&6gzw@%c4iB+2Q+t?ri5d zE6bt>Dq7|UXgkZI2P(Q?vnimhmyk}zvgm>01L~bm)2fFlOHoTeSwO#dRy|D70!lzT zcvd}1;hCVGfY$yZ{cJp|9;Gs%ETHvF%O0d`hbN#NOv@gl^s^oU+Rn7>F@AzK{72Bn zPwyvWTJ{j7!5b1#*Fm|~Jwok&4qu)&7SJEAb&pW`qh)r9=?_Lt2|9#{3Gb^Rtc&kc6wvksltcJ`Pu|sc*#)$8 z0cD4;;$3~0@7C2Wp!t^n=92~VlX>|)3Ie+NT6I8+o<2YG@_RG}lzdikufM}Kpe&#< z-0SbK3uvN~)d3}+2lfqUcKoDs-^*k^31}Vr1~fZfRJ@O9>JXlMR-yx~5&_*tlm&DI zI?yU{3jXuUKN(Ffpt**Blg|WN(CQ?h@Bhnabv_Ab3N2`r4&e7=%SnLC^2vutzvw}; zRYG4UtLWFGsh8xw9}DOTJ!rS?-EkF7JW`fVE@N7w2^}^GJ&Py{XcwB$qadN}3n;Vs zKoh!b+LwxGX+Bv%Ptb)v+x{IQTAa_sd4ERpfi85~HSqQ6=Rc=^&pZ7#ETCJop;xMd zuT4(Ro|XJA1aylwbSr6LaXvZ6v_l{IUH9d}}(2CoCk?S1)haN~cy^9Xhp#>7wchT8CU_T%G252}rP zX}g4Gp6C=)mpUI{e;cx^r+b9dp~j~;+$it*74wg8{t6#tKMW#DK&2dqK}6@S?r9QX zOZ+IB=GvYp6}H3OaF4HwdOP=mDRow}rzymcMMAR-46gwc XW!B8eoQ&2N00000NkvXXu0mjfoaZJ( delta 2622 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 Date: Tue, 12 Mar 2024 17:13:34 +0000 Subject: [PATCH 3/5] Add date to description --- DESCRIPTION | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DESCRIPTION b/DESCRIPTION index 1f1ea8e..18b98ce 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,3 +26,5 @@ Suggests: rmarkdown, units VignetteBuilder: knitr +Date: 2024-03-07 + From 3148d7280b35ca8d8ce28e1b33b274391f5115d0 Mon Sep 17 00:00:00 2001 From: RichardPatterson Date: Mon, 18 Mar 2024 16:15:37 +0000 Subject: [PATCH 4/5] Update description and NEWS --- DESCRIPTION | 4 ++-- NEWS.md | 6 ++++++ R/midlines_refine.R | 2 +- README.Rmd | 10 ++++++++++ README.md | 18 ++++++++++++++++++ 5 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 NEWS.md diff --git a/DESCRIPTION b/DESCRIPTION index 18b98ce..e8a2ca6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: midlines Title: Estimate Polygon Midlines -Version: 0.0.0.9001 +Version: 0.0.0.9002 Authors@R: person(given = "Richard", family = "Patterson", @@ -26,5 +26,5 @@ Suggests: rmarkdown, units VignetteBuilder: knitr -Date: 2024-03-07 +Date: 2024-03-12 diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..e59280d --- /dev/null +++ b/NEWS.md @@ -0,0 +1,6 @@ +# midlines 0.0.0.9002 + +- removed dplyr dependency +- added igraph dependency +- replaced wonky bespoke code that grouped intersecting lines with igraph function. This effects midlines_group(), midlines_check(), midlines_dedensify(), midlines_debit(), midlines_smooth(). The new code is ~2.5x fast than the previous version. +- Vignette added diff --git a/R/midlines_refine.R b/R/midlines_refine.R index aad8f05..741016b 100644 --- a/R/midlines_refine.R +++ b/R/midlines_refine.R @@ -11,7 +11,7 @@ midlines_group = function(x) { touches = sf::st_touches(x) graph = igraph::graph_from_adj_list(touches) - groups <- igraph::components(graph)$membership + groups = igraph::components(graph)$membership grouped = stats::aggregate(x, by = list(group_id = groups), FUN = unique) diff --git a/README.Rmd b/README.Rmd index 6b5d78d..8d9c47a 100644 --- a/README.Rmd +++ b/README.Rmd @@ -298,6 +298,16 @@ plot(pols) ``` +## Another use case + +An alternative use case is to merge two versions of a road network that are measured differently and therefore do not exactly match. Use buffers to creating a polygon around both networks and then estimate the midline of this polygon to give a combined network against which each can be compared. See this [blog](https://richardpatterson.github.io/2023/08/06/network_merge.html) post for more details. + +## Citation + +If you use this package in your work it would be helpful if you would cite it. A suggested citation is: + +_Patterson, R. midlines: Estimate Polygon Midlines. R package version 0.0.0.9002. DOI: 10.17863/CAM.100113_ + ## Alternatives * The very comprehensive R package [`cmgo`](https://github.com/AntoniusGolly/cmgo) does many of the same things as this package and much more. If your aim to to estimate the midline of a river, you should probably start with [`cmgo`](https://github.com/AntoniusGolly/cmgo) diff --git a/README.md b/README.md index ec584c0..6f56460 100644 --- a/README.md +++ b/README.md @@ -436,6 +436,24 @@ perimeter of the river Thames polygon as the starting point. +## Another use case + +An alternative use case is to merge two versions of a road network that +are measured differently and therefore do not exactly match. Use buffers +to creating a polygon around both networks and then estimate the midline +of this polygon to give a combined network against which each can be +compared. See this +[blog](https://richardpatterson.github.io/2023/08/06/network_merge.html) +post for more details. + +## Citation + +If you use this package in your work it would be helpful if you would +cite it. A suggested citation is: + +*Patterson, R. midlines: Estimate Polygon Midlines. R package version +0.0.0.9002. DOI: 10.17863/CAM.100113* + ## Alternatives - The very comprehensive R package From 987019d7890327bd0e05fbe6b83c6ceacfa0b6b6 Mon Sep 17 00:00:00 2001 From: RichardPatterson Date: Mon, 18 Mar 2024 16:15:37 +0000 Subject: [PATCH 5/5] Update description and NEWS --- DESCRIPTION | 4 ++-- NEWS.md | 6 ++++++ R/midlines_refine.R | 11 +---------- README.Rmd | 10 ++++++++++ README.md | 18 ++++++++++++++++++ 5 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 NEWS.md diff --git a/DESCRIPTION b/DESCRIPTION index 18b98ce..e8a2ca6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: midlines Title: Estimate Polygon Midlines -Version: 0.0.0.9001 +Version: 0.0.0.9002 Authors@R: person(given = "Richard", family = "Patterson", @@ -26,5 +26,5 @@ Suggests: rmarkdown, units VignetteBuilder: knitr -Date: 2024-03-07 +Date: 2024-03-12 diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..e59280d --- /dev/null +++ b/NEWS.md @@ -0,0 +1,6 @@ +# midlines 0.0.0.9002 + +- removed dplyr dependency +- added igraph dependency +- replaced wonky bespoke code that grouped intersecting lines with igraph function. This effects midlines_group(), midlines_check(), midlines_dedensify(), midlines_debit(), midlines_smooth(). The new code is ~2.5x fast than the previous version. +- Vignette added diff --git a/R/midlines_refine.R b/R/midlines_refine.R index aad8f05..637886d 100644 --- a/R/midlines_refine.R +++ b/R/midlines_refine.R @@ -11,7 +11,7 @@ midlines_group = function(x) { touches = sf::st_touches(x) graph = igraph::graph_from_adj_list(touches) - groups <- igraph::components(graph)$membership + groups = igraph::components(graph)$membership grouped = stats::aggregate(x, by = list(group_id = groups), FUN = unique) @@ -24,15 +24,6 @@ midlines_group = function(x) { return(grouped) } -# This seems to be okay. It returns a df rather than a tibble, but that's fine because that's what it it passed. -# d = midlines_group(removed) -# e = midlines::midlines_group(removed) -# d -# e -# class(d) -# class(e) -# all.equal(d,e) -# class(removed) diff --git a/README.Rmd b/README.Rmd index 6b5d78d..8d9c47a 100644 --- a/README.Rmd +++ b/README.Rmd @@ -298,6 +298,16 @@ plot(pols) ``` +## Another use case + +An alternative use case is to merge two versions of a road network that are measured differently and therefore do not exactly match. Use buffers to creating a polygon around both networks and then estimate the midline of this polygon to give a combined network against which each can be compared. See this [blog](https://richardpatterson.github.io/2023/08/06/network_merge.html) post for more details. + +## Citation + +If you use this package in your work it would be helpful if you would cite it. A suggested citation is: + +_Patterson, R. midlines: Estimate Polygon Midlines. R package version 0.0.0.9002. DOI: 10.17863/CAM.100113_ + ## Alternatives * The very comprehensive R package [`cmgo`](https://github.com/AntoniusGolly/cmgo) does many of the same things as this package and much more. If your aim to to estimate the midline of a river, you should probably start with [`cmgo`](https://github.com/AntoniusGolly/cmgo) diff --git a/README.md b/README.md index ec584c0..6f56460 100644 --- a/README.md +++ b/README.md @@ -436,6 +436,24 @@ perimeter of the river Thames polygon as the starting point. +## Another use case + +An alternative use case is to merge two versions of a road network that +are measured differently and therefore do not exactly match. Use buffers +to creating a polygon around both networks and then estimate the midline +of this polygon to give a combined network against which each can be +compared. See this +[blog](https://richardpatterson.github.io/2023/08/06/network_merge.html) +post for more details. + +## Citation + +If you use this package in your work it would be helpful if you would +cite it. A suggested citation is: + +*Patterson, R. midlines: Estimate Polygon Midlines. R package version +0.0.0.9002. DOI: 10.17863/CAM.100113* + ## Alternatives - The very comprehensive R package