├── src ├── .gitignore ├── manipulate.h ├── coders.h ├── validate.h ├── RcppExports.cpp ├── validate.cpp ├── manipulate.cpp ├── olctools.cpp └── coders.cpp ├── LICENSE ├── .Rbuildignore ├── tests ├── testthat.R └── testthat │ ├── test_encoding.R │ ├── test_validators.R │ ├── test_decoding.R │ └── test_shortener.R ├── R ├── check.R ├── olctools.R └── RcppExports.R ├── NAMESPACE ├── .gitignore ├── .travis.yml ├── man ├── olctools.Rd ├── decode_olc.Rd ├── shorten_olc.Rd ├── encode_olc.Rd ├── recover_olc.Rd └── olc_validate.Rd ├── olctools.Rproj ├── NEWS ├── DESCRIPTION ├── vignettes ├── Introduction_to_olctools.R └── Introduction_to_olctools.Rmd ├── README.md └── CONDUCT.md /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2015 2 | COPYRIGHT HOLDER: Oliver Keyes 3 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^CONDUCT\.md$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | ^.*\.travis.yml 5 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(olctools) 3 | 4 | test_check("olctools") 5 | -------------------------------------------------------------------------------- /R/check.R: -------------------------------------------------------------------------------- 1 | olc_full <- function(olc){ 2 | 3 | } 4 | 5 | olc_short <- function(olc){ 6 | 7 | } 8 | 9 | olc_valid <- function(olc){ 10 | 11 | } 12 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(decode_olc) 4 | export(encode_olc) 5 | export(recover_olc) 6 | export(shorten_olc) 7 | export(validate_full) 8 | export(validate_olc) 9 | export(validate_short) 10 | importFrom(Rcpp,sourceCpp) 11 | useDynLib(olctools) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Example code in package build process 6 | *-Ex.R 7 | # RStudio files 8 | .Rproj.user/ 9 | # produced vignettes 10 | vignettes/*.html 11 | vignettes/*.pdf 12 | .Rproj.user 13 | # Makefile bits and bobs 14 | *.cache 15 | *.log 16 | inst/doc 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Sample .travis.yml for R projects 2 | 3 | language: r 4 | warnings_are_errors: false 5 | sudo: required 6 | 7 | env: 8 | global: 9 | - CRAN: http://cran.rstudio.com 10 | 11 | r_packages: 12 | - testthat 13 | - knitr 14 | - Rcpp 15 | - rmarkdown 16 | notifications: 17 | email: 18 | on_success: change 19 | on_failure: change 20 | -------------------------------------------------------------------------------- /R/olctools.R: -------------------------------------------------------------------------------- 1 | #' @title Tools for handling Open Location Codes 2 | #' @name olctools 3 | #' @description \href{http://openlocationcode.com/}{Open Location Codes} are a Google-created standard for identifying geographic locations. 4 | #' olctools provides utilities for validating, encoding and decoding entries that follow this standard. 5 | #' @useDynLib olctools 6 | #' @importFrom Rcpp sourceCpp 7 | #' @docType package 8 | #' @aliases olctools olctools-package 9 | NULL 10 | -------------------------------------------------------------------------------- /man/olctools.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/olctools.R 3 | \docType{package} 4 | \name{olctools} 5 | \alias{olctools} 6 | \alias{olctools-package} 7 | \title{Tools for handling Open Location Codes} 8 | \description{ 9 | \href{http://openlocationcode.com/}{Open Location Codes} are a Google-created standard for identifying geographic locations. 10 | olctools provides utilities for validating, encoding and decoding entries that follow this standard. 11 | } 12 | 13 | -------------------------------------------------------------------------------- /olctools.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 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 | PackageCheckArgs: --as-cran 22 | PackageRoxygenize: rd,collate,namespace,vignette 23 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | olctools 0.3.0 2 | ----------------------------------- 3 | * NA support incorporated. 4 | * Bug in reference implementation with recovering shortened OLCs fixed over there, and subsequently 5 | fixed over here! 6 | 7 | olctools 0.2.1 8 | ----------------------------------- 9 | * first CRAN release. 10 | * Bugfixes to allow for a Solaris release. 11 | 12 | olctools 0.2.0 13 | ----------------------------------- 14 | 15 | * shorten_olc added, allowing for full Open Location Codes to be converted to short ones. 16 | * recover_olc added, allowing for shortened codes to be converted to full ones. 17 | * dependency on Boost::Regex deprecated, meaning this /should/ work on Windows. 18 | * vignette added. 19 | -------------------------------------------------------------------------------- /src/manipulate.h: -------------------------------------------------------------------------------- 1 | #include "coders.h" 2 | #include 3 | 4 | using namespace Rcpp; 5 | 6 | #ifndef __OLC_MANIP__ 7 | #define __OLC_MANIP__ 8 | 9 | class olc_manipulate: public olc_coders { 10 | 11 | private: 12 | 13 | int min_trim_length; 14 | 15 | std::string shorten_single(std::string olc, double latitude, double longitude); 16 | 17 | std::string recover_single(std::string olc, double latitude, double longitude); 18 | 19 | public: 20 | 21 | CharacterVector shorten_vector(CharacterVector olc, NumericVector latitude, NumericVector longitude); 22 | 23 | CharacterVector recover_vector(CharacterVector olc, NumericVector latitude, NumericVector longitude); 24 | 25 | olc_manipulate(); 26 | }; 27 | #endif 28 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: olctools 2 | Type: Package 3 | Title: Open Location Code Handling in R 4 | Version: 0.3.0 5 | Date: 2016-05-07 6 | Author: Oliver Keyes 7 | Maintainer: Oliver Keyes 8 | Description: 'Open Location Codes' (https://openlocationcode.com/) are a Google- 9 | created standard for identifying geographic locations. olctools provides 10 | utilities for validating, encoding and decoding entries that follow this 11 | standard. 12 | License: MIT + file LICENSE 13 | Suggests: 14 | testthat, 15 | knitr 16 | LinkingTo: Rcpp 17 | Imports: 18 | Rcpp 19 | VignetteBuilder: knitr 20 | RoxygenNote: 5.0.1 21 | URL: https://github.com/Ironholds/olctools 22 | BugReports: https://github.com/ironholds/olctools/issues 23 | -------------------------------------------------------------------------------- /man/decode_olc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppExports.R 3 | \name{decode_olc} 4 | \alias{decode_olc} 5 | \title{Decode Open Location Codes into Latitude and Longitude Pairs} 6 | \usage{ 7 | decode_olc(olcs) 8 | } 9 | \arguments{ 10 | \item{olcs}{a vector of Open Location Codes, generated through \code{encode_olc} or 11 | an equivalent tool.} 12 | } 13 | \description{ 14 | \code{decode_olc} takes Open Location Codes and, if they're 15 | valid (see \code{\link{validate_full}}) returns the minium, centred and maximum 16 | latitude and longitude for those coordinates. 17 | } 18 | \examples{ 19 | decode_olc("7FG49Q00+") 20 | 21 | } 22 | \seealso{ 23 | \code{\link{encode_olc}} for the opposite operation, and \code{\link{shorten_olc}} to convert 24 | "full" Open Location Codes to "short" Open Location Codes. 25 | } 26 | 27 | -------------------------------------------------------------------------------- /vignettes/Introduction_to_olctools.R: -------------------------------------------------------------------------------- 1 | ## ---- eval=FALSE--------------------------------------------------------- 2 | # library(olctools) 3 | # # Encode the location of Mbgathi Road, in Nairobi, Kenya 4 | # encode_olc(-1.314063, 36.79881, 10) 5 | # 6 | # # [1] "6GCRMQPX+9G" 7 | 8 | ## ---- eval=FALSE--------------------------------------------------------- 9 | # shorten_olc("6GCRMQPX+9G", -1.314063, 36.79881) 10 | # 11 | # # [1] "+9G" 12 | 13 | ## ---- eval=FALSE--------------------------------------------------------- 14 | # str(decode_olc(c("6GCRMQPX+9G", "7FG49QCJ+2VX"))) 15 | # 16 | # # 'data.frame': 2 obs. of 7 variables: 17 | # # $ latitude_low : num -1.31 20.37 18 | # # $ longitude_low : num 36.8 2.78 19 | # # $ latitude_center : num -1.31 20.37 20 | # # $ longitude_center: num 36.8 2.78 21 | # # $ latitude_high : num -1.31 20.37 22 | # # $ longitude_high : num 36.8 2.78 23 | # # $ code_lengths : int 10 11 24 | 25 | ## ---- eval=FALSE--------------------------------------------------------- 26 | # recover_olc("9G8F+6X", 47.4, 8.6) 27 | # 28 | # # [1] "8FVC9G8F+6X" 29 | 30 | -------------------------------------------------------------------------------- /src/coders.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "validate.h" 3 | using namespace Rcpp; 4 | 5 | #ifndef __OLC_CODE__ 6 | #define __OLC_CODE__ 7 | 8 | class olc_coders: public olc_validate { 9 | 10 | protected: 11 | 12 | int grid_rows; 13 | 14 | int grid_cols; 15 | 16 | double grid_degrees; 17 | 18 | unsigned int max_pair_length; 19 | 20 | std::vector < double > resolution_levels; 21 | 22 | double clip_lat(double lat); 23 | 24 | double clip_longitude(double longitude); 25 | 26 | double lat_precision(unsigned int length); 27 | 28 | std::vector < double > olc_decode_single(std::string olc); 29 | 30 | std::string olc_encode_single(double lat, double longitude, unsigned int output_length); 31 | 32 | private: 33 | 34 | 35 | std::vector < double > olc_decode_pair(std::string code, int offset); 36 | 37 | std::vector < double > olc_decode_grid(std::string code); 38 | 39 | 40 | public: 41 | 42 | CharacterVector olc_encode_vector(NumericVector latitude, NumericVector longitude, 43 | IntegerVector code_length); 44 | 45 | DataFrame olc_decode_vector(CharacterVector olcs); 46 | 47 | olc_coders(); 48 | 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /man/shorten_olc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppExports.R 3 | \name{shorten_olc} 4 | \alias{shorten_olc} 5 | \title{Shorten Full Open Location Codes} 6 | \usage{ 7 | shorten_olc(olcs, lats, longs) 8 | } 9 | \arguments{ 10 | \item{olcs}{a vector of open location codes, generated with \code{\link{encode_olc}} or through 11 | any other means.} 12 | 13 | \item{lats}{a numeric vector of latitudes.} 14 | 15 | \item{longs}{a numeric vector of longitudes, equivalent in size to \code{lats}.} 16 | } 17 | \description{ 18 | One of the things that makes OLCs useful is that they can shortened - you can trim 19 | characters off them, saving space without substantially compromising the accuracy. \code{shorten_olc} 20 | takes full-length OLCs (generated with \code{\link{encode_olc}} or any other way) and shortens them. 21 | } 22 | \examples{ 23 | #Encode an OLC and then shorten it 24 | olc <- encode_olc(51.3708675,-1.217765625, 12) 25 | validate_full(olc) 26 | # [1] TRUE 27 | 28 | olc <- shorten_olc(olc, 51.3708675,-1.217765625) 29 | validate_short(olc) 30 | # [1] TRUE 31 | 32 | } 33 | \seealso{ 34 | \code{\link{encode_olc}} to create full Open Location Codes. 35 | } 36 | 37 | -------------------------------------------------------------------------------- /man/encode_olc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppExports.R 3 | \name{encode_olc} 4 | \alias{encode_olc} 5 | \title{Encode Latitude and Longitude Pairs as Open Location Codes} 6 | \usage{ 7 | encode_olc(lats, longs, length) 8 | } 9 | \arguments{ 10 | \item{lats}{a numeric vector of latitudes.} 11 | 12 | \item{longs}{a numeric vector of longitudes, equivalent in size to \code{lats}} 13 | 14 | \item{length}{the length you want the resulting OLCs to be. The conventional lengths 15 | are 10 or 11, with any number above 8 and any \emph{even} number below it being acceptable. \code{length} 16 | should consist of either a single value, if you want all codes to be calculated to the same length, or a 17 | vector of values the same size as \code{lats} and \code{longs} if you want to pre-set values.} 18 | } 19 | \description{ 20 | \code{encode_olc} creates Open Location Codes from 21 | latitude and longitude values, of a specified length. 22 | } 23 | \examples{ 24 | encode_olc(20.375, 2.775,6) 25 | 26 | } 27 | \seealso{ 28 | \code{\link{decode_olc}} for the opposite operation, and \code{\link{shorten_olc}} to convert 29 | "full" Open Location Codes to "short" Open Location Codes. 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/validate.h: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace Rcpp; 3 | 4 | #ifndef __OLC_VALIDATE__ 5 | #define __OLC_VALIDATE__ 6 | 7 | class olc_validate { 8 | 9 | protected: 10 | 11 | /** 12 | * Valid characters for an OLC to have. Basically the character set and + or 0. 13 | */ 14 | std::string valid_chars; 15 | 16 | /** 17 | * The separator character for an OLC 18 | */ 19 | std::string separator; 20 | 21 | /** 22 | * Where in the OLC the separator character should appear. 23 | */ 24 | unsigned int separator_position; 25 | 26 | /** 27 | * The padding character (0). 28 | */ 29 | std::string padding; 30 | 31 | std::string character_set; 32 | 33 | int charset_length; 34 | 35 | int max_latitude; 36 | 37 | int max_longitude; 38 | 39 | bool olc_check_full_single(std::string olc); 40 | 41 | bool olc_check_short_single(std::string olc); 42 | 43 | private: 44 | 45 | bool olc_check_single(std::string olc); 46 | 47 | bool olc_check_either_single(std::string olc); 48 | 49 | public: 50 | 51 | LogicalVector olc_check_full_vector(CharacterVector olc); 52 | LogicalVector olc_check_short_vector(CharacterVector olc); 53 | LogicalVector olc_check_either_vector(CharacterVector olc); 54 | olc_validate(); 55 | }; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Open Location Codes in R 2 | 3 | An R implementation of the Google [Open Location Codes](https://openlocationcode.com) standard. 4 | 5 | __Author:__ Oliver Keyes
6 | __License:__ [MIT](https://opensource.org/licenses/MIT)
7 | __Status:__ Stable 8 | 9 | ![downloads](http://cranlogs.r-pkg.org/badges/grand-total/olctools) 10 | [![Travis-CI Build Status](https://travis-ci.org/Ironholds/olctools.svg?branch=master)](https://travis-ci.org/Ironholds/olctools) 11 | 12 | The Google Open Location Code standard allows you to encode the latitude and longitude of an area into a compressed string, 13 | resolving it down to a 14 square meter box. `olctools` provides a fast, vectorised R implementation of this standard that 14 | you can use to encode, decode, shorten, expand or validate OLCs. 15 | 16 | Please note that this project is released with a [Contributor Code of Conduct](https://github.com/Ironholds/olctools/blob/master/CONDUCT.md). By participating in this project you agree to abide by its terms. 17 | 18 | ## Installation 19 | 20 | `olctools` depends on Rcpp but is otherwise dependency-free! It can be grabbed from CRAN with: 21 | 22 | install.packages("olctools") 23 | 24 | Alternately, the development version can be obtained through: 25 | 26 | devtools::install_github("ironholds/olctools") 27 | -------------------------------------------------------------------------------- /man/recover_olc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppExports.R 3 | \name{recover_olc} 4 | \alias{recover_olc} 5 | \title{Recover Full Open Location Codes From Shortened Codes} 6 | \usage{ 7 | recover_olc(olcs, lats, longs) 8 | } 9 | \arguments{ 10 | \item{olcs}{a vector of short open location codes, generated with \code{\link{shorten_olc}} or through 11 | any other means.} 12 | 13 | \item{lats}{a numeric vector of latitudes.} 14 | 15 | \item{longs}{a numeric vector of longitudes, equivalent in size to \code{lats}.} 16 | } 17 | \description{ 18 | \code{\link{shorten_olc}} (and other sources) shorten a code, reducing 19 | the space it occupies. They also limit its ability to be translated back into latitude/longitude 20 | pairs. \code{recover_olc} recovers a full code from a shortened one, allowing it to be decoded with 21 | \code{\link{decode_olc}}. \emph{Some} loss of accuracy or precision is expected - and as it finds 22 | the closest match to the coordinates rather than to the original code, the characters may be very 23 | different. 24 | } 25 | \examples{ 26 | # Shorten an OLC and then recover the nearest full code. Note the actual characters differ. 27 | shortened_code <- shorten_olc("8FVC9G8F+6X", 47.5, 8.5); 28 | recovered_code <- recover_olc(shortened_code, 47.4, 8.6); 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /man/olc_validate.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppExports.R 3 | \name{validate_olc} 4 | \alias{olc_validate} 5 | \alias{validate_full} 6 | \alias{validate_olc} 7 | \alias{validate_short} 8 | \title{Check the Validity of Open Location Codes} 9 | \usage{ 10 | validate_olc(codes) 11 | 12 | validate_short(codes) 13 | 14 | validate_full(codes) 15 | } 16 | \arguments{ 17 | \item{codes}{a character vector containing Open Location Codes.} 18 | } 19 | \value{ 20 | a vector of TRUE and FALSE values, where TRUE corresponds to a 21 | valid code and FALSE an invalid. 22 | } 23 | \description{ 24 | These functions allow a useR to check whether OLCs they've 25 | been provided are valid or not. \code{valid_short} identifies whether 26 | a vector of OLCs are valid "short" codes; \code{valid_long} identifies 27 | whether OLCs are valid "long" codes, and \code{valid_full} identifies 28 | whether OLCs are valid, full stop. 29 | } 30 | \examples{ 31 | #Validate that a particular OLC is valid 32 | validate_olc("WC2345+G6g") 33 | #[1] TRUE 34 | 35 | #It is! Is it a short? 36 | validate_short("WC2345+G6g") 37 | #[1] TRUE 38 | #Yep! 39 | 40 | #So it's not full? 41 | validate_full("WC2345+G6g") 42 | #[1] FALSE 43 | #Nope! 44 | } 45 | \seealso{ 46 | \code{\link{decode_olc}} and \code{\link{encode_olc}} for creating 47 | and resolving valid Open Location Codes. 48 | } 49 | 50 | -------------------------------------------------------------------------------- /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 | (http:contributor-covenant.org), version 1.0.0, available at 25 | http://contributor-covenant.org/version/1/0/0/ 26 | -------------------------------------------------------------------------------- /tests/testthat/test_encoding.R: -------------------------------------------------------------------------------- 1 | testthat::context("Test OLC encoding") 2 | 3 | # Uses https://github.com/google/open-location-code/blob/master/test_data/encodingTests.csv 4 | testthat::test_that("OLC encoding works for very simple cases", { 5 | testthat::expect_equal(encode_olc(20.375, 2.775, 6), "7FG49Q00+") 6 | testthat::expect_equal(encode_olc(20.3700625, 2.7821875, 10), "7FG49QCJ+2V") 7 | }) 8 | 9 | testthat::test_that("OLC encoding works for more-complex cases with lengths > 10",{ 10 | testthat::expect_equal(encode_olc(20.3701125, 2.782234375, 11), "7FG49QCJ+2VX") 11 | testthat::expect_equal(encode_olc(20.3701135, 2.78223535156, 13), "7FG49QCJ+2VXGJ") 12 | testthat::expect_equal(encode_olc(47.0000625,8.0000625, 10), "8FVC2222+22") 13 | }) 14 | 15 | testthat::test_that("Negative values can be encoded", { 16 | testthat::expect_equal(encode_olc(-41.2730625,174.7859375, 10), "4VCPPQGP+Q9") 17 | }) 18 | 19 | testthat::test_that("OLC encoding throws an error with odd-numbered requested lengths below 8",{ 20 | testthat::expect_that(encode_olc(20.375, 2.775, 5), testthat::throws_error("The length value")) 21 | testthat::expect_that(encode_olc(20.375, 2.775, 7), testthat::throws_error("The length value")) 22 | }) 23 | 24 | testthat::test_that("OLC encoding has NA support", { 25 | testthat::expect_that(encode_olc(20.364, NA, 5), is.na) 26 | testthat::expect_that(encode_olc(20.364, 2.775, NA), is.na) 27 | testthat::expect_that(encode_olc(NA, 2.775, 5), is.na) 28 | }) 29 | -------------------------------------------------------------------------------- /tests/testthat/test_validators.R: -------------------------------------------------------------------------------- 1 | testthat::context("Test OLC validation") 2 | 3 | testthat::test_that("Invalid OLCs can be identified", { 4 | invalid_olcs <- c("8FWC2345+G", "8FWC2_45+G6", "8FWC2η45+G6", 5 | "8FWC2345+G6+", "8FWC2300+G6", "WC2300+G6g","WC2345+G") 6 | 7 | testthat::expect_that(any(validate_olc(invalid_olcs)), equals(FALSE)) 8 | }) 9 | 10 | testthat::test_that("Invalid short OLCs can be identified", { 11 | invalid_shorts <- c("8FWC2345+G6", "8FWC2345+G6G", "8fwc2345+", 12 | "8FWCX400+") 13 | testthat::expect_that(any(validate_short(invalid_shorts)), equals(FALSE)) 14 | }) 15 | 16 | testthat::test_that("Valid short OLCs can be identified", { 17 | valid_shorts <- c("WC2345+G6g","2345+G6", "45+G6", "+G6") 18 | testthat::expect_that(all(validate_short(valid_shorts)), equals(TRUE)) 19 | }) 20 | 21 | testthat::test_that("Invalid full OLCs can be identified", { 22 | invalid_full <- c("WC2345+G6g","2345+G6", "45+G6", "+G6") 23 | testthat::expect_that(any(validate_full(invalid_full)), equals(FALSE)) 24 | }) 25 | 26 | testthat::test_that("Valid full OLCs can be identified", { 27 | valid_fulls <- c("8FWC2345+G6","8FWC2345+G6G", "8FWCX400+") 28 | testthat::expect_that(all(validate_full(valid_fulls)), equals(TRUE)) 29 | }) 30 | 31 | testthat::test_that("OLC validators have NA support", { 32 | testthat::expect_true(is.na(validate_full(NA))) 33 | testthat::expect_true(is.na(validate_short(NA))) 34 | testthat::expect_true(is.na(validate_olc(NA))) 35 | 36 | }) 37 | -------------------------------------------------------------------------------- /tests/testthat/test_decoding.R: -------------------------------------------------------------------------------- 1 | testthat::context("Test OLC decoding") 2 | 3 | # We need to make sure both are being properly rounded, here. 4 | format_num <- function(n){ 5 | unlist(lapply(n, function(x){ 6 | return(signif(x, 8)) 7 | })) 8 | } 9 | 10 | # Uses https://github.com/google/open-location-code/blob/master/test_data/encodingTests.csv 11 | testthat::test_that("OLC decoding works for very simple cases", { 12 | result <- unlist(decode_olc("7FG49Q00+")) 13 | testthat::expect_that(result[c(3,4,7)], is_equivalent_to(c(20.375, 2.775, 6))) 14 | result <- unlist(decode_olc("7FG49QCJ+2V")) 15 | testthat::expect_that(result[c(3,4,7)], is_equivalent_to(c(20.3700625, 2.7821875, 10))) 16 | }) 17 | 18 | testthat::test_that("OLC decoding works for more-complex cases with lengths > 10", { 19 | result <- unlist(decode_olc("7FG49QCJ+2VX")) 20 | testthat::expect_that(format_num(result[c(3,4,7)]), is_equivalent_to(format_num(c(20.3700625, 2.7821875, 11)))) 21 | result <- unlist(decode_olc("7FG49QCJ+2VXGJ")) 22 | testthat::expect_that(format_num(result[c(3,4,7)]), is_equivalent_to(format_num(c(20.3700625, 2.7821875, 13)))) 23 | result <- unlist(decode_olc("8FVC2222+22")) 24 | testthat::expect_that(format_num(result[c(3,4,7)]), is_equivalent_to(format_num(c(47.0000625, 8.0000625, 10)))) 25 | }) 26 | 27 | testthat::test_that("Negative values can be decoded", { 28 | result <- unlist(decode_olc("4VCPPQGP+Q9")) 29 | testthat::expect_that(format_num(result[c(3,4,7)]), is_equivalent_to(format_num(c(-41.2730625, 174.7859375, 10)))) 30 | }) 31 | 32 | testthat::test_that("NA support exists", { 33 | result <- unlist(unname(decode_olc(NA))) 34 | testthat::expect_true(all(is.na(result))) 35 | }) 36 | -------------------------------------------------------------------------------- /tests/testthat/test_shortener.R: -------------------------------------------------------------------------------- 1 | testthat::context("Test OLC shortening and recovery") 2 | 3 | # https://github.com/google/open-location-code/blob/master/test_data/shortCodeTests.csv 4 | testthat::test_that("OLC shortening works for simple cases", { 5 | testthat::expect_equal(shorten_olc("9C3W9QCJ+2VX",51.3701125,-1.217765625), "+2VX") 6 | }) 7 | 8 | testthat::test_that("OLC shortening works +/- .000755", { 9 | testthat::expect_equal(shorten_olc("9C3W9QCJ+2VX",51.3708675,-1.217765625), "CJ+2VX") 10 | testthat::expect_equal(shorten_olc("9C3W9QCJ+2VX",51.3701125,-1.217010625), "CJ+2VX") 11 | }) 12 | 13 | testthat::test_that("OLC shortening works +/- .0151", { 14 | testthat::expect_equal(shorten_olc("9C3W9QCJ+2VX",51.3852125,-1.217765625), "9QCJ+2VX") 15 | testthat::expect_equal(shorten_olc("9C3W9QCJ+2VX",51.3550125,-1.217765625), "9QCJ+2VX") 16 | testthat::expect_equal(shorten_olc("9C3W9QCJ+2VX",51.3701125,-1.232865625), "9QCJ+2VX") 17 | testthat::expect_equal(shorten_olc("9C3W9QCJ+2VX",51.3701125,-1.202665625), "9QCJ+2VX") 18 | }) 19 | 20 | testthat::test_that("OLC recovery works",{ 21 | testthat::expect_equal(recover_olc("9G8F+6X", 47.4, 8.6), "8FVC9G8F+6X") 22 | }) 23 | 24 | testthat::test_that("The floating-point bug in the reference implementation does not regress", { 25 | testthat::expect_equal(recover_olc("22+", 42.899, 9.012), "8FJFW222+") 26 | testthat::expect_equal(recover_olc("22+", 14.95125, -23.5001), "796RXG22+") 27 | }) 28 | 29 | testthat::test_that("NA support works", { 30 | testthat::expect_true(is.na(recover_olc("22+", 14.95125, NA))) 31 | testthat::expect_true(is.na(recover_olc("22+", NA, 14.95125))) 32 | testthat::expect_true(is.na(recover_olc(NA, 14.95125, 14.95125))) 33 | 34 | }) 35 | -------------------------------------------------------------------------------- /vignettes/Introduction_to_olctools.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to olctools" 3 | author: "Oliver Keyes" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Introduction to olctools} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ## Open Location Codes in R 13 | `olctools` is an R package for handling [Open Location Codes](http://openlocationcode.com/), a Google standard for representing latitude and longitude pairs, to varying degrees of accuracy. The package allows you to encode, decode, shorten, lengthen and validate such codes. 14 | 15 | ### How Open Location Codes work 16 | 17 | Locations are usually represented as street addresses or latitude/longitude pairs. The problem is that street addresses only work 18 | where such a street address *exists* (try using it to describe unmapped areas) and latitude/longitude pairs are pretty big and unwieldy. 19 | 20 | Open Location Codes compress latitude/longitude pairs to Latin alphanumeric characters in base 20, allowing an area of the globe the size of a football pitch to be described in just 11 characters. For more information, see [the definition](https://github.com/google/open-location-code/blob/master/docs/olc_definition.adoc)! 21 | 22 | ### Encoding OLCs in R 23 | 24 | Open Location Codes come in two flavours; short, and full. Full codes refer to a specific, tiny area, while short codes (which are, uh. Shorter) exclude the larger-scale information in order to produce a smaller OLC, which can be used in conjunction with information about the general location in the same way a full code can. 25 | 26 | Producing a full code is easy, and just requires latitude and longitude values and an idea of how long you want your codes to be (in other words, how precise they are). The length is conventionally 10 or 11, but any number is acceptable, as long as it's not an odd number below 8: 27 | 28 | ```{r, eval=FALSE} 29 | library(olctools) 30 | # Encode the location of Mbgathi Road, in Nairobi, Kenya 31 | encode_olc(-1.314063, 36.79881, 10) 32 | 33 | # [1] "6GCRMQPX+9G" 34 | ``` 35 | 36 | Generating a shortened code just requires the full code and the original latitude/longitude pair: 37 | ```{r, eval=FALSE} 38 | shorten_olc("6GCRMQPX+9G", -1.314063, 36.79881) 39 | 40 | # [1] "+9G" 41 | ``` 42 | 43 | ### Validating and decoding OLCs 44 | 45 | You might generate codes, or you might have them given to you, and in that case it's nice to be able to check that they're valid. This 46 | is done with one of three functions; `validate_full`, which takes a vector and verifies if each code is a valid *full* Open Location Code, 47 | `validate_short`, which does the same for short OLCs, and `validate_olc`, which simply checks that each element of the vector is a valid false *or* short code. 48 | 49 | And once you've made sure they're valid, why not convert them back into latitude and longitude pairs? Because OLCs refer to a grid rather 50 | than a point, the output isn't a precise latitude/longitude pair, but instead 3 pairs, representing the lower left, centre and upper right 51 | of the box the code covers. These are returned as a data.frame, along with the length of the code: 52 | 53 | ```{r, eval=FALSE} 54 | str(decode_olc(c("6GCRMQPX+9G", "7FG49QCJ+2VX"))) 55 | 56 | # 'data.frame': 2 obs. of 7 variables: 57 | # $ latitude_low : num -1.31 20.37 58 | # $ longitude_low : num 36.8 2.78 59 | # $ latitude_center : num -1.31 20.37 60 | # $ longitude_center: num 36.8 2.78 61 | # $ latitude_high : num -1.31 20.37 62 | # $ longitude_high : num 36.8 2.78 63 | # $ code_lengths : int 10 11 64 | ``` 65 | 66 | Only full OLCs can be decoded, which leads us neatly on to... 67 | 68 | ### Lengthening OLCs 69 | 70 | As mentioned, OLCs can be shortened and compressed, at the loss of some accuracy, to make them easier to pass around; they can then be paired with a broader geographic location to be resolved back to a full code. `shorten_olc("8FVC9G8F+6X", 47.4, 8.6)` produces `9G8F+6X`; we can turn that back into a full OLC with `recover_olc`: 71 | 72 | ```{r, eval=FALSE} 73 | recover_olc("9G8F+6X", 47.4, 8.6) 74 | 75 | # [1] "8FVC9G8F+6X" 76 | ``` 77 | -------------------------------------------------------------------------------- /src/RcppExports.cpp: -------------------------------------------------------------------------------- 1 | // This file was generated by Rcpp::compileAttributes 2 | // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #include 5 | 6 | using namespace Rcpp; 7 | 8 | // validate_olc 9 | LogicalVector validate_olc(CharacterVector codes); 10 | RcppExport SEXP olctools_validate_olc(SEXP codesSEXP) { 11 | BEGIN_RCPP 12 | SEXP __sexp_result; 13 | { 14 | Rcpp::RNGScope __rngScope; 15 | Rcpp::traits::input_parameter< CharacterVector >::type codes(codesSEXP ); 16 | LogicalVector __result = validate_olc(codes); 17 | PROTECT(__sexp_result = Rcpp::wrap(__result)); 18 | } 19 | UNPROTECT(1); 20 | return __sexp_result; 21 | END_RCPP 22 | } 23 | // validate_short 24 | LogicalVector validate_short(CharacterVector codes); 25 | RcppExport SEXP olctools_validate_short(SEXP codesSEXP) { 26 | BEGIN_RCPP 27 | SEXP __sexp_result; 28 | { 29 | Rcpp::RNGScope __rngScope; 30 | Rcpp::traits::input_parameter< CharacterVector >::type codes(codesSEXP ); 31 | LogicalVector __result = validate_short(codes); 32 | PROTECT(__sexp_result = Rcpp::wrap(__result)); 33 | } 34 | UNPROTECT(1); 35 | return __sexp_result; 36 | END_RCPP 37 | } 38 | // validate_full 39 | LogicalVector validate_full(CharacterVector codes); 40 | RcppExport SEXP olctools_validate_full(SEXP codesSEXP) { 41 | BEGIN_RCPP 42 | SEXP __sexp_result; 43 | { 44 | Rcpp::RNGScope __rngScope; 45 | Rcpp::traits::input_parameter< CharacterVector >::type codes(codesSEXP ); 46 | LogicalVector __result = validate_full(codes); 47 | PROTECT(__sexp_result = Rcpp::wrap(__result)); 48 | } 49 | UNPROTECT(1); 50 | return __sexp_result; 51 | END_RCPP 52 | } 53 | // encode_olc 54 | CharacterVector encode_olc(NumericVector lats, NumericVector longs, IntegerVector length); 55 | RcppExport SEXP olctools_encode_olc(SEXP latsSEXP, SEXP longsSEXP, SEXP lengthSEXP) { 56 | BEGIN_RCPP 57 | SEXP __sexp_result; 58 | { 59 | Rcpp::RNGScope __rngScope; 60 | Rcpp::traits::input_parameter< NumericVector >::type lats(latsSEXP ); 61 | Rcpp::traits::input_parameter< NumericVector >::type longs(longsSEXP ); 62 | Rcpp::traits::input_parameter< IntegerVector >::type length(lengthSEXP ); 63 | CharacterVector __result = encode_olc(lats, longs, length); 64 | PROTECT(__sexp_result = Rcpp::wrap(__result)); 65 | } 66 | UNPROTECT(1); 67 | return __sexp_result; 68 | END_RCPP 69 | } 70 | // decode_olc 71 | DataFrame decode_olc(CharacterVector olcs); 72 | RcppExport SEXP olctools_decode_olc(SEXP olcsSEXP) { 73 | BEGIN_RCPP 74 | SEXP __sexp_result; 75 | { 76 | Rcpp::RNGScope __rngScope; 77 | Rcpp::traits::input_parameter< CharacterVector >::type olcs(olcsSEXP ); 78 | DataFrame __result = decode_olc(olcs); 79 | PROTECT(__sexp_result = Rcpp::wrap(__result)); 80 | } 81 | UNPROTECT(1); 82 | return __sexp_result; 83 | END_RCPP 84 | } 85 | // shorten_olc 86 | CharacterVector shorten_olc(CharacterVector olcs, NumericVector lats, NumericVector longs); 87 | RcppExport SEXP olctools_shorten_olc(SEXP olcsSEXP, SEXP latsSEXP, SEXP longsSEXP) { 88 | BEGIN_RCPP 89 | SEXP __sexp_result; 90 | { 91 | Rcpp::RNGScope __rngScope; 92 | Rcpp::traits::input_parameter< CharacterVector >::type olcs(olcsSEXP ); 93 | Rcpp::traits::input_parameter< NumericVector >::type lats(latsSEXP ); 94 | Rcpp::traits::input_parameter< NumericVector >::type longs(longsSEXP ); 95 | CharacterVector __result = shorten_olc(olcs, lats, longs); 96 | PROTECT(__sexp_result = Rcpp::wrap(__result)); 97 | } 98 | UNPROTECT(1); 99 | return __sexp_result; 100 | END_RCPP 101 | } 102 | // recover_olc 103 | CharacterVector recover_olc(CharacterVector olcs, NumericVector lats, NumericVector longs); 104 | RcppExport SEXP olctools_recover_olc(SEXP olcsSEXP, SEXP latsSEXP, SEXP longsSEXP) { 105 | BEGIN_RCPP 106 | SEXP __sexp_result; 107 | { 108 | Rcpp::RNGScope __rngScope; 109 | Rcpp::traits::input_parameter< CharacterVector >::type olcs(olcsSEXP ); 110 | Rcpp::traits::input_parameter< NumericVector >::type lats(latsSEXP ); 111 | Rcpp::traits::input_parameter< NumericVector >::type longs(longsSEXP ); 112 | CharacterVector __result = recover_olc(olcs, lats, longs); 113 | PROTECT(__sexp_result = Rcpp::wrap(__result)); 114 | } 115 | UNPROTECT(1); 116 | return __sexp_result; 117 | END_RCPP 118 | } 119 | -------------------------------------------------------------------------------- /src/validate.cpp: -------------------------------------------------------------------------------- 1 | #include "validate.h" 2 | 3 | bool olc_validate::olc_check_single(std::string olc){ 4 | 5 | //Scan for illegal characters 6 | unsigned int input_size = olc.size(); 7 | if(input_size == 0){ 8 | return false; 9 | } else { 10 | 11 | for(unsigned int i = 0; i < input_size; i++){ 12 | if(valid_chars.find(toupper(olc[i])) == std::string::npos){ 13 | return false; 14 | 15 | } 16 | } 17 | 18 | } 19 | 20 | //Check that the separator is present 21 | size_t separator_location = olc.find(separator); 22 | if(separator_location == std::string::npos){ 23 | return false; 24 | } 25 | 26 | //Are there >1? Also a no-no 27 | if(separator_location != olc.rfind(separator)){ 28 | return false; 29 | 30 | } 31 | 32 | //Is it present in a valid location? 33 | if(separator_location > separator_position || separator_location % 2 == 1){ 34 | return false; 35 | } 36 | 37 | //Is there only 1 character after the separator? (you don't need to have chars but 38 | //you can't have just 1) 39 | //(C++ is 0-offset and that includes find() results. Sigh.) 40 | if((olc.size() - separator_location) == 2){ 41 | return false; 42 | } 43 | 44 | //Check if padding is present 45 | size_t regex_result = olc.find(padding); 46 | if(regex_result == 0){ 47 | return false; 48 | } else if(regex_result != std::string::npos){ 49 | 50 | signed int last_loc = regex_result; 51 | int match_sum = 1; 52 | 53 | while(regex_result != std::string::npos){ 54 | regex_result = olc.find(padding, regex_result + 1); 55 | if(regex_result != std::string::npos){ 56 | if(last_loc + 1 != (signed int) regex_result){ 57 | return false; 58 | } 59 | match_sum++; 60 | } 61 | } 62 | 63 | if(match_sum % 2 == 1){ 64 | return false; 65 | } 66 | 67 | if(separator_location != (olc.size() - 1)){ 68 | return false; 69 | } 70 | } 71 | 72 | return true; 73 | } 74 | 75 | bool olc_validate::olc_check_full_single(std::string olc){ 76 | 77 | //Run a character check first to avoid being silly. 78 | //If it's not valid, or is a valid short, it's not a valid full. 79 | if(!olc_check_single(olc) || olc_check_short_single(olc)){ 80 | return false; 81 | } 82 | 83 | //Retrieve the first latitude value (the first value), decode 84 | //it into numeric, and see if it's >= 180. If so, unpossible. 85 | if( (signed int) (character_set.find(olc[0]) * charset_length) >= (max_latitude * 2)){ 86 | return false; 87 | } 88 | 89 | //Do the same for longitude, only this time >=360 90 | if(olc.size() > 1){ 91 | if( (signed int) (character_set.find(olc[1]) * charset_length) >= (max_longitude * 2)){ 92 | return false; 93 | } 94 | } 95 | 96 | return true; 97 | } 98 | 99 | bool olc_validate::olc_check_short_single(std::string olc){ 100 | 101 | //Run a character check first to avoid being silly. 102 | if(!olc_check_single(olc)){ 103 | return false; 104 | } 105 | 106 | //If the separator is < separator_position..it's a short. 107 | if(olc.find(separator) != std::string::npos && (unsigned int) olc.find(separator) < separator_position){ 108 | return true; 109 | }; 110 | 111 | //Otherwise, false. 112 | return false; 113 | } 114 | 115 | bool olc_validate::olc_check_either_single(std::string olc){ 116 | 117 | if(olc_check_full_single(olc) || olc_check_short_single(olc)){ 118 | return true; 119 | } 120 | 121 | return false; 122 | } 123 | 124 | LogicalVector olc_validate::olc_check_full_vector(CharacterVector olc){ 125 | unsigned int input_size = olc.size(); 126 | LogicalVector output(input_size); 127 | 128 | for(unsigned int i = 0; i < input_size; i++){ 129 | if((i % 10000) == 0){ 130 | Rcpp::checkUserInterrupt(); 131 | } 132 | if(olc[i] == NA_STRING){ 133 | output[i] = NA_LOGICAL; 134 | } else { 135 | output[i] = olc_check_full_single(Rcpp::as(olc[i])); 136 | } 137 | } 138 | 139 | return output; 140 | } 141 | 142 | LogicalVector olc_validate::olc_check_short_vector(CharacterVector olc){ 143 | unsigned int input_size = olc.size(); 144 | LogicalVector output(input_size); 145 | 146 | for(unsigned int i = 0; i < input_size; i++){ 147 | if((i % 10000) == 0){ 148 | Rcpp::checkUserInterrupt(); 149 | } 150 | if(olc[i] == NA_STRING){ 151 | output[i] = NA_LOGICAL; 152 | } else { 153 | output[i] = olc_check_short_single(Rcpp::as(olc[i])); 154 | } 155 | } 156 | 157 | return output; 158 | } 159 | 160 | LogicalVector olc_validate::olc_check_either_vector(CharacterVector olc){ 161 | unsigned int input_size = olc.size(); 162 | LogicalVector output(input_size); 163 | 164 | for(unsigned int i = 0; i < input_size; i++){ 165 | if((i % 10000) == 0){ 166 | Rcpp::checkUserInterrupt(); 167 | } 168 | if(olc[i] == NA_STRING){ 169 | output[i] = NA_LOGICAL; 170 | } else { 171 | output[i] = olc_check_either_single(Rcpp::as(olc[i])); 172 | } 173 | } 174 | 175 | return output; 176 | } 177 | 178 | olc_validate::olc_validate(){ 179 | valid_chars = "CFGHJMPQRVWX23456789+0"; 180 | separator = "+"; 181 | separator_position = 8; 182 | padding = "0"; 183 | character_set = "23456789CFGHJMPQRVWX"; 184 | charset_length = character_set.size(); 185 | max_latitude = 90; 186 | max_longitude = 180; 187 | } 188 | -------------------------------------------------------------------------------- /src/manipulate.cpp: -------------------------------------------------------------------------------- 1 | #include "manipulate.h" 2 | 3 | std::string olc_manipulate::shorten_single(std::string olc, double latitude, double longitude){ 4 | if(!olc_check_full_single(olc)){ 5 | throw std::range_error("The Open Location Codes provided must be complete. Incomplete code: " + olc); 6 | } 7 | 8 | if(olc.find(padding) != std::string::npos){ 9 | throw std::range_error("The Open Location Codes provided cannot have padding characters. Padded code: " + olc); 10 | } 11 | 12 | for(unsigned int i = 0; i < olc.size(); i++){ 13 | olc[i] = toupper(olc[i]); 14 | } 15 | 16 | std::vector < double > decoded_code = olc_decode_single(olc); 17 | if(decoded_code[6] < min_trim_length){ 18 | throw std::range_error("Open Location Codes must be >6 in length to be shortened. Offending code: " + olc); 19 | } 20 | 21 | longitude = clip_longitude(longitude); 22 | latitude = clip_lat(latitude); 23 | double range = std::max(std::abs((double) decoded_code[5] - longitude), std::abs((double) decoded_code[4] - latitude)); 24 | 25 | for(unsigned int i = resolution_levels.size() - 2; i >= 1; i--){ 26 | if(range < (resolution_levels[i] * 0.3)){ 27 | return olc.substr((i+1)*2); 28 | } 29 | } 30 | return olc; 31 | } 32 | 33 | std::string olc_manipulate::recover_single(std::string olc, double latitude, double longitude){ 34 | 35 | if(!olc_check_short_single(olc)){ 36 | if(olc_check_full_single(olc)){ 37 | return olc; 38 | } 39 | throw std::range_error("codes provided to recover_olc must be valid short Open Location Codes. Offending code: " + olc); 40 | } 41 | 42 | double ref_longitude = clip_longitude(longitude); 43 | double ref_latitude = clip_lat(latitude); 44 | 45 | for(unsigned int i = 0; i < olc.size(); i++){ 46 | olc[i] = toupper(olc[i]); 47 | } 48 | 49 | int padding_length = (separator_position - olc.find(separator)); 50 | double resolution = pow(20.0, (2.0 - (padding_length / 2.0))); 51 | double area_to_edge = resolution / 2.0; 52 | 53 | std::vector < double > code_area = olc_decode_single( 54 | olc_encode_single(ref_latitude, ref_longitude, max_pair_length).substr(0, padding_length) + olc 55 | ); 56 | 57 | double degrees_difference = (code_area[4] - ref_latitude); 58 | if(degrees_difference > area_to_edge){ 59 | code_area[4] -= resolution; 60 | } else if(degrees_difference < -area_to_edge){ 61 | code_area[4] += resolution; 62 | } 63 | 64 | degrees_difference = (code_area[5] - ref_longitude); 65 | if(degrees_difference > area_to_edge){ 66 | code_area[5] -= resolution; 67 | } else if(degrees_difference < -area_to_edge){ 68 | code_area[5] += resolution; 69 | } 70 | 71 | return(olc_encode_single(code_area[4], code_area[5], code_area[6])); 72 | } 73 | 74 | CharacterVector olc_manipulate::shorten_vector(CharacterVector olc, NumericVector latitude, NumericVector longitude){ 75 | 76 | if(latitude.size() != longitude.size()){ 77 | throw std::range_error("There must be as many longitude values as latitude values"); 78 | } 79 | 80 | unsigned int input_size = olc.size(); 81 | CharacterVector output(input_size); 82 | 83 | if(latitude.size() == 1){ 84 | 85 | for(unsigned int i = 0; i < input_size; i++){ 86 | if((i % 10000) == 0){ 87 | Rcpp::checkUserInterrupt(); 88 | } 89 | 90 | if(CharacterVector::is_na(olc[i]) || NumericVector::is_na(latitude[0]) || NumericVector::is_na(longitude[0])){ 91 | output[i] = NA_STRING; 92 | } else { 93 | output[i] = shorten_single(Rcpp::as(olc[i]), latitude[0], longitude[0]); 94 | } 95 | } 96 | 97 | } else if(latitude.size() == input_size){ 98 | 99 | for(unsigned int i = 0; i < input_size; i++){ 100 | if((i % 10000) == 0){ 101 | Rcpp::checkUserInterrupt(); 102 | } 103 | 104 | if(CharacterVector::is_na(olc[i]) || NumericVector::is_na(latitude[i]) || NumericVector::is_na(longitude[i])){ 105 | output[i] = NA_STRING; 106 | } else { 107 | output[i] = shorten_single(Rcpp::as(olc[i]), latitude[i], longitude[i]); 108 | } 109 | } 110 | 111 | } else { 112 | throw std::range_error("the latitude and longitude parameters must contain either one value, or one value for each OLC"); 113 | } 114 | 115 | return output; 116 | } 117 | 118 | CharacterVector olc_manipulate::recover_vector(CharacterVector olc, NumericVector latitude, 119 | NumericVector longitude){ 120 | 121 | if(latitude.size() != longitude.size()){ 122 | throw std::range_error("There must be as many longitude values as latitude values"); 123 | } 124 | 125 | unsigned int input_size = olc.size(); 126 | CharacterVector output(input_size); 127 | 128 | if(latitude.size() == 1){ 129 | 130 | for(unsigned int i = 0; i < input_size; i++){ 131 | if((i % 10000) == 0){ 132 | Rcpp::checkUserInterrupt(); 133 | } 134 | if(CharacterVector::is_na(olc[i]) || NumericVector::is_na(latitude[0]) || NumericVector::is_na(longitude[0])){ 135 | output[i] = NA_STRING; 136 | } else { 137 | output[i] = recover_single(Rcpp::as(olc[i]), latitude[0], longitude[0]); 138 | } 139 | 140 | } 141 | 142 | } else if(latitude.size() == input_size){ 143 | 144 | for(unsigned int i = 0; i < input_size; i++){ 145 | if((i % 10000) == 0){ 146 | Rcpp::checkUserInterrupt(); 147 | } 148 | if(CharacterVector::is_na(olc[i]) || NumericVector::is_na(latitude[i]) || NumericVector::is_na(longitude[i])){ 149 | output[i] = NA_STRING; 150 | } else { 151 | output[i] = recover_single(Rcpp::as(olc[i]), latitude[i], longitude[i]); 152 | } 153 | } 154 | 155 | } else { 156 | throw std::range_error("the latitude and longitude parameters must contain either one value, or one value for each OLC"); 157 | } 158 | 159 | return output; 160 | } 161 | 162 | olc_manipulate::olc_manipulate(){ 163 | min_trim_length = 6; 164 | } 165 | -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # This file was generated by Rcpp::compileAttributes 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #'@title Check the Validity of Open Location Codes 5 | #'@description These functions allow a useR to check whether OLCs they've 6 | #'been provided are valid or not. \code{valid_short} identifies whether 7 | #'a vector of OLCs are valid "short" codes; \code{valid_long} identifies 8 | #'whether OLCs are valid "long" codes, and \code{valid_full} identifies 9 | #'whether OLCs are valid, full stop. 10 | #' 11 | #'@param codes a character vector containing Open Location Codes. 12 | #' 13 | #'@return a vector of TRUE and FALSE values, where TRUE corresponds to a 14 | #'valid code and FALSE an invalid. 15 | #' 16 | #'@seealso \code{\link{decode_olc}} and \code{\link{encode_olc}} for creating 17 | #'and resolving valid Open Location Codes. 18 | #' 19 | #'@examples 20 | #'#Validate that a particular OLC is valid 21 | #'validate_olc("WC2345+G6g") 22 | #'#[1] TRUE 23 | #' 24 | #'#It is! Is it a short? 25 | #'validate_short("WC2345+G6g") 26 | #'#[1] TRUE 27 | #'#Yep! 28 | #' 29 | #'#So it's not full? 30 | #'validate_full("WC2345+G6g") 31 | #'#[1] FALSE 32 | #'#Nope! 33 | #'@export 34 | #'@aliases olc_validate 35 | #'@rdname olc_validate 36 | validate_olc <- function(codes) { 37 | .Call('olctools_validate_olc', PACKAGE = 'olctools', codes) 38 | } 39 | 40 | #'@rdname olc_validate 41 | #'@export 42 | validate_short <- function(codes) { 43 | .Call('olctools_validate_short', PACKAGE = 'olctools', codes) 44 | } 45 | 46 | #'@rdname olc_validate 47 | #'@export 48 | validate_full <- function(codes) { 49 | .Call('olctools_validate_full', PACKAGE = 'olctools', codes) 50 | } 51 | 52 | #'@title Encode Latitude and Longitude Pairs as Open Location Codes 53 | #'@description \code{encode_olc} creates Open Location Codes from 54 | #'latitude and longitude values, of a specified length. 55 | #' 56 | #'@param lats a numeric vector of latitudes. 57 | #' 58 | #'@param longs a numeric vector of longitudes, equivalent in size to \code{lats} 59 | #' 60 | #'@param length the length you want the resulting OLCs to be. The conventional lengths 61 | #'are 10 or 11, with any number above 8 and any \emph{even} number below it being acceptable. \code{length} 62 | #'should consist of either a single value, if you want all codes to be calculated to the same length, or a 63 | #'vector of values the same size as \code{lats} and \code{longs} if you want to pre-set values. 64 | #' 65 | #'@seealso \code{\link{decode_olc}} for the opposite operation, and \code{\link{shorten_olc}} to convert 66 | #'"full" Open Location Codes to "short" Open Location Codes. 67 | #' 68 | #'@examples 69 | #'encode_olc(20.375, 2.775,6) 70 | #' 71 | #'@export 72 | encode_olc <- function(lats, longs, length) { 73 | .Call('olctools_encode_olc', PACKAGE = 'olctools', lats, longs, length) 74 | } 75 | 76 | #'@title Decode Open Location Codes into Latitude and Longitude Pairs 77 | #'@description \code{decode_olc} takes Open Location Codes and, if they're 78 | #'valid (see \code{\link{validate_full}}) returns the minium, centred and maximum 79 | #'latitude and longitude for those coordinates. 80 | #' 81 | #'@param olcs a vector of Open Location Codes, generated through \code{encode_olc} or 82 | #'an equivalent tool. 83 | #' 84 | #'@examples 85 | #'decode_olc("7FG49Q00+") 86 | #' 87 | #'@seealso \code{\link{encode_olc}} for the opposite operation, and \code{\link{shorten_olc}} to convert 88 | #'"full" Open Location Codes to "short" Open Location Codes. 89 | #' 90 | #'@export 91 | decode_olc <- function(olcs) { 92 | .Call('olctools_decode_olc', PACKAGE = 'olctools', olcs) 93 | } 94 | 95 | #'@title Shorten Full Open Location Codes 96 | #'@description One of the things that makes OLCs useful is that they can shortened - you can trim 97 | #'characters off them, saving space without substantially compromising the accuracy. \code{shorten_olc} 98 | #'takes full-length OLCs (generated with \code{\link{encode_olc}} or any other way) and shortens them. 99 | #' 100 | #'@param olcs a vector of open location codes, generated with \code{\link{encode_olc}} or through 101 | #'any other means. 102 | #' 103 | #'@param lats a numeric vector of latitudes. 104 | #' 105 | #'@param longs a numeric vector of longitudes, equivalent in size to \code{lats}. 106 | #' 107 | #'@seealso \code{\link{encode_olc}} to create full Open Location Codes. 108 | #'@examples 109 | #'#Encode an OLC and then shorten it 110 | #'olc <- encode_olc(51.3708675,-1.217765625, 12) 111 | #'validate_full(olc) 112 | #'# [1] TRUE 113 | #' 114 | #'olc <- shorten_olc(olc, 51.3708675,-1.217765625) 115 | #'validate_short(olc) 116 | #'# [1] TRUE 117 | #' 118 | #'@export 119 | shorten_olc <- function(olcs, lats, longs) { 120 | .Call('olctools_shorten_olc', PACKAGE = 'olctools', olcs, lats, longs) 121 | } 122 | 123 | #'@title Recover Full Open Location Codes From Shortened Codes 124 | #'@description \code{\link{shorten_olc}} (and other sources) shorten a code, reducing 125 | #'the space it occupies. They also limit its ability to be translated back into latitude/longitude 126 | #'pairs. \code{recover_olc} recovers a full code from a shortened one, allowing it to be decoded with 127 | #'\code{\link{decode_olc}}. \emph{Some} loss of accuracy or precision is expected - and as it finds 128 | #'the closest match to the coordinates rather than to the original code, the characters may be very 129 | #'different. 130 | #' 131 | #'@param olcs a vector of short open location codes, generated with \code{\link{shorten_olc}} or through 132 | #'any other means. 133 | #' 134 | #'@param lats a numeric vector of latitudes. 135 | #' 136 | #'@param longs a numeric vector of longitudes, equivalent in size to \code{lats}. 137 | #' 138 | #'@examples 139 | #'# Shorten an OLC and then recover the nearest full code. Note the actual characters differ. 140 | #'shortened_code <- shorten_olc("8FVC9G8F+6X", 47.5, 8.5); 141 | #'recovered_code <- recover_olc(shortened_code, 47.4, 8.6); 142 | #' 143 | #'@export 144 | recover_olc <- function(olcs, lats, longs) { 145 | .Call('olctools_recover_olc', PACKAGE = 'olctools', olcs, lats, longs) 146 | } 147 | 148 | -------------------------------------------------------------------------------- /src/olctools.cpp: -------------------------------------------------------------------------------- 1 | #include "validate.h" 2 | #include "coders.h" 3 | #include "manipulate.h" 4 | 5 | using namespace Rcpp; 6 | 7 | //'@title Check the Validity of Open Location Codes 8 | //'@description These functions allow a useR to check whether OLCs they've 9 | //'been provided are valid or not. \code{valid_short} identifies whether 10 | //'a vector of OLCs are valid "short" codes; \code{valid_long} identifies 11 | //'whether OLCs are valid "long" codes, and \code{valid_full} identifies 12 | //'whether OLCs are valid, full stop. 13 | //' 14 | //'@param codes a character vector containing Open Location Codes. 15 | //' 16 | //'@return a vector of TRUE and FALSE values, where TRUE corresponds to a 17 | //'valid code and FALSE an invalid. 18 | //' 19 | //'@seealso \code{\link{decode_olc}} and \code{\link{encode_olc}} for creating 20 | //'and resolving valid Open Location Codes. 21 | //' 22 | //'@examples 23 | //'#Validate that a particular OLC is valid 24 | //'validate_olc("WC2345+G6g") 25 | //'#[1] TRUE 26 | //' 27 | //'#It is! Is it a short? 28 | //'validate_short("WC2345+G6g") 29 | //'#[1] TRUE 30 | //'#Yep! 31 | //' 32 | //'#So it's not full? 33 | //'validate_full("WC2345+G6g") 34 | //'#[1] FALSE 35 | //'#Nope! 36 | //'@export 37 | //'@aliases olc_validate 38 | //'@rdname olc_validate 39 | //[[Rcpp::export]] 40 | LogicalVector validate_olc(CharacterVector codes){ 41 | olc_validate validate_inst; 42 | return validate_inst.olc_check_either_vector(codes); 43 | } 44 | 45 | //'@rdname olc_validate 46 | //'@export 47 | //[[Rcpp::export]] 48 | LogicalVector validate_short(CharacterVector codes){ 49 | olc_validate validate_inst; 50 | return validate_inst.olc_check_short_vector(codes); 51 | } 52 | 53 | //'@rdname olc_validate 54 | //'@export 55 | //[[Rcpp::export]] 56 | LogicalVector validate_full(CharacterVector codes){ 57 | olc_validate validate_inst; 58 | return validate_inst.olc_check_full_vector(codes); 59 | } 60 | 61 | //'@title Encode Latitude and Longitude Pairs as Open Location Codes 62 | //'@description \code{encode_olc} creates Open Location Codes from 63 | //'latitude and longitude values, of a specified length. 64 | //' 65 | //'@param lats a numeric vector of latitudes. 66 | //' 67 | //'@param longs a numeric vector of longitudes, equivalent in size to \code{lats} 68 | //' 69 | //'@param length the length you want the resulting OLCs to be. The conventional lengths 70 | //'are 10 or 11, with any number above 8 and any \emph{even} number below it being acceptable. \code{length} 71 | //'should consist of either a single value, if you want all codes to be calculated to the same length, or a 72 | //'vector of values the same size as \code{lats} and \code{longs} if you want to pre-set values. 73 | //' 74 | //'@seealso \code{\link{decode_olc}} for the opposite operation, and \code{\link{shorten_olc}} to convert 75 | //'"full" Open Location Codes to "short" Open Location Codes. 76 | //' 77 | //'@examples 78 | //'encode_olc(20.375, 2.775,6) 79 | //' 80 | //'@export 81 | //[[Rcpp::export]] 82 | CharacterVector encode_olc(NumericVector lats, NumericVector longs, IntegerVector length){ 83 | olc_coders code_inst; 84 | return code_inst.olc_encode_vector(lats, longs, length); 85 | } 86 | 87 | //'@title Decode Open Location Codes into Latitude and Longitude Pairs 88 | //'@description \code{decode_olc} takes Open Location Codes and, if they're 89 | //'valid (see \code{\link{validate_full}}) returns the minium, centred and maximum 90 | //'latitude and longitude for those coordinates. 91 | //' 92 | //'@param olcs a vector of Open Location Codes, generated through \code{encode_olc} or 93 | //'an equivalent tool. 94 | //' 95 | //'@examples 96 | //'decode_olc("7FG49Q00+") 97 | //' 98 | //'@seealso \code{\link{encode_olc}} for the opposite operation, and \code{\link{shorten_olc}} to convert 99 | //'"full" Open Location Codes to "short" Open Location Codes. 100 | //' 101 | //'@export 102 | //[[Rcpp::export]] 103 | DataFrame decode_olc(CharacterVector olcs){ 104 | olc_coders code_inst; 105 | return code_inst.olc_decode_vector(olcs); 106 | } 107 | 108 | //'@title Shorten Full Open Location Codes 109 | //'@description One of the things that makes OLCs useful is that they can shortened - you can trim 110 | //'characters off them, saving space without substantially compromising the accuracy. \code{shorten_olc} 111 | //'takes full-length OLCs (generated with \code{\link{encode_olc}} or any other way) and shortens them. 112 | //' 113 | //'@param olcs a vector of open location codes, generated with \code{\link{encode_olc}} or through 114 | //'any other means. 115 | //' 116 | //'@param lats a numeric vector of latitudes. 117 | //' 118 | //'@param longs a numeric vector of longitudes, equivalent in size to \code{lats}. 119 | //' 120 | //'@seealso \code{\link{encode_olc}} to create full Open Location Codes. 121 | //'@examples 122 | //'#Encode an OLC and then shorten it 123 | //'olc <- encode_olc(51.3708675,-1.217765625, 12) 124 | //'validate_full(olc) 125 | //'# [1] TRUE 126 | //' 127 | //'olc <- shorten_olc(olc, 51.3708675,-1.217765625) 128 | //'validate_short(olc) 129 | //'# [1] TRUE 130 | //' 131 | //'@export 132 | //[[Rcpp::export]] 133 | CharacterVector shorten_olc(CharacterVector olcs, NumericVector lats, NumericVector longs){ 134 | olc_manipulate manip_inst; 135 | return manip_inst.shorten_vector(olcs, lats, longs); 136 | } 137 | 138 | //'@title Recover Full Open Location Codes From Shortened Codes 139 | //'@description \code{\link{shorten_olc}} (and other sources) shorten a code, reducing 140 | //'the space it occupies. They also limit its ability to be translated back into latitude/longitude 141 | //'pairs. \code{recover_olc} recovers a full code from a shortened one, allowing it to be decoded with 142 | //'\code{\link{decode_olc}}. \emph{Some} loss of accuracy or precision is expected - and as it finds 143 | //'the closest match to the coordinates rather than to the original code, the characters may be very 144 | //'different. 145 | //' 146 | //'@param olcs a vector of short open location codes, generated with \code{\link{shorten_olc}} or through 147 | //'any other means. 148 | //' 149 | //'@param lats a numeric vector of latitudes. 150 | //' 151 | //'@param longs a numeric vector of longitudes, equivalent in size to \code{lats}. 152 | //' 153 | //'@examples 154 | //'# Shorten an OLC and then recover the nearest full code. Note the actual characters differ. 155 | //'shortened_code <- shorten_olc("8FVC9G8F+6X", 47.5, 8.5); 156 | //'recovered_code <- recover_olc(shortened_code, 47.4, 8.6); 157 | //' 158 | //'@export 159 | //[[Rcpp::export]] 160 | CharacterVector recover_olc(CharacterVector olcs, NumericVector lats, NumericVector longs){ 161 | olc_manipulate manip_inst; 162 | return manip_inst.recover_vector(olcs, lats, longs); 163 | } 164 | -------------------------------------------------------------------------------- /src/coders.cpp: -------------------------------------------------------------------------------- 1 | #include "coders.h" 2 | 3 | double olc_coders::clip_lat(double lat){ 4 | 5 | if(lat < -90){ 6 | return -90; 7 | } 8 | 9 | if(lat > 90){ 10 | return 90; 11 | } 12 | 13 | return lat; 14 | } 15 | 16 | double olc_coders::clip_longitude(double longitude){ 17 | 18 | while(longitude < -180){ 19 | longitude = longitude + 360; 20 | } 21 | 22 | while(longitude >= 180){ 23 | longitude = longitude - 360; 24 | } 25 | 26 | return longitude; 27 | } 28 | 29 | double olc_coders::lat_precision(unsigned int length){ 30 | if(length < max_pair_length){ 31 | return pow(20.0, floor(-(length/2.0) + 2.0)); 32 | } 33 | return pow(20.0, -3.0)/pow( (double) grid_rows, (double) (length - max_pair_length)); 34 | } 35 | 36 | std::string olc_coders::olc_encode_single(double lat, double longitude, unsigned int output_length){ 37 | 38 | if(output_length < 2 || (output_length < separator_position && output_length % 2 == 1)){ 39 | throw std::range_error("The length value you have provided is not valid; see the documentation"); 40 | } 41 | 42 | std::string output; 43 | lat = clip_lat(lat); 44 | longitude = clip_longitude(longitude); 45 | 46 | if(lat == 90){ 47 | lat = lat_precision(output_length); 48 | } 49 | 50 | unsigned int to_encode_length = fmin(output_length, max_pair_length); 51 | double adjusted_lat = lat + max_latitude; 52 | double adjusted_long = longitude + max_longitude; 53 | int digit_value = 0; 54 | unsigned int digit_count = 0; 55 | double place_value; 56 | 57 | while(digit_count < to_encode_length){ 58 | 59 | //Lat first 60 | place_value = resolution_levels[floor(digit_count/2.0)]; 61 | digit_value = floor(adjusted_lat/place_value); 62 | adjusted_lat -= digit_value * place_value; 63 | output += character_set[digit_value]; 64 | digit_count++; 65 | 66 | //Long 67 | digit_value = floor(adjusted_long/place_value); 68 | adjusted_long -= digit_value * place_value; 69 | output += character_set[digit_value]; 70 | digit_count++; 71 | 72 | if(digit_count == separator_position && digit_count < to_encode_length) { 73 | output += "+"; 74 | } 75 | } 76 | 77 | if((unsigned int) output.size() < separator_position){ 78 | while((unsigned int) output.size() < separator_position){ 79 | output += padding; 80 | } 81 | } 82 | 83 | if((unsigned int) output.size() == separator_position){ 84 | output += separator; 85 | } 86 | 87 | if(output_length > max_pair_length){ 88 | 89 | int additional_length = output_length - max_pair_length; 90 | double lat_values = grid_degrees; 91 | double long_values = grid_degrees; 92 | double adjusted_latitude = fmod((lat + max_latitude), lat_values); 93 | double adjusted_longitude = fmod((longitude + max_longitude), long_values); 94 | 95 | for(signed int i = 0; i < additional_length; i++){ 96 | int row = floor(adjusted_latitude/(lat_values/grid_rows)); 97 | int col = floor(adjusted_longitude/(long_values/grid_cols)); 98 | lat_values = (lat_values/grid_rows); 99 | long_values = (long_values/grid_cols); 100 | 101 | adjusted_latitude -= row * lat_values; 102 | adjusted_longitude -= col * long_values; 103 | output += character_set[row * grid_cols + col]; 104 | } 105 | 106 | } 107 | return output; 108 | } 109 | 110 | std::vector < double > olc_coders::olc_decode_pair(std::string code, int offset){ 111 | 112 | unsigned int inval = 0; 113 | unsigned int input_length = code.size(); 114 | double output_value = 0; 115 | std::vector < double > output(2); 116 | 117 | while((inval * 2 + offset) < input_length){ 118 | output_value += (character_set.find(code[inval * 2 + offset]) * resolution_levels[inval]); 119 | inval++; 120 | } 121 | 122 | output[0] = output_value; 123 | output[1] = (output_value + resolution_levels[(inval - 1)]); 124 | return output; 125 | } 126 | 127 | std::vector < double > olc_coders::olc_decode_grid(std::string code){ 128 | double latitude_low = 0.0; 129 | double longitude_low = 0.0; 130 | double lat_place_value = grid_degrees; 131 | double long_place_value = grid_degrees; 132 | int row = 0; 133 | int col = 0; 134 | int code_index = 0.0; 135 | std::vector < double > output(4); 136 | 137 | for(unsigned int i = 0; i < code.size(); i++){ 138 | code_index = character_set.find(code[i]); 139 | row = floor((double) code_index/ (double) grid_cols); 140 | col = code_index % grid_cols; 141 | lat_place_value = (lat_place_value / grid_rows); 142 | long_place_value = (long_place_value / grid_cols); 143 | latitude_low += (row * lat_place_value); 144 | longitude_low += (col * long_place_value); 145 | } 146 | 147 | output.push_back(latitude_low); 148 | output.push_back(latitude_low + lat_place_value); 149 | output.push_back(longitude_low); 150 | output.push_back(longitude_low + long_place_value); 151 | return output; 152 | } 153 | 154 | std::vector < double > olc_coders::olc_decode_single(std::string olc){ 155 | 156 | if(!olc_check_full_single(olc)){ 157 | throw std::range_error("The Open Location Codes provided must be complete. Incomplete code: " + olc); 158 | } 159 | 160 | //Remove separator and padding character, upper-case 161 | std::string validated_olc; 162 | for(unsigned int i = 0; i < olc.size(); i++){ 163 | if(olc[i] != padding[0] && olc[i] != separator[0]){ 164 | validated_olc.push_back(toupper(olc[i])); 165 | } 166 | } 167 | 168 | //Decode the pairs 169 | std::vector < double > output; 170 | std::vector < double > holding = olc_decode_pair(validated_olc.substr(0, max_pair_length), 0); 171 | holding[0] -= max_latitude; 172 | holding[1] -= max_latitude; 173 | output.insert(output.end(), holding.begin(), holding.end()); 174 | 175 | holding = olc_decode_pair(validated_olc.substr(0, max_pair_length), 1); 176 | holding[0] -= max_longitude; 177 | holding[1] -= max_longitude; 178 | output.insert(output.end(), holding.begin(), holding.end()); 179 | 180 | if(validated_olc.size() > max_pair_length){ 181 | std::vector < double > grid_decode_results = olc_decode_grid(validated_olc.substr(max_pair_length)); 182 | for(unsigned int i = 0; i < 4; i++){ 183 | output[i] += grid_decode_results[i]; 184 | } 185 | } 186 | 187 | output.push_back(std::min((output[0] + (output[1] - output[0])/2), (double) max_latitude)); 188 | output.push_back(std::min((output[2] + (output[3] - output[2])/2), (double) max_longitude)); 189 | output.push_back(validated_olc.size()); 190 | 191 | return output; 192 | } 193 | 194 | CharacterVector olc_coders::olc_encode_vector(NumericVector latitude, NumericVector longitude, 195 | IntegerVector code_length){ 196 | 197 | if(latitude.size() != longitude.size()){ 198 | throw std::range_error("There must be as many longitude values as latitude values"); 199 | } 200 | 201 | unsigned int input_size = latitude.size(); 202 | CharacterVector output(input_size); 203 | 204 | if(code_length.size() == 1){ 205 | if(IntegerVector::is_na(code_length[0])){ 206 | for(unsigned int i = 0; i < input_size; i++){ 207 | if((i % 10000) == 0){ 208 | Rcpp::checkUserInterrupt(); 209 | } 210 | output[i] = NA_STRING; 211 | } 212 | } else { 213 | for(unsigned int i = 0; i < input_size; i++){ 214 | if((i % 10000) == 0){ 215 | Rcpp::checkUserInterrupt(); 216 | } 217 | if(NumericVector::is_na(latitude[i]) || NumericVector::is_na(longitude[i])){ 218 | output[i] = NA_STRING; 219 | } else { 220 | output[i] = olc_encode_single(latitude[i], longitude[i], code_length[0]); 221 | } 222 | } 223 | } 224 | 225 | } else if(code_length.size() == input_size){ 226 | 227 | for(unsigned int i = 0; i < input_size; i++){ 228 | if((i % 10000) == 0){ 229 | Rcpp::checkUserInterrupt(); 230 | } 231 | 232 | if(NumericVector::is_na(latitude[i]) || NumericVector::is_na(longitude[i]) || IntegerVector::is_na(code_length[i])){ 233 | output[i] = NA_STRING; 234 | } else { 235 | output[i] = olc_encode_single(latitude[i], longitude[i], code_length[i]); 236 | } 237 | } 238 | 239 | } else { 240 | throw std::range_error("the vector code_length must contain either one value, or one value for each input latitude and longitude"); 241 | } 242 | 243 | return output; 244 | } 245 | 246 | DataFrame olc_coders::olc_decode_vector(CharacterVector olcs){ 247 | 248 | unsigned int input_size = olcs.size(); 249 | NumericVector low_lats(input_size); 250 | NumericVector low_longs(input_size); 251 | NumericVector high_lats(input_size); 252 | NumericVector high_longs(input_size); 253 | NumericVector center_lats(input_size); 254 | NumericVector center_longs(input_size); 255 | IntegerVector code_lengths(input_size); 256 | NumericVector holding(7); 257 | 258 | for(unsigned int i = 0; i < input_size; i++){ 259 | 260 | if(olcs[i] == NA_STRING){ 261 | low_lats[i] = NA_REAL; 262 | high_lats[i] = NA_REAL; 263 | low_longs[i] = NA_REAL; 264 | high_longs[i] = NA_REAL; 265 | center_lats[i] = NA_REAL; 266 | center_longs[i] = NA_REAL; 267 | code_lengths[i] = NA_INTEGER; 268 | } else { 269 | holding = olc_decode_single(Rcpp::as(olcs[i])); 270 | low_lats[i] = holding[0]; 271 | high_lats[i] = holding[1]; 272 | low_longs[i] = holding[2]; 273 | high_longs[i] = holding[3]; 274 | center_lats[i] = holding[4]; 275 | center_longs[i] = holding[5]; 276 | code_lengths[i] = holding[6]; 277 | } 278 | } 279 | 280 | return DataFrame::create(_["latitude_low"] = low_lats, 281 | _["longitude_low"] = low_longs, 282 | _["latitude_center"] = center_lats, 283 | _["longitude_center"] = center_longs, 284 | _["latitude_high"] = high_lats, 285 | _["longitude_high"] = high_longs, 286 | _["code_lengths"] = code_lengths, 287 | _["stringsAsFactors"] = false); 288 | } 289 | 290 | olc_coders::olc_coders(){ 291 | grid_rows = 5; 292 | grid_cols = 4; 293 | grid_degrees = 0.000125; 294 | max_pair_length = 10; 295 | resolution_levels.push_back(20.0); 296 | resolution_levels.push_back(1.0); 297 | resolution_levels.push_back(.05); 298 | resolution_levels.push_back(.0025); 299 | resolution_levels.push_back(.000125); 300 | } 301 | --------------------------------------------------------------------------------