{"action":"created","issue":{"url":"https://api.github.com/repos/tidyverse/ggplot2/issues/2761","repository_url":"https://api.github.com/repos/tidyverse/ggplot2","labels_url":"https://api.github.com/repos/tidyverse/ggplot2/issues/2761/labels{/name}","comments_url":"https://api.github.com/repos/tidyverse/ggplot2/issues/2761/comments","events_url":"https://api.github.com/repos/tidyverse/ggplot2/issues/2761/events","html_url":"https://github.com/tidyverse/ggplot2/pull/2761","id":341713297,"node_id":"MDExOlB1bGxSZXF1ZXN0MjAxNzg2Njg5","number":2761,"title":"Implement geom_sf_label() and geom_sf_text()","user":{"login":"yutannihilation","id":1978793,"node_id":"MDQ6VXNlcjE5Nzg3OTM=","avatar_url":"https://avatars3.githubusercontent.com/u/1978793?v=4","gravatar_id":"","url":"https://api.github.com/users/yutannihilation","html_url":"https://github.com/yutannihilation","followers_url":"https://api.github.com/users/yutannihilation/followers","following_url":"https://api.github.com/users/yutannihilation/following{/other_user}","gists_url":"https://api.github.com/users/yutannihilation/gists{/gist_id}","starred_url":"https://api.github.com/users/yutannihilation/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yutannihilation/subscriptions","organizations_url":"https://api.github.com/users/yutannihilation/orgs","repos_url":"https://api.github.com/users/yutannihilation/repos","events_url":"https://api.github.com/users/yutannihilation/events{/privacy}","received_events_url":"https://api.github.com/users/yutannihilation/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":7,"created_at":"2018-07-16T23:27:15Z","updated_at":"2018-08-12T09:11:39Z","closed_at":null,"author_association":"CONTRIBUTOR","pull_request":{"url":"https://api.github.com/repos/tidyverse/ggplot2/pulls/2761","html_url":"https://github.com/tidyverse/ggplot2/pull/2761","diff_url":"https://github.com/tidyverse/ggplot2/pull/2761.diff","patch_url":"https://github.com/tidyverse/ggplot2/pull/2761.patch"},"body":"This PR closes #2742.\r\n\r\nThough this is still a work in progress, I create this PR, hoping that someone may stop me when I go wrong... Any advices are appreciated.\r\n\r\n## Background\r\n\r\nIt's very convenient that `geom_sf()` automatically chooses the proper geometries (point, line or polygon) based on the type of the simple feature in the data. But, we sometimes want to choose different type of geoms, for example, texts and labels.\r\n\r\nHowever, adding flexibility to `geom_sf()` (or underlying `sf::st_as_grob()`) leads to unmaintainable complexity as discussed in #2111. So, we need to implement it as separate geoms/stats for code simplicity. Here, I propose `geom_sf_label()` and `geom_sf_text()`, and their corresponding stat `stat_sf_coordinates()`.\r\n\r\n## Design\r\n\r\n### `StatSfCoordinates`\r\n\r\nTexts and labels need pairs of `x` and `y`. `StatSfCoordinates` is a stat to provide these via computed variable `X` and `Y` (capital letters). `stat_sf_coordinates()` is the `stat_` function corresponding to this.\r\n\r\n`X` and `Y` dimensions of  a `sf` object can be retrieved by `sf::st_coordinates()`. But, we cannot simply use `sf::st_coordinates()` texts and labels require exactly one coordinate per geometry whereas it returns multiple locations for a polygon or a line. Instead, these two steps are needed:\r\n\r\n1. Choose one point per geometry by some function like `sf::st_centroid()` and `sf::st_point_on_surface()`.\r\n2. Retrieve coordinates from the points by `sf::st_coordinates()`.\r\n\r\nFor the first step, users can pass arbitrary function via parameter `fun.geometry`. By default, `sf::st_point_on_surface()` is used; I think this is more appropriate than `sf::st_centroid()` since lables and texts usually are intended to be put within the polygon or the line.\r\n\r\n### `geom_sf_label()` and `geom_sf_text()`\r\n\r\n`geom_sf_label()` and `geom_sf_text()` are basically same with `geom_label()` and `geom_text()` for each, except that:\r\n\r\n* they uses `StatSfCoordinates` for `stat`\r\n* they set `geometry` aesthetic automatically just as `geom_sf()` does\r\n* they accept `fun.geometry` argument\r\n\r\n## Considerations\r\n\r\n### `Z` and `M` dimension\r\n\r\nSimple feature objects may have `Z` and/or `M` dimension in addition to `X` and `Y`. These dimensions may be useful for plotting. But,\r\n\r\n* `sf::st_point_on_surface()` and `sf::st_centroid()` fails if the geometry has `Z` or `M` dimension\r\n* `sf::st_centroid()` ignores `Z` dimension, but fails for `M` dimension as well.\r\n\r\nThe user may need to remove these dimensions by `st_dm()` either beforehand or in a custom `fun.geometry`.\r\n\r\n### Warning for longitude/latitude data\r\n\r\nFunctions that does calculation based on the distances may warn if the on data is in unprojected coordinate system. Though the warnings should not be suppressed, we need to be careful to use `geom_sf_label()` in examples. Otherwise, the tests fail.\r\n\r\n\r\n## TODOs\r\n\r\n- [x] Add tests\r\n- [x] Write more docs about `stat_sf_coordinates()`\r\n- [x] Write more docs about argument `fun.geometry`\r\n    - [x] Write some notes about warnings for longitude/latitude data\r\n- [x] Do we really support `Z` and `M` dimension? (yes)\r\n\r\n## Usages\r\n\r\n``` r\r\nlibrary(ggplot2)\r\n\r\nnc <- sf::st_read(system.file(\"shape/nc.shp\", package = \"sf\"), quiet = TRUE)\r\nstorms_xyz <- sf::st_read(system.file(\"shape/storms_xyz.shp\", package = \"sf\"), quiet = TRUE)\r\n\r\n# texts and labels\r\np <- ggplot(nc[1:3, ]) +\r\n  geom_sf(aes(fill = AREA))\r\n\r\np + geom_sf_text(aes(label = NAME), colour = \"white\")\r\n#> Warning in st_point_on_surface.sfc(data$geometry): st_point_on_surface may\r\n#> not give correct results for longitude/latitude data\r\n```\r\n\r\n![](https://i.imgur.com/YjasEnf.png)\r\n\r\n``` r\r\np + geom_sf_label(aes(label = NAME))\r\n#> Warning in st_point_on_surface.sfc(data$geometry): st_point_on_surface may\r\n#> not give correct results for longitude/latitude data\r\n```\r\n\r\n![](https://i.imgur.com/g6u2JuN.png)\r\n\r\n``` r\r\n\r\n# using the stat\r\nggplot(storms_xyz) +\r\n  stat_sf_coordinates()\r\n```\r\n\r\n![](https://i.imgur.com/3Ze12Tj.png)\r\n\r\n``` r\r\n\r\n# mapping Z dimension\r\nggplot(storms_xyz) +\r\n  stat_sf_coordinates(aes(colour = stat(Z)))\r\n```\r\n\r\n![](https://i.imgur.com/heTc7mA.png)\r\n\r\nCreated on 2018-07-17 by the [reprex package](http://reprex.tidyverse.org) (v0.2.0).\r\n"},"comment":{"url":"https://api.github.com/repos/tidyverse/ggplot2/issues/comments/412329409","html_url":"https://github.com/tidyverse/ggplot2/pull/2761#issuecomment-412329409","issue_url":"https://api.github.com/repos/tidyverse/ggplot2/issues/2761","id":412329409,"node_id":"MDEyOklzc3VlQ29tbWVudDQxMjMyOTQwOQ==","user":{"login":"yutannihilation","id":1978793,"node_id":"MDQ6VXNlcjE5Nzg3OTM=","avatar_url":"https://avatars3.githubusercontent.com/u/1978793?v=4","gravatar_id":"","url":"https://api.github.com/users/yutannihilation","html_url":"https://github.com/yutannihilation","followers_url":"https://api.github.com/users/yutannihilation/followers","following_url":"https://api.github.com/users/yutannihilation/following{/other_user}","gists_url":"https://api.github.com/users/yutannihilation/gists{/gist_id}","starred_url":"https://api.github.com/users/yutannihilation/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/yutannihilation/subscriptions","organizations_url":"https://api.github.com/users/yutannihilation/orgs","repos_url":"https://api.github.com/users/yutannihilation/repos","events_url":"https://api.github.com/users/yutannihilation/events{/privacy}","received_events_url":"https://api.github.com/users/yutannihilation/received_events","type":"User","site_admin":false},"created_at":"2018-08-12T09:11:39Z","updated_at":"2018-08-12T09:11:39Z","author_association":"CONTRIBUTOR","body":"(Disclaimer: I'm not a GIS expert but a guy who just wants to add labels on `geom_sf()`. So, I might be wrong, especially about technical phrasing...)\r\n\r\n> 1. Have you considered using lower case for x, y, z, m? That seems more in line with typical ggplot2 naming.\r\n\r\nIn sf package (and probably in simple feature access), they are always written as capital. So, I felt it's more natural to use capital letters for computed variables so that we can distinguish them from ggplot's coordinates. That said, I'm still wondering whether they are essentially the same thing, `X` and `Y` dimensions of simple feature access, and `x` and `y` axis of ggplot2...\r\n\r\n> 2. Have you considered returning NAs for z and m when those values are not available? Would that make things overall more predictable?\r\n\r\nThanks, this sounds nice.\r\n\r\n> 3. I don't fully understand the comment about errors when z and m are not available. Does my point 2 address this or is there a different type of error that can occur?\r\n\r\nDifferent. As described above, `StatSfCoordinates` takes these two steps and the error occurs in the first step:\r\n\r\n1. Choose one point per geometry by some function like `sf::st_centroid()` and `sf::st_point_on_surface()`.\r\n1. Retrieve coordinates from the points by `sf::st_coordinates()`.\r\n\r\nYour suggestion is about when the first step succeeds but doesn't return `Z` and `M` value, but what I was thinking is when the calculation itself fails.\r\n\r\nFor example, consider we have a `sf` object of single `POLYGON` feature and want to determine a location to plot a label:\r\n\r\n``` r\r\nlibrary(ggplot2)\r\nlibrary(sf)\r\n#> Linking to GEOS 3.6.1, GDAL 2.2.3, proj.4 4.9.3\r\n\r\nmat_poly_xy <- rbind(\r\n  c(1, 1), c(-1, 1), c(-1, -1), c(1, -1), c(1, 1)\r\n)\r\npoly_xy <- st_polygon(list(mat_poly_xy))\r\npoly_xy\r\n#> POLYGON ((1 1, -1 1, -1 -1, 1 -1, 1 1))\r\n```\r\n\r\nIn this case, we can choose a point on the polygon by `st_point_on_surface()` and extract the coordinates by `st_coordinates()`.\r\n\r\n``` r\r\nd_poly_xy <- st_sf(geometry = st_sfc(poly_xy))\r\n\r\nd_pt_xy <- st_point_on_surface(d_poly_xy)\r\nxy <- st_coordinates(d_pt_xy)\r\n```\r\n\r\nSo we can combine the coordinates with the original data\r\n\r\n``` r\r\ncbind(d_pt_xy, xy)\r\n#> Simple feature collection with 1 feature and 2 fields\r\n#> geometry type:  POINT\r\n#> dimension:      XY\r\n#> bbox:           xmin: 0 ymin: 0 xmax: 0 ymax: 0\r\n#> epsg (SRID):    NA\r\n#> proj4string:    NA\r\n#>   X Y    geometry\r\n#> 1 0 0 POINT (0 0)\r\n```\r\n\r\nBut, if the polygon has `M` dimension, the function fails.\r\n\r\n``` r\r\nmat_poly_xym <- cbind(mat_poly_xy, 1)\r\n\r\npoly_xym <- st_polygon(list(mat_poly_xym), dim = \"XYM\")\r\n\r\npoly_xym\r\n#> POLYGON M ((1 1 1, -1 1 1, -1 -1 1, 1 -1 1, 1 1 1))\r\n\r\nd_poly_xym <- st_sf(geometry = st_sfc(poly_xym))\r\n\r\nst_point_on_surface(d_poly_xym)\r\n#> Error in CPL_geos_op(\"point_on_surface\", x, numeric(0), integer(0), numeric(0), : GEOS does not support XYM or XYZM geometries; use st_zm() to drop M\r\n```\r\n"}}
{"actor":{"display_login":"yutannihilation"}}