├── .github ├── .gitignore └── workflows │ ├── pkgdown.yaml │ └── R-CMD-check.yaml ├── vignettes ├── .gitignore ├── README-unnamed-chunk-10-1.png ├── README-unnamed-chunk-11-1.png ├── README-unnamed-chunk-12-1.png ├── README-unnamed-chunk-13-1.png ├── README-unnamed-chunk-2-1.png ├── README-unnamed-chunk-3-1.png ├── README-unnamed-chunk-4-1.png ├── README-unnamed-chunk-5-1.png ├── README-unnamed-chunk-6-1.png ├── README-unnamed-chunk-7-1.png ├── README-unnamed-chunk-8-1.png ├── README-unnamed-chunk-9-1.png └── graticule.Rmd ├── .gitignore ├── tests ├── testthat.R └── testthat │ ├── test-tiles.R │ ├── test-no-longitude-warnings.R │ └── testgraticule.R ├── man ├── figures │ ├── README-lonlat-1.png │ ├── README-lonlat-2.png │ ├── README-lonlat-3.png │ ├── README-wedge-1.png │ ├── README-example-1.png │ └── README-rhumb-points-1.png ├── graticule_labels.Rd ├── graticule.Rd └── lonlat.Rd ├── inst └── extdata │ ├── nt_20140320_f17_v01_s.bin │ └── getice.R ├── R ├── utils.R ├── zzz.R ├── pathos.R ├── lonlat.R ├── graticule2.R └── graticule.R ├── cran-comments.md ├── .Rbuildignore ├── graticule.Rproj ├── NAMESPACE ├── DESCRIPTION ├── CODE_OF_CONDUCT.md ├── NEWS.md ├── README.Rmd └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | inst/doc 5 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | test_check("graticule") 3 | -------------------------------------------------------------------------------- /man/figures/README-lonlat-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/man/figures/README-lonlat-1.png -------------------------------------------------------------------------------- /man/figures/README-lonlat-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/man/figures/README-lonlat-2.png -------------------------------------------------------------------------------- /man/figures/README-lonlat-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/man/figures/README-lonlat-3.png -------------------------------------------------------------------------------- /man/figures/README-wedge-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/man/figures/README-wedge-1.png -------------------------------------------------------------------------------- /man/figures/README-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/man/figures/README-example-1.png -------------------------------------------------------------------------------- /inst/extdata/nt_20140320_f17_v01_s.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/inst/extdata/nt_20140320_f17_v01_s.bin -------------------------------------------------------------------------------- /man/figures/README-rhumb-points-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/man/figures/README-rhumb-points-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-10-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-10-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-11-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-11-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-12-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-13-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-13-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-2-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-3-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-4-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-5-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-6-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-6-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-7-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-7-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-8-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-8-1.png -------------------------------------------------------------------------------- /vignettes/README-unnamed-chunk-9-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hypertidy/graticule/HEAD/vignettes/README-unnamed-chunk-9-1.png -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' @importFrom raster xmin xmax 2 | xrange <- function(x) { 3 | c(xmin(x), xmax(x)) 4 | } 5 | #' @importFrom raster ymin ymax 6 | yrange <- function(x) { 7 | c(ymin(x), ymax(x)) 8 | } 9 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | 2 | .onLoad <- function(libname, pkgname) { 3 | op <- options() 4 | op.graticule <- list( 5 | graticule.mindist = 5e4 6 | ) 7 | toset <- !(names(op.graticule) %in% names(op)) 8 | if (any(toset)) options(op.graticule[toset]) 9 | 10 | invisible() 11 | } 12 | -------------------------------------------------------------------------------- /inst/extdata/getice.R: -------------------------------------------------------------------------------- 1 | icefile <- "ftp://sidads.colorado.edu/pub/DATASETS/nsidc0051_gsfc_nasateam_seaice/final-gsfc/south/daily/2014/nt_20140320_f17_v01_s.bin" 2 | tfile <- file.path("inst", "extdata", basename(icefile)) 3 | if (!file.exists(tfile)) download.file(icefile, tfile, mode = "wb") 4 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## graticule 0.3.0 2 | 3 | Fixed failing tests. 4 | 5 | Thank you! 6 | 7 | ## Test environments 8 | 9 | * win-builder (release and devel) 10 | * Linux local release, github actions 11 | * macbuilder 12 | 13 | ## R CMD check results 14 | 15 | There were no ERRORs or WARNINGs or NOTEs. 16 | 17 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ./*.png 4 | ./README.Rmd 5 | .gitignore 6 | cran-comments.md 7 | vignettes/*.png 8 | ^\.travis\.yml$ 9 | ^README\.Rmd$ 10 | ^README-.*\.png$ 11 | docs/* 12 | revdep/* 13 | ^CODE_OF_CONDUCT\.md$ 14 | ^appveyor\.yml$ 15 | ^codecov\.yml$ 16 | ^CRAN-RELEASE$ 17 | ^\.github$ 18 | -------------------------------------------------------------------------------- /tests/testthat/test-tiles.R: -------------------------------------------------------------------------------- 1 | test_that("tiles is sensible ", { 2 | g <- graticule::graticule(seq(-180, 180, by = 15), lat = c(-45, -43), tiles = TRUE) 3 | expect_true(nrow(g) == 24) 4 | expect_equivalent(extent(g), extent(-180, 180, -45, -43)) 5 | 6 | expect_error(graticule(89, tiles = TRUE)) 7 | expect_error(graticule(c(10, 11), 5, tiles = TRUE)) 8 | }) 9 | -------------------------------------------------------------------------------- /tests/testthat/test-no-longitude-warnings.R: -------------------------------------------------------------------------------- 1 | lon <- seq(170, 350, by = 10) 2 | lat <- c(-50, -40) 3 | 4 | test_that("no longitude warnings", { 5 | expect_s4_class(graticule(lon, lat), "SpatialLinesDataFrame") 6 | 7 | expect_s4_class(graticule(lon, lat, tiles = TRUE), "SpatialPolygonsDataFrame") 8 | 9 | expect_s4_class(graticule_labels(lon, lat), "SpatialPointsDataFrame") 10 | }) 11 | -------------------------------------------------------------------------------- /graticule.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace,vignette 22 | -------------------------------------------------------------------------------- /tests/testthat/testgraticule.R: -------------------------------------------------------------------------------- 1 | context("Basic function") 2 | 3 | test_that("graticule creation is successful", { 4 | expect_that(graticule(seq(100, 240, by = 15), seq(-85, -30, by = 15)), is_a("SpatialLinesDataFrame")) 5 | expect_that(graticule(seq(100, 240, by = 15), seq(-85, -30, by = 15), nverts = 20), is_a("SpatialLinesDataFrame")) 6 | expect_that(graticule(seq(100, 240, by = 15), seq(-85, -30, by = 15), nverts = 100, xlim = c(-180, 180), ylim = c(-60, -30)), is_a("SpatialLinesDataFrame")) 7 | 8 | }) 9 | 10 | test_that("labels work", { 11 | expect_that(graticule_labels(seq(100, 240, by = 15), seq(-85, -30, by = 15)), is_a("SpatialPointsDataFrame")) 12 | }) 13 | 14 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(graticule) 4 | export(graticule_labels) 5 | export(lonlat) 6 | importFrom(graphics,contour) 7 | importFrom(raster,"values<-") 8 | importFrom(raster,extent) 9 | importFrom(raster,isLonLat) 10 | importFrom(raster,ncell) 11 | importFrom(raster,raster) 12 | importFrom(raster,res) 13 | importFrom(raster,xmax) 14 | importFrom(raster,xmin) 15 | importFrom(raster,ymax) 16 | importFrom(raster,ymin) 17 | importFrom(reproj,reproj_xy) 18 | importFrom(sp,"coordinates<-") 19 | importFrom(sp,"proj4string<-") 20 | importFrom(sp,CRS) 21 | importFrom(sp,Line) 22 | importFrom(sp,Lines) 23 | importFrom(sp,SpatialLines) 24 | importFrom(sp,SpatialLinesDataFrame) 25 | importFrom(sp,degreeLabelsEW) 26 | importFrom(sp,degreeLabelsNS) 27 | importFrom(stats,approx) 28 | importFrom(utils,head) 29 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: graticule 2 | Type: Package 3 | Title: Meridional and Parallel Lines for Maps 4 | Version: 0.3.0 5 | Authors@R: person("Michael D.","Sumner", role = c("aut", "cre"), email = 6 | "mdsumner@gmail.com") 7 | Description: Create graticule lines and labels for maps. Control the creation 8 | of lines or tiles by setting their placement (at particular meridians and parallels) 9 | and extent (along parallels and meridians). Labels are created independently of 10 | lines. 11 | License: GPL-3 12 | Depends: 13 | sp 14 | Imports: 15 | raster, 16 | utils, 17 | geosphere, 18 | stats, 19 | reproj (>= 0.4.3) 20 | Suggests: 21 | sessioninfo, 22 | knitr, 23 | spex, 24 | testthat (>= 2.1.0), 25 | rmarkdown, 26 | covr 27 | VignetteBuilder: knitr 28 | Encoding: UTF-8 29 | BugReports: https://github.com/hypertidy/graticule/issues 30 | URL: https://github.com/hypertidy/graticule, https://hypertidy.github.io/graticule/ 31 | RoxygenNote: 7.2.3 32 | -------------------------------------------------------------------------------- /man/graticule_labels.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graticule.R 3 | \name{graticule_labels} 4 | \alias{graticule_labels} 5 | \title{Create graticule labels.} 6 | \usage{ 7 | graticule_labels(lons, lats, xline, yline, proj = NULL) 8 | } 9 | \arguments{ 10 | \item{lons}{longitudes for meridional labels} 11 | 12 | \item{lats}{latitudes for parallel labels} 13 | 14 | \item{xline}{meridian/s for placement of parallel labels} 15 | 16 | \item{yline}{parallel/s for placement of meridian labels} 17 | 18 | \item{proj}{optional proj.4 string for output object} 19 | } 20 | \value{ 21 | SpatialPoints object with labels for downstream use 22 | } 23 | \description{ 24 | Returns a set of points with labels, for plotting in conjuction with \code{\link{graticule}}. 25 | } 26 | \details{ 27 | SpatialPoints are returned in the projection of \code{proj} if given, or longlat / WGS84. 28 | } 29 | \examples{ 30 | xx <- c(100, 120, 160, 180) 31 | yy <- c(-80,-70,-60, -50,-45, -30) 32 | prj <- "+proj=lcc +lon_0=150 +lat_0=-80 +lat_1=-85 +lat_2=-75 +ellps=WGS84" 33 | plot(graticule(lons = xx, lats = yy, proj = prj)) 34 | labs <- graticule_labels(lons = xx, lats = yy, xline = 100, yline = -80, proj = prj) 35 | op <- par(xpd = NA) 36 | text(labs, lab = parse(text = labs$lab), pos = c(2, 1)[labs$islon + 1], adj = 1.2) 37 | par(op) 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - uses: r-lib/actions/setup-pandoc@v2 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::pkgdown, local::. 34 | needs: website 35 | 36 | - name: Build site 37 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 38 | shell: Rscript {0} 39 | 40 | - name: Deploy to GitHub pages 🚀 41 | if: github.event_name != 'pull_request' 42 | uses: JamesIves/github-pages-deploy-action@v4.4.1 43 | with: 44 | clean: false 45 | branch: gh-pages 46 | folder: docs 47 | -------------------------------------------------------------------------------- /R/pathos.R: -------------------------------------------------------------------------------- 1 | 2 | # Create a mesh of evenly spaced lines in another projection. 3 | # 4 | # @param x object to build line mesh for 5 | # @param proj the other projection 6 | # 7 | # @return spatial object 8 | # @export 9 | # 10 | # @examples 11 | # \dontrun{ 12 | # library(maptools) 13 | # data(wrld_simpl) 14 | # library(raster) 15 | # w <- subset(wrld_simpl, NAME == "Australia") 16 | # plot(w) 17 | # laea <- pathologicule(w, "+proj=laea +lon_0=147 +lat_0=-42 +ellps=WGS84") 18 | # stere <- pathologicule(w, "+proj=stere +lon_0=147 +lat_0=-42 +ellps=WGS84") 19 | # plot(laea, add = TRUE, col = "dodgerblue") 20 | # plot(stere, add = TRUE, col = "firebrick") 21 | # 22 | # stere <- "+proj=stere +lat_0=-90 +ellps=WGS84" 23 | # p <- spTransform(subset(wrld_simpl, coordinates(wrld_simpl)[,2] < -20), stere) 24 | # plot(extent(p) + 1e6, asp = 1, type = "n"); plot(p, add = TRUE) 25 | # laea <- pathologicule(p, "+proj=laea +lon_0=147 +lat_0=-72 +ellps=WGS84") 26 | # stere <- pathologicule(p, "+proj=stere +lon_0=147 +lat_0=-42 +ellps=WGS84") 27 | # plot(laea, add = TRUE, col = "dodgerblue") 28 | # plot(stere, add = TRUE, col = "firebrick") 29 | # } 30 | # gridicule <- function(x, proj) { 31 | # y <- spTransform(x, proj) 32 | # xr <- xrange(y) 33 | # yr <- yrange(y) 34 | # xs <- seq(xr[1], xr[2], length = 15) 35 | # ys <- seq(yr[1], yr[2], length = 15) 36 | # g <- graticule(xs, ys) 37 | # projection(g) <- proj 38 | # spTransform(g, projection(x)) 39 | # } 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who 4 | contribute through reporting issues, posting feature requests, updating documentation, 5 | submitting pull requests or patches, and other activities. 6 | 7 | We are committed to making participation in this project a harassment-free experience for 8 | everyone, regardless of level of experience, gender, gender identity and expression, 9 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 10 | 11 | Examples of unacceptable behavior by participants include the use of sexual language or 12 | imagery, derogatory comments or personal attacks, trolling, public or private harassment, 13 | insults, or other unprofessional conduct. 14 | 15 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 16 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 17 | Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed 18 | from the project team. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 21 | opening an issue or contacting one or more of the project maintainers. 22 | 23 | This Code of Conduct is adapted from the Contributor Covenant 24 | (https://www.contributor-covenant.org), version 1.0.0, available at 25 | https://contributor-covenant.org/version/1/0/0/. 26 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: any::rcmdcheck 45 | needs: check 46 | 47 | - uses: r-lib/actions/check-r-package@v2 48 | with: 49 | upload-snapshots: true 50 | -------------------------------------------------------------------------------- /man/graticule.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/graticule.R 3 | \docType{package} 4 | \name{graticule} 5 | \alias{graticule} 6 | \title{graticule: graticule lines for maps} 7 | \usage{ 8 | graticule(lons, lats, nverts = NULL, xlim, ylim, proj = NULL, tiles = FALSE) 9 | } 10 | \arguments{ 11 | \item{lons}{longitudes for meridional lines} 12 | 13 | \item{lats}{latitudes for parallel lines} 14 | 15 | \item{nverts}{number of discrete vertices for each segment} 16 | 17 | \item{xlim}{maximum range of parallel lines} 18 | 19 | \item{ylim}{maximum range of meridional lines} 20 | 21 | \item{proj}{optional proj.4 string for output object} 22 | 23 | \item{tiles}{if \code{TRUE} return polygons as output} 24 | } 25 | \value{ 26 | SpatialLines or SpatialPolygons object 27 | } 28 | \description{ 29 | Specify the creation of lines along meridians by specifying their placement 30 | at particular \code{lons} (longitudes) and \code{lats} (latitudes) and their extents 31 | with \code{xlim} (extent of parallel line in longitude) and \code{ylim} (extent of meridional line in latitude). 32 | } 33 | \details{ 34 | Provide a valid PROJ.4 string to return the graticule lines in this projection. If this is not specified the graticule 35 | lines are returned in their original longlat / WGS84. 36 | All segments are discretized as _rhumb_lines_ at `getOption("graticule.mindist")` metres, which 37 | defaults to `5e4`. 38 | The arguments \code{xlim}, \code{ylim} and \code{nverts} are ignored if \code{tiles} is \code{TRUE}. 39 | } 40 | \examples{ 41 | graticule() 42 | } 43 | -------------------------------------------------------------------------------- /man/lonlat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/lonlat.R 3 | \name{lonlat} 4 | \alias{lonlat} 5 | \title{Add longitude latitude lines to a plot} 6 | \usage{ 7 | lonlat( 8 | x, 9 | na.rm = FALSE, 10 | lon = FALSE, 11 | lat = FALSE, 12 | ..., 13 | plot = TRUE, 14 | add = TRUE 15 | ) 16 | } 17 | \arguments{ 18 | \item{x}{input raster} 19 | 20 | \item{na.rm}{logical, remove missing values from generated coordinates} 21 | 22 | \item{lon}{if TRUE, only longitude plotted} 23 | 24 | \item{lat}{if TRUE (and `lon = FALSE`) only latitude plotted} 25 | 26 | \item{...}{passed to [graphics::contour()]} 27 | 28 | \item{plot}{logical, plot the result} 29 | 30 | \item{add}{logical, add to current plot or instantiate one} 31 | } 32 | \value{ 33 | RasterBrick of the longitude and latitude values, two layers 34 | 35 | (invisibly) the raster (RasterBrick) object with longitude and latitude values of the input 36 | as two layers, otherwise this function used for side-effect (drawing on a plot) 37 | } 38 | \description{ 39 | Use the coordinates of the input raster to generate coordinate rasters, these are 40 | then used in a contour plot. 41 | } 42 | \details{ 43 | Plot is added to an existing plot by default. 44 | } 45 | \examples{ 46 | plot(c(-180, 180), c(-90, 90)) 47 | lonlat(raster::raster()) 48 | 49 | p <- raster::projectExtent(raster::raster(), "+proj=igh") 50 | lonlat(p, add = FALSE) 51 | lonlat(p, levels = seq(-180, 180, by =15), add = FALSE) 52 | 53 | lonlat(p, levels = seq(-180, 180, by = 5), add = FALSE, lon = TRUE) 54 | lonlat(p, levels = seq(-180, 180, by = 15), add = TRUE, lat = TRUE) 55 | } 56 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # graticule 0.3.0 2 | 3 | * Remove checks for tests upset by package startup messages. 4 | 5 | # graticule 0.2.0 6 | 7 | * Now return values of functions are documented. 8 | 9 | * Now import the reproj package for the coordinate transformation support. 10 | 11 | * Removed function `pathologicule()`, no one will miss it. Might reappear as {gridicule} when we have 12 | better PROJ support. 13 | 14 | * Removed unused methods package from Imports. 15 | 16 | * Removed rgdal, maptools, rworldmap, oce from Suggests, and tested check succeeds without 17 | those being installed. 18 | 19 | * New function `lonlat()` for quick and dirty plots and to generate fields of longitude and 20 | latitude. 21 | 22 | * update for #16, not great still but better 23 | 24 | 25 | 26 | # graticule 0.1.6 27 | 28 | * Release to fix problems on CRAN, unused LazyData and dep on rmarkdown. 29 | 30 | * Remove warnings from geosphere about longitude, thanks to @Maschette for the suggestion. 31 | 32 | * Fix bug caused by new tile creation. https://github.com/AustralianAntarcticDivision/SOmap/issues/66 33 | 34 | * Tile and line graticules are now created by the same process, which discretizes 35 | to a default value of 5e4m (50km). This is settable with `options(graticule.mindist = )`. 36 | 37 | * graticule no longer shares the extra dependency from raster on rgeos, rasterToPolygons is no longer used 38 | 39 | * Added supporting information to the package. 40 | 41 | 42 | VERSION 0.1.2 43 | 44 | o specify LCC standard parallels explicitly to avoid problems from some PROJ.4 installations 45 | 46 | VERSION 0.1.0 47 | 48 | o new function pathologicule to draw the other projection 49 | 50 | VERSION 0.0.3 51 | 52 | o upgraded for release 53 | 54 | o added ice file raw data for vignette 55 | 56 | VERSION 0.0.2 57 | 58 | o added tiles option to graticule to return polygons 59 | 60 | o added a readme for the GitHub front page 61 | 62 | VERSION 0.0.1 63 | 64 | o basics working 65 | 66 | -------------------------------------------------------------------------------- /R/lonlat.R: -------------------------------------------------------------------------------- 1 | #' Add longitude latitude lines to a plot 2 | #' 3 | #' Use the coordinates of the input raster to generate coordinate rasters, these are 4 | #' then used in a contour plot. 5 | #' 6 | #' Plot is added to an existing plot by default. 7 | #' 8 | #' @param x input raster 9 | #' @param na.rm logical, remove missing values from generated coordinates 10 | #' @param ... passed to [graphics::contour()] 11 | #' @param plot logical, plot the result 12 | #' @param add logical, add to current plot or instantiate one 13 | #' @param lon if TRUE, only longitude plotted 14 | #' @param lat if TRUE (and `lon = FALSE`) only latitude plotted 15 | #' 16 | #' @return RasterBrick of the longitude and latitude values, two layers 17 | #' @export 18 | #' @return (invisibly) the raster (RasterBrick) object with longitude and latitude values of the input 19 | #' as two layers, otherwise this function used for side-effect (drawing on a plot) 20 | #' @importFrom graphics contour 21 | #' @examples 22 | #' plot(c(-180, 180), c(-90, 90)) 23 | #' lonlat(raster::raster()) 24 | #' 25 | #' p <- raster::projectExtent(raster::raster(), "+proj=igh") 26 | #' lonlat(p, add = FALSE) 27 | #' lonlat(p, levels = seq(-180, 180, by =15), add = FALSE) 28 | #' 29 | #' lonlat(p, levels = seq(-180, 180, by = 5), add = FALSE, lon = TRUE) 30 | #' lonlat(p, levels = seq(-180, 180, by = 15), add = TRUE, lat = TRUE) 31 | lonlat <- function(x, na.rm = FALSE, lon = FALSE, lat = FALSE, ..., plot = TRUE, add = TRUE) { 32 | x <- x[[1]] 33 | ll <- FALSE 34 | if (is.na(raster::projection(x))) { 35 | message("no projection metadata on raster, assuming longlat") 36 | ll <- TRUE 37 | } 38 | cell <- seq_len(raster::ncell(x)) 39 | if (na.rm) cell <- cell[!is.na(raster::values(x))] 40 | lons <- raster::raster(x) 41 | lats <- raster::raster(x) 42 | if (ll) { 43 | lons[cell] <- raster::xFromCell(x, cell) 44 | lats[cell] <- raster::yFromCell(x, cell) 45 | } else { 46 | xy <- raster::xyFromCell(x, cell) 47 | 48 | suppressWarnings( xy <- reproj::reproj_xy(xy, lonlatp4(), source = raster::projection(x))) 49 | lons[cell] <- xy[,1] 50 | lats[cell] <- xy[,2] 51 | } 52 | if (plot) { 53 | if (lon || !lat) raster::contour(lons, add = add, ...) 54 | if (!lon) raster::contour(lats, add = TRUE, ...) 55 | } 56 | invisible(raster::brick(lons, lats)) 57 | } 58 | -------------------------------------------------------------------------------- /R/graticule2.R: -------------------------------------------------------------------------------- 1 | #' @noRd 2 | #' @examples 3 | #' lon <- seq(-40, 40, by = 10) 4 | #' lat <- seq( -60, -40, by = 5) 5 | #' g <- graticule_tiles(lon, lat, margin = TRUE) 6 | #' xx <- seq(-90, 90, length = 10) + 147 7 | #' yy <- seq(-90, 90, length = 5) 8 | #' g <- graticule_tiles(xx, yy, proj = "+proj=ortho +lon_0=147 +ellps=WGS84") 9 | #' plot(g, col = c("black", "grey")) 10 | #' prj <- "+proj=stere +lat_0=-90 +lat_ts=-70 +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378273 +b=6356889.449 +units=m +no_defs" 11 | #' meridians <- seq(-180, 160, by = 20) 12 | #' parallels <- c(-80, -73.77, -68, -55, -45) 13 | #' polargrid <- graticule_tiles(lons = c(meridians, 180), 14 | #' lats = parallels, proj = prj) 15 | NULL 16 | 17 | #' Graticule tiles 18 | #' @noRd 19 | #' 20 | #' @importFrom stats approx 21 | #' @importFrom utils head 22 | graticule_tiles <- function(lons = seq(-180, 180, by = 15), lats = seq(-84, 84, by = 12), 23 | nverts = 24, proj = NULL, 24 | margin = FALSE) { 25 | if (length(lons) < 2) stop("length of argument `lons` is < 2") 26 | if (length(lats) < 2) stop("length of argument `lats` is < 2") 27 | grid <- raster::raster(raster::extent(range(lons), range(lats)), 28 | ncols = length(lons)-1, nrow = length(lats)-1) 29 | 30 | 31 | if (margin) { 32 | 33 | cells <- 34 | c(raster::cellFromRow(grid, 1), ## top margin 35 | raster::cellFromCol(grid, ncol(grid))[-c(1,nrow(grid))], ## right margin 36 | utils::head(rev(raster::cellFromRow(grid, nrow(grid))), -1), ## bottom margin 37 | utils::head(rev(raster::cellFromCol(grid, 1)), -1)) ## left margin 38 | } else { 39 | cells <- seq_len(raster::ncell(grid)) 40 | } 41 | 42 | ll <- vector("list", length(cells)) 43 | ## loop extents of every pixel (I know, I know) 44 | p4 <- lonlatp4() 45 | for (i in seq_along(ll)) { 46 | ex <- raster::extentFromCells(grid, cells[i]) 47 | m1 <- ll_extent(c(ex@xmin, ex@xmax), c(ex@ymin, ex@ymax)) 48 | if (!is.null(proj)) { 49 | m1 <- reproj::reproj_xy(m1, proj, source = lonlatp4()) 50 | p4 <- proj 51 | } 52 | ll[[i]] <- m1 53 | } 54 | xx <- do.call(raster::spPolygons, ll) 55 | 56 | xx <- sp::SpatialPolygonsDataFrame(xx, data.frame(x = 1:length(xx))) 57 | raster::projection(xx) <- p4 58 | xx 59 | } 60 | 61 | 62 | ll_extent <- function(lonrange, latrange, nverts = 24, mindist = 1e5) { 63 | ## technically we don't need meridionally segmentation, but this is general enough 64 | ## for any set of segments assumed to be rhumb lines 65 | dat2 <- matrix(c(lonrange, 66 | latrange)[c(1, 2, 2, 2, 2, 1, 1, 1, 67 | 3, 3, 3, 4, 4, 4, 4, 3)], 68 | ncol = 2) 69 | l <- lapply(seq(1, nrow(dat2), by = 2), 70 | function(.x) { 71 | xy <- dat2[c(.x, .x + 1), ] 72 | suppressWarnings(dst <- geosphere::distRhumb(xy[1, ], xy[2, ])) 73 | nn <- if (!is.null(nverts)) nverts else round(dst/mindist) 74 | nn <- max(c(nn, 3)) ## there's got to be a limit 75 | cbind(stats::approx(xy[,1], n = nn)$y, approx(xy[,2], n = nn)$y) 76 | }) 77 | do.call(rbind, lapply(l, head, -1)) 78 | } 79 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | editor_options: 4 | chunk_output_type: console 5 | --- 6 | 7 | 8 | 9 | ```{r, include = FALSE} 10 | knitr::opts_chunk$set( 11 | collapse = TRUE, 12 | comment = "#>", 13 | fig.path = "man/figures/README-", 14 | out.width = "100%" 15 | ) 16 | ``` 17 | # graticule 18 | 19 | 20 | 21 | [![CRAN status](https://www.r-pkg.org/badges/version/graticule)](https://CRAN.R-project.org/package=graticule) 22 | [![R-CMD-check](https://github.com/hypertidy/graticule/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/hypertidy/graticule/actions/workflows/R-CMD-check.yaml) 23 | 24 | 25 | 26 | Graticules are the longitude latitude lines shown on a projected map, and defining and drawing these lines is not easy to automate. The graticule package provides the tools to create and draw these lines by explicit specification by the user. This provides a good compromise between high-level automation and the flexibility to drive the low level details as needed, using base graphics in R. 27 | 28 | 29 | ## Installation 30 | 31 | You can install the released version of graticule from [CRAN](https://CRAN.R-project.org) with: 32 | 33 | ``` r 34 | install.packages("graticule") 35 | ``` 36 | 37 | And the development version from [GitHub](https://github.com/) with: 38 | 39 | ``` r 40 | # install.packages("devtools") 41 | devtools::install_github("hypertidy/graticule") 42 | ``` 43 | ## Example 44 | 45 | This is a basic example which shows how to create a graticule at specific longitude and latitude spacings 46 | and in a given projection. 47 | 48 | ```{r example} 49 | library(graticule) 50 | grat <- graticule(lons = seq(100, 220, by = 15), lats = seq(-60, -10, by = 5), proj = "+proj=laea +lon_0=140 +lat_0=-90 +datum=WGS84") 51 | plot(grat) 52 | ``` 53 | 54 | There is an automatic segmentation that is done at equal distances along these [rhumb lines](https://en.wikipedia.org/wiki/Rhumb_line). This is not an *[ideal spacing](https://bost.ocks.org/mike/example/)* but is an improvement on the common alternatives, and is easier to work with when you need fine control. 55 | 56 | If your projection is not wildly warped in most areas then the default rhumb line segmentation is the best first step. 57 | 58 | ```{r rhumb-points} 59 | plot(as(grat, "SpatialPoints")) 60 | ``` 61 | 62 | This also allows the common case of creating a sensible single polygon *wedge*, i.e. 63 | 64 | ```{r wedge} 65 | wedge <- graticule(lons = c(-40, 40), lats = c(-60, -40), proj = "+proj=laea +lat_0=-50 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs") 66 | plot(wedge) 67 | points(as(wedge, "SpatialPoints")) 68 | ``` 69 | 70 | ## Quick and dirty plot 71 | 72 | Give it a raster and let it plot, can provide `levels` as per contour (values of longitude or latitude to draw as lines), and separate longitude or latitude plot on their own. 73 | 74 | Sometimes it's enough, sometimes we can muck around to get what we want. 75 | 76 | ```{r lonlat} 77 | tfile <- system.file("extdata", "nt_20140320_f17_v01_s.bin", package = "graticule", mustWork = TRUE) 78 | ice <- raster::raster(tfile) 79 | ice[!ice > 0] <- NA 80 | raster::plot(ice, col = palr::ice_pal(100)) 81 | lonlat(ice) 82 | 83 | raster::plot(ice, col = palr::ice_pal(100)) 84 | lonlat(ice, lon = TRUE, levels = seq(-180, 165, by = 15)) 85 | lonlat(ice, lat = TRUE, levels = seq(-85, -40, by = 5)) 86 | 87 | ## not much good so let's get the actual arrays 88 | lon <- lonlat(ice, plot = FALSE)[[1]] 89 | lat <- lonlat(ice, plot = FALSE)[[2]] 90 | lon[lat < -85] <- NA 91 | raster::plot(ice, col = palr::ice_pal(100)) 92 | raster::contour(lon, add = TRUE) 93 | lonlat(ice, lat = TRUE, levels = seq(-85, -40, by = 5)) 94 | 95 | 96 | ``` 97 | 98 | ## Known Issues 99 | 100 | Please feel free to share your experiences and report problems at https://github.com/hypertidy/graticule/issues 101 | 102 | * general problems with segmentation, this is not done smartly yet (see hypertidy/bigcurve or the s2 package) 103 | * There's work needed for when `graticule_labels()` are created without using `xline/yline`, need more careful separation between generating every combination in the grid versus single lines 104 | 105 | 106 | --- 107 | 108 | Please note that the 'graticule' project is released with a 109 | [Contributor Code of Conduct](https://github.com/hypertidy/graticule/blob/master/CODE_OF_CONDUCT.md). 110 | By contributing to this project, you agree to abide by its terms. 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # graticule 5 | 6 | 7 | 8 | [![CRAN 9 | status](https://www.r-pkg.org/badges/version/graticule)](https://CRAN.R-project.org/package=graticule) 10 | [![R-CMD-check](https://github.com/hypertidy/graticule/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/hypertidy/graticule/actions/workflows/R-CMD-check.yaml) 11 | 12 | 13 | Graticules are the longitude latitude lines shown on a projected map, 14 | and defining and drawing these lines is not easy to automate. The 15 | graticule package provides the tools to create and draw these lines by 16 | explicit specification by the user. This provides a good compromise 17 | between high-level automation and the flexibility to drive the low level 18 | details as needed, using base graphics in R. 19 | 20 | ## Installation 21 | 22 | You can install the released version of graticule from 23 | [CRAN](https://CRAN.R-project.org) with: 24 | 25 | ``` r 26 | install.packages("graticule") 27 | ``` 28 | 29 | And the development version from [GitHub](https://github.com/) with: 30 | 31 | ``` r 32 | # install.packages("devtools") 33 | devtools::install_github("hypertidy/graticule") 34 | ``` 35 | 36 | ## Example 37 | 38 | This is a basic example which shows how to create a graticule at 39 | specific longitude and latitude spacings and in a given projection. 40 | 41 | ``` r 42 | library(graticule) 43 | #> Loading required package: sp 44 | grat <- graticule(lons = seq(100, 220, by = 15), lats = seq(-60, -10, by = 5), proj = "+proj=laea +lon_0=140 +lat_0=-90 +datum=WGS84") 45 | plot(grat) 46 | ``` 47 | 48 | 49 | 50 | There is an automatic segmentation that is done at equal distances along 51 | these [rhumb lines](https://en.wikipedia.org/wiki/Rhumb_line). This is 52 | not an *[ideal spacing](https://bost.ocks.org/mike/example/)* but is an 53 | improvement on the common alternatives, and is easier to work with when 54 | you need fine control. 55 | 56 | If your projection is not wildly warped in most areas then the default 57 | rhumb line segmentation is the best first step. 58 | 59 | ``` r 60 | plot(as(grat, "SpatialPoints")) 61 | ``` 62 | 63 | 64 | 65 | This also allows the common case of creating a sensible single polygon 66 | *wedge*, i.e.  67 | 68 | ``` r 69 | wedge <- graticule(lons = c(-40, 40), lats = c(-60, -40), proj = "+proj=laea +lat_0=-50 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs") 70 | plot(wedge) 71 | points(as(wedge, "SpatialPoints")) 72 | ``` 73 | 74 | 75 | 76 | ## Quick and dirty plot 77 | 78 | Give it a raster and let it plot, can provide `levels` as per contour 79 | (values of longitude or latitude to draw as lines), and separate 80 | longitude or latitude plot on their own. 81 | 82 | Sometimes it’s enough, sometimes we can muck around to get what we want. 83 | 84 | ``` r 85 | tfile <- system.file("extdata", "nt_20140320_f17_v01_s.bin", package = "graticule", mustWork = TRUE) 86 | ice <- raster::raster(tfile) 87 | ice[!ice > 0] <- NA 88 | raster::plot(ice, col = palr::ice_pal(100)) 89 | lonlat(ice) 90 | ``` 91 | 92 | 93 | 94 | ``` r 95 | 96 | raster::plot(ice, col = palr::ice_pal(100)) 97 | lonlat(ice, lon = TRUE, levels = seq(-180, 165, by = 15)) 98 | lonlat(ice, lat = TRUE, levels = seq(-85, -40, by = 5)) 99 | ``` 100 | 101 | 102 | 103 | ``` r 104 | 105 | ## not much good so let's get the actual arrays 106 | lon <- lonlat(ice, plot = FALSE)[[1]] 107 | lat <- lonlat(ice, plot = FALSE)[[2]] 108 | lon[lat < -85] <- NA 109 | raster::plot(ice, col = palr::ice_pal(100)) 110 | raster::contour(lon, add = TRUE) 111 | lonlat(ice, lat = TRUE, levels = seq(-85, -40, by = 5)) 112 | ``` 113 | 114 | 115 | 116 | ## Known Issues 117 | 118 | Please feel free to share your experiences and report problems at 119 | 120 | 121 | - general problems with segmentation, this is not done smartly yet 122 | (see hypertidy/bigcurve or the s2 package) 123 | - There’s work needed for when `graticule_labels()` are created 124 | without using `xline/yline`, need more careful separation between 125 | generating every combination in the grid versus single lines 126 | 127 | ------------------------------------------------------------------------ 128 | 129 | Please note that the ‘graticule’ project is released with a [Contributor 130 | Code of 131 | Conduct](https://github.com/hypertidy/graticule/blob/master/CODE_OF_CONDUCT.md). 132 | By contributing to this project, you agree to abide by its terms. 133 | -------------------------------------------------------------------------------- /R/graticule.R: -------------------------------------------------------------------------------- 1 | #' graticule: graticule lines for maps 2 | #' 3 | #' @docType package 4 | #' @name graticule 5 | NULL 6 | limfun <- function(x, lim, meridian = TRUE, nverts = NULL) { 7 | 8 | mindist <- getOption("graticule.mindist") 9 | if (is.na(mindist) || is.null(mindist) || !is.numeric(mindist)) { 10 | mindist <- 5e4 11 | warning(sprintf("option('graticule.mindist') is malformed, using %fm", mindist)) 12 | } 13 | if (!meridian) { 14 | out <- ll_extent(lim, c(x, x), mindist = mindist, nverts = nverts) 15 | } else { 16 | out <- ll_extent(c(x, x), lim, mindist = mindist, nverts = nverts) 17 | } 18 | out 19 | } 20 | 21 | step_fun <- function(x, steps, nd = 60, meridian = TRUE) { 22 | ind <- 1:2 23 | 24 | if (!meridian) ind <- 2:1 25 | op <- options(warn = -1) 26 | on.exit(options(op)) 27 | step_seg <- as.data.frame(utils::head(matrix(steps, nrow = length(steps)+1, ncol = 2), -2)) 28 | lapply(split(step_seg, 1:nrow(step_seg)), function(a) cbind(x = x, y = seq(unlist(a)[1], unlist(a)[2], length = nd))[, ind]) 29 | } 30 | 31 | buildlines <- function(x) { 32 | do.call("rbind", lapply(seq_along(x), function(xx) { 33 | res <- data.frame(x[[xx]], rep(xx, nrow(x[[xx]]))) 34 | names(res) <- c("x", "y", "id") 35 | res 36 | })) 37 | } 38 | 39 | ## from raster findMethods("isLonLat")[["character"]] 40 | # isLonLat <- function (x) 41 | # { 42 | # res1 <- grep("longlat", as.character(x), fixed = TRUE) 43 | # res2 <- grep("lonlat", as.character(x), fixed = TRUE) 44 | # if (length(res1) == 0L && length(res2) == 0L) { 45 | # return(FALSE) 46 | # } 47 | # else { 48 | # return(TRUE) 49 | # } 50 | # } 51 | 52 | lonlatp4 <- function() { 53 | "+proj=longlat +datum=WGS84" 54 | } 55 | 56 | #' Create graticule lines. 57 | #' 58 | #' Specify the creation of lines along meridians by specifying their placement 59 | #' at particular \code{lons} (longitudes) and \code{lats} (latitudes) and their extents 60 | #' with \code{xlim} (extent of parallel line in longitude) and \code{ylim} (extent of meridional line in latitude). 61 | #' 62 | #' Provide a valid PROJ.4 string to return the graticule lines in this projection. If this is not specified the graticule 63 | #' lines are returned in their original longlat / WGS84. 64 | #' All segments are discretized as _rhumb_lines_ at `getOption("graticule.mindist")` metres, which 65 | #' defaults to `5e4`. 66 | #' The arguments \code{xlim}, \code{ylim} and \code{nverts} are ignored if \code{tiles} is \code{TRUE}. 67 | #' @param lons longitudes for meridional lines 68 | #' @param lats latitudes for parallel lines 69 | #' @param nverts number of discrete vertices for each segment 70 | #' @param xlim maximum range of parallel lines 71 | #' @param ylim maximum range of meridional lines 72 | #' @param proj optional proj.4 string for output object 73 | #' @param tiles if \code{TRUE} return polygons as output 74 | #' @return SpatialLines or SpatialPolygons object 75 | #' @export 76 | #' @importFrom reproj reproj_xy 77 | #' @importFrom raster isLonLat raster extent values<- ncell res 78 | #' @importFrom sp SpatialLinesDataFrame Line Lines SpatialLines CRS 79 | #' @examples 80 | #' graticule() 81 | graticule <- function(lons, lats, nverts = NULL, xlim, ylim, proj = NULL, tiles = FALSE) { 82 | if (is.null(proj)) proj <- lonlatp4() 83 | proj <- as.character(proj) ## in case we are given CRS 84 | trans <- FALSE 85 | if (tiles) { 86 | if (!missing(xlim)) { 87 | warning("xlim is ignored if 'tiles = TRUE'") 88 | } 89 | if (!missing(ylim)) { 90 | warning("ylim is ignored if 'tiles = TRUE'") 91 | } 92 | 93 | } 94 | if (!raster::isLonLat(proj)) trans <- TRUE 95 | if (missing(lons)) { 96 | #usr <- par("usr") 97 | #if (all(usr == c(0, 1, 0, 1))) { 98 | lons <- seq(-180, 180, by = 15) 99 | } 100 | if (missing(lats)) { 101 | lats <- seq(-90, 90, by = 10) 102 | } 103 | 104 | if (tiles) { 105 | pp <- graticule_tiles(lons, lats, proj = proj) 106 | return(pp) 107 | } 108 | if (missing(xlim)) xlim <- range(lons) 109 | if (missing(ylim)) ylim <- range(lats) 110 | xline <- lapply(lons, limfun, lim = ylim, meridian = TRUE, nverts = nverts) 111 | yline <- lapply(lats, limfun, lim = xlim, meridian = FALSE, nverts = nverts) 112 | xs <- buildlines(xline) 113 | ys <- buildlines(yline) 114 | ys$id <- ys$id + max(xs$id) 115 | xs$type <- "meridian" 116 | ys$type <- "parallel" 117 | d <- rbind(xs, ys) 118 | d0 <- split(d, d$id) 119 | l <- vector("list", length(d0)) 120 | for (i in seq_along(d0)) { 121 | m1 <- as.matrix(d0[[i]][, c("x", "y")]) 122 | if (trans) { 123 | m1 <- reproj::reproj_xy(m1, proj, source = "+proj=longlat +datum=WGS84") 124 | } else { 125 | proj <- "OGC:CRS84" 126 | } 127 | l1 <- sp::Lines(list(sp::Line(m1)), ID = as.character(i)) 128 | l[[i]] <- l1 129 | } 130 | l <- sp::SpatialLinesDataFrame(sp::SpatialLines(l, proj4string = sp::CRS(proj)), 131 | data.frame(ID = as.character(seq_along(l)))) 132 | l 133 | 134 | } 135 | 136 | #' Create graticule labels. 137 | #' 138 | #' Returns a set of points with labels, for plotting in conjuction with \code{\link{graticule}}. 139 | #' 140 | #' SpatialPoints are returned in the projection of \code{proj} if given, or longlat / WGS84. 141 | #' @param lons longitudes for meridional labels 142 | #' @param lats latitudes for parallel labels 143 | #' @param xline meridian/s for placement of parallel labels 144 | #' @param yline parallel/s for placement of meridian labels 145 | #' @param proj optional proj.4 string for output object 146 | #' @export 147 | #' @importFrom sp degreeLabelsEW degreeLabelsNS coordinates<- proj4string<- 148 | #' @return SpatialPoints object with labels for downstream use 149 | #' @examples 150 | #' xx <- c(100, 120, 160, 180) 151 | #' yy <- c(-80,-70,-60, -50,-45, -30) 152 | #' prj <- "+proj=lcc +lon_0=150 +lat_0=-80 +lat_1=-85 +lat_2=-75 +ellps=WGS84" 153 | #' plot(graticule(lons = xx, lats = yy, proj = prj)) 154 | #' labs <- graticule_labels(lons = xx, lats = yy, xline = 100, yline = -80, proj = prj) 155 | #' op <- par(xpd = NA) 156 | #' text(labs, lab = parse(text = labs$lab), pos = c(2, 1)[labs$islon + 1], adj = 1.2) 157 | #' par(op) 158 | graticule_labels <- function(lons, lats, xline, yline, proj = NULL) { 159 | if (is.null(proj)) proj <- lonlatp4() 160 | proj <- as.character(proj) ## in case we are given CRS 161 | trans <- FALSE 162 | if (!raster::isLonLat(proj)) trans <- TRUE 163 | if (missing(lons)) { 164 | #usr <- par("usr") 165 | #if (all(usr == c(0, 1, 0, 1))) { 166 | lons <- seq(-180, 180, by = 15) 167 | } 168 | if (missing(lats)) { 169 | lats <- seq(-90, 90, by = 10) 170 | } 171 | if (missing(xline)) xline <- lons 172 | if (missing(yline)) yline <- lats 173 | 174 | lonlabs <- expand.grid(x = lons, y = yline) 175 | lonlabs$lab <- degreeLabelsEW(lonlabs$x) 176 | lonlabs$islon <- TRUE 177 | latlabs <- expand.grid(x = xline, y = lats) 178 | latlabs$lab <- degreeLabelsNS(latlabs$y) 179 | latlabs$islon <- FALSE 180 | l <- rbind(lonlabs, latlabs) 181 | p4 <- lonlatp4() 182 | if (trans) { 183 | l[1:2] <- reproj::reproj_xy(l[1:2], proj, source = p4) 184 | p4 <- proj 185 | } 186 | 187 | coordinates(l) <- 1:2 188 | 189 | 190 | proj4string(l) <- CRS(p4) 191 | 192 | l 193 | } 194 | -------------------------------------------------------------------------------- /vignettes/graticule.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Graticule" 3 | author: "Michael D. Sumner" 4 | date: "`r Sys.Date()`" 5 | output: 6 | rmarkdown::html_vignette: 7 | fig_width: 9 8 | fig_height: 9 9 | vignette: > 10 | %\VignetteIndexEntry{Graticule} 11 | \usepackage[utf8]{inputenc} 12 | %\VignetteEngine{knitr::rmarkdown} 13 | editor_options: 14 | chunk_output_type: console 15 | --- 16 | 17 | 18 | 19 | 20 | ```{r, echo = FALSE} 21 | knitr::opts_chunk$set( 22 | collapse = TRUE, 23 | comment = "#>", 24 | fig.path = "README-" 25 | ) 26 | 27 | 28 | ``` 29 | 30 | # Graticule 31 | 32 | Graticules are the longitude latitude lines shown on a projected map, and defining and drawing these lines is not easy to automate. The graticule package provides the tools to 33 | create and draw these lines by explicit specification by the user. This provides a good compromise between high-level automation and the flexibility to drive the low level details as needed, using base graphics in R. 34 | 35 | 36 | Please note that this is an evolving topic, across a number of packages in R. There's no 37 | ongoing integration of how best to do this, and some of the commentary in this vignette 38 | will be out of date quickly as individual packages do updates. I've recorded the exact versions used for this document at the end. 39 | 40 | This is an area that needs much more discussion and outlining of needs and experiences. 41 | 42 | 43 | # Examples 44 | 45 | 46 | A simple example to build a map around the state of Victoria in Australia. Victoria uses a local Lambert Conformal Conic projection that was introduced while the shift to GDA94 was implemented, to reduce complications due to working with more than one UTM zone for the state. 47 | 48 | ```{r,message=FALSE, eval=FALSE} 49 | library(raster) 50 | 51 | library(graticule) 52 | 53 | ## VicGrid 54 | prj <- "+proj=lcc +lat_1=-36 +lat_2=-38 +lat_0=-37 +lon_0=145 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs" 55 | 56 | 57 | ## specify exactly where we want meridians and parallels 58 | lons <- seq(140, 150, length = 5) 59 | lats <- seq(-40, -35, length = 6) 60 | ## optionally, specify the extents of the meridians and parallels 61 | ## here we push them out a little on each side 62 | xl <- range(lons) + c(-0.4, 0.4) 63 | yl <- range(lats) + c(-0.4, 0.4) 64 | ## build the lines with our precise locations and ranges 65 | grat <- graticule(lons, lats, proj = prj, xlim = xl, ylim = yl) 66 | ## build the labels, here they sit exactly on the western and northern extent 67 | ## of our line ranges 68 | labs <- graticule_labels(lons, lats, xline = min(xl), yline = max(yl), proj = prj) 69 | 70 | ## set up a map extent and plot 71 | op <- par(mar = rep(0, 4)) 72 | plot(extent(grat) + c(4, 2) * 1e5, asp = 1, type = "n", axes = FALSE, xlab = "", ylab = "") 73 | #plot(pmap, add = TRUE) 74 | ## the lines are a SpatialLinesDataFrame 75 | plot(grat, add = TRUE, lty = 5, col = rgb(0, 0, 0, 0.8)) 76 | ## the labels are a SpatialPointsDataFrame, and islon tells us which kind 77 | text(subset(labs, labs$islon), lab = parse(text = labs$lab[labs$islon]), pos = 3) 78 | text(subset(labs, !labs$islon), lab = parse(text = labs$lab[!labs$islon]), pos = 2) 79 | par(op) 80 | 81 | ``` 82 | 83 | 84 | ## A polar example 85 | 86 | Download some sea ice concentration data and plot with a graticule. These passive microwave data are defined on a Polar Stereographic grid on the Hughes ellipsoid (predating WGS84), and there are daily files available since 1978. This is not the prettiest map, but the example is showing how we have control over exactly where the lines are created. We can build the lines anywhere, not necessarily at regular intervals or rounded numbers, and we can over or under extend the parallels relative to the meridians and vice versa. 87 | 88 | ```{r,message=FALSE} 89 | library(raster) 90 | library(graticule) 91 | 92 | tfile <- system.file("extdata", "nt_20140320_f17_v01_s.bin", package = "graticule") 93 | ice <- raster(tfile) 94 | 95 | meridians <- seq(-180, 160, by = 20) 96 | parallels <- c(-80, -73.77, -68, -55, -45) 97 | mlim <- c(-180, 180) 98 | plim <- c(-88, -50) 99 | grat <- graticule(lons = meridians, lats = parallels, xlim = mlim, ylim = plim, proj = projection(ice)) 100 | labs <- graticule_labels(meridians, parallels, xline = -45, yline = -60, proj = projection(ice)) 101 | plot(ice, axes = FALSE) 102 | plot(grat, add = TRUE, lty = 3) 103 | text(labs, lab = parse(text= labs$lab), col= c("firebrick", "darkblue")[labs$islon + 1], cex = 0.85) 104 | title(sprintf("Sea ice concentration %s", gsub(".bin", "", basename(tfile))), cex.main = 0.8) 105 | title(sub = projection(ice), cex.sub = 0.6) 106 | ``` 107 | 108 | ## Create the graticule as polygons 109 | 110 | Continuing from the sea ice example, build the graticule grid as actual polygons. Necessarily the `xlim/ylim` option is ignored since we have not specified sensibly closed polygonal rings where there are under or over laps. 111 | 112 | ```{r} 113 | polargrid <- graticule(lons = c(meridians, 180), lats = parallels, proj = projection(ice), tiles = TRUE) 114 | centroids <- reproj::reproj_xy(coordinates(polargrid), "+proj=longlat +datum=WGS84", source = projection(ice)) 115 | labs <- graticule_labels(meridians, parallels, proj = projection(ice)) 116 | #labs <- graticule_labels(as.integer(centroids[,1]), as.integer(centroids[,2]), proj = projection(ice)) 117 | #labs <- labs[!duplicated(as.data.frame(labs)), ] ## this needs a fix 118 | cols <- sample(colors(), nrow(polargrid)) 119 | op <- par(mar = rep(0, 4)) 120 | plot(polargrid, col = cols, bg = "black") 121 | text(labs[labs$islon, ], lab = parse(text = labs$lab[labs$islon]), col = "white", cex = 0.9, pos = 3) 122 | par(op) 123 | 124 | ``` 125 | ## Comparison to tools in sp and rgdal 126 | 127 | **This section kept for historical interest only. ** 128 | 129 | See: https://github.com/hypertidy/graticule/issues/19#issuecomment-1379874409 130 | 131 | 132 | 133 | Also see here for another implementation of the Tissot Indicatrix in R by user [whuber on GIS StackExchange](https://gis.stackexchange.com/questions/31651/an-example-tissot-ellipse-for-an-equirectangular-projection). This is available in the [dev package tissot](https://github.com/hypertidy/tissot). 134 | 135 | 136 | ## Notes 137 | 138 | Efforts could be shared with the sp and rgdal projects to improve the functionality for the `llgridlines` and its worker functions `gridlines` and `gridat` in that central place, and I agree with this. But I have an interest in working with graticules more directly as objects, and potentially stored in relational-table approach built on dplyr, and so I just found it simpler to start from scratch in this package. Also, there is a lot of this functionality spread around the place in sp, raster, maptools, fields, oce and many others. It is time for a new review, analogous to the effort that built sp in ca. 2002. 139 | 140 | ### Terminology 141 | 142 | I tend to use the same terminology as used within [Manifold System](https://manifold.net/) *because it's so awesome* and that's where I first learnt about most of these concepts. In my experience not many people use the term *graticule* in this way, so take it from the master himself on page 8 (Snyder, 1987): 143 | 144 | > To identify the location of points on the Earth, a graticule or network of longitude 145 | > and latitude lines has been superimposed on the surface. They are commonly 146 | > referred to as meridians and parallels, respectively. 147 | 148 | 149 | ## References 150 | 151 | [Snyder, John Parr. Map projections--A working manual. No. 1395. USGPO, 1987.](https://pubs.er.usgs.gov/publication/pp1395) 152 | 153 | ## Environment 154 | 155 | ```{r} 156 | sessioninfo::session_info() 157 | ``` 158 | 159 | --------------------------------------------------------------------------------