diff --git a/.Rprofile b/.Rprofile deleted file mode 100644 index c18a505..0000000 --- a/.Rprofile +++ /dev/null @@ -1,5 +0,0 @@ -suppressMessages( - suppressWarnings({ - if (isTRUE(require(paint))) paint::unmask_print() - }) -) diff --git a/CRAN-SUBMISSION b/CRAN-SUBMISSION new file mode 100644 index 0000000..c69abca --- /dev/null +++ b/CRAN-SUBMISSION @@ -0,0 +1,3 @@ +Version: 0.4.4 +Date: 2025-07-23 12:38:35 UTC +SHA: 11f3693d2f3ce60c26146e771d0dd17e7385d318 diff --git a/DESCRIPTION b/DESCRIPTION index c16faae..0606a54 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -8,7 +8,7 @@ Description: Parse messy geographic coordinates from various character formats minutes, or seconds; add and subtract degrees, minutes, and seconds. C++ code herein originally inspired from code written by Jeffrey D. Bogan, but then completely re-written. -Version: 0.4.3 +Version: 0.4.4 Authors@R: c( person("Scott", "Chamberlain", role = "aut", email = "sckott@protonmail.com", diff --git a/NEWS.md b/NEWS.md index 1de8cb2..b656225 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,11 @@ +parzer 0.4.4 +============ + +### BUG FIX + +* Fixed a bug in longitude conversion where "74E5423" would be converted to NA while "74W5423" was correctly converted. +* Improved C++ management of NA values. + parzer 0.4.3 ============ diff --git a/R/dms-fxns.R b/R/dms-fxns.R index 1f79c1f..5c950e8 100644 --- a/R/dms-fxns.R +++ b/R/dms-fxns.R @@ -105,16 +105,8 @@ pz_s <- function(x) { `+.pz` <- function(e1, e2) { e1u <- unclass_strip_atts(e1) e2u <- unclass_strip_atts(e2) - e1 <- switch(attr(e1, "type"), - deg = e1u, - min = e1u / 60, - sec = e1u / 3600 - ) - e2 <- switch(attr(e2, "type"), - deg = e2u, - min = e2u / 60, - sec = e2u / 3600 - ) + e1 <- switch(attr(e1, "type"), deg = e1u, min = e1u / 60, sec = e1u / 3600) + e2 <- switch(attr(e2, "type"), deg = e2u, min = e2u / 60, sec = e2u / 3600) structure(e1 + e2, class = "pz", type = "deg") } #' @export @@ -122,16 +114,8 @@ pz_s <- function(x) { `-.pz` <- function(e1, e2) { e1u <- unclass_strip_atts(e1) e2u <- unclass_strip_atts(e2) - e1 <- switch(attr(e1, "type"), - deg = e1u, - min = e1u / 60, - sec = e1u / 3600 - ) - e2 <- switch(attr(e2, "type"), - deg = e2u, - min = e2u / 60, - sec = e2u / 3600 - ) + e1 <- switch(attr(e1, "type"), deg = e1u, min = e1u / 60, sec = e1u / 3600) + e2 <- switch(attr(e2, "type"), deg = e2u, min = e2u / 60, sec = e2u / 3600) structure(e1 - e2, class = "pz", type = "deg") } #' @export diff --git a/R/parzer-package.R b/R/parzer-package.R index c5a974d..6dc4845 100644 --- a/R/parzer-package.R +++ b/R/parzer-package.R @@ -8,4 +8,4 @@ NULL #' `r lifecycle::badge("stable")` #' #' parse geographic coordinates -"_PACKAGE" \ No newline at end of file +"_PACKAGE" diff --git a/R/zzz.R b/R/zzz.R index 46974f4..f839150 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,7 +1,9 @@ assert <- function(x, y) { if (!is.null(x)) { if (!inherits(x, y)) { - stop(deparse(substitute(x)), " must be of class ", + stop( + deparse(substitute(x)), + " must be of class ", paste0(y, collapse = ", "), call. = FALSE ) diff --git a/codemeta.json b/codemeta.json index d487c56..8e5a50f 100644 --- a/codemeta.json +++ b/codemeta.json @@ -4,23 +4,17 @@ "identifier": "parzer", "description": "Parse messy geographic coordinates from various character formats to decimal degree numeric values. Parse coordinates into their parts (degree, minutes, seconds); calculate hemisphere from coordinates; pull out individually degrees, minutes, or seconds; add and subtract degrees, minutes, and seconds. C++ code herein originally inspired from code written by Jeffrey D. Bogan, but then completely re-written.", "name": "parzer: Parse Messy Geographic Coordinates", - "relatedLink": ["https://docs.ropensci.org/parzer/", "https://CRAN.R-project.org/package=parzer"], + "relatedLink": "https://docs.ropensci.org/parzer/", "codeRepository": "https://github.com/ropensci/parzer", "issueTracker": "https://github.com/ropensci/parzer/issues", "license": "https://spdx.org/licenses/MIT", - "version": "0.4.3", + "version": "0.4.4", "programmingLanguage": { "@type": "ComputerLanguage", "name": "R", "url": "https://r-project.org" }, "runtimePlatform": "R version 4.5.0 (2025-04-11)", - "provider": { - "@id": "https://cran.r-project.org", - "@type": "Organization", - "name": "Comprehensive R Archive Network (CRAN)", - "url": "https://cran.r-project.org" - }, "author": [ { "@type": "Person", @@ -198,7 +192,7 @@ "applicationCategory": "Geospatial", "isPartOf": "https://ropensci.org", "keywords": ["geospatial", "data", "latitude", "longitude", "parser", "coordinates", "r", "rstats", "geo", "r-package"], - "fileSize": "4749.036KB", + "fileSize": "6120.123KB", "releaseNotes": "https://github.com/ropensci/parzer/blob/master/NEWS.md", "readme": "https://github.com/ropensci/parzer/blob/main/README.md", "contIntegration": ["https://github.com/ropensci/parzer/actions/", "https://app.codecov.io/github/ropensci/parzer?branch=main"], diff --git a/cran-comments.md b/cran-comments.md index a89f2ff..c601466 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,19 +1,15 @@ ## R CMD check results -0 errors | 0 warnings | 1 note - -* NOTE: unable to verify current time +0 errors | 0 warnings | 0 note * This is a new release. * `revdepcheck` passed -* Local and GHA and win_devel R CMD check passed - -## Changes not listed in NEWS - -* Removed the README file from the package directory as requested by Professor Ripley. +* Local and GHA and win_devel and r-project macOS buider R CMD check passed Dear CRAN reviewer, +The package was silently archived because of a C++ undefined cast. Hopefully this has been fixed. + Thank you for your time reviewing this release. Dr Alban Sagouis diff --git a/revdep/README.md b/revdep/README.md index 15a1b87..3e60df8 100644 --- a/revdep/README.md +++ b/revdep/README.md @@ -10,15 +10,16 @@ |collate |en_US.UTF-8 | |ctype |en_US.UTF-8 | |tz |Europe/Berlin | -|date |2025-05-19 | +|date |2025-07-22 | |pandoc |3.6.4 @ /opt/homebrew/bin/pandoc | -|quarto |1.6.40 @ /Applications/Positron.app/Contents/Resources/app/quarto/bin/quarto | +|quarto |1.7.31 @ /Applications/Positron.app/Contents/Resources/app/quarto/bin/quarto | # Dependencies -|package |old |new |Δ | -|:-------|:-----|:-----|:--| -|parzer |0.4.1 |0.4.2 |* | +|package |old |new |Δ | +|:-------|:---|:-----|:--| +|parzer |NA |0.4.4 |* | +|Rcpp |NA |1.1.0 |* | # Revdeps diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 25b0ff1..1d8e389 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -67,7 +67,7 @@ BEGIN_RCPP END_RCPP } // split_decimal_degree -std::vector split_decimal_degree(const double& x, const std::string& fmt); +Rcpp::List split_decimal_degree(const double& x, const std::string& fmt); RcppExport SEXP _parzer_split_decimal_degree(SEXP xSEXP, SEXP fmtSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; diff --git a/src/latlong.cpp b/src/latlong.cpp index 362c990..add8ca7 100644 --- a/src/latlong.cpp +++ b/src/latlong.cpp @@ -132,10 +132,11 @@ bool has_non_direction_letters(std::string& s, const std::string& reggex) { return z; } +// useful to NA on "-45.23232e24" bool has_e_with_trailing_numbers(const std::string& s) { bool res = false; // s = str_tolower(s); - std::regex reg("[0-9]+e[0-9]+"); + std::regex reg("[0-9]+e[0-9]+$"); std::smatch match; if (std::regex_search(s, match, reg)) { res = true; @@ -243,7 +244,7 @@ double convert_lon(std::string& str) { if ( str.size() == 0 || !any_digits(str) || - has_non_direction_letters(str, "abcfghijklmnopqrstuvxyz") || + has_non_direction_letters(str, "abcfghijklmnopqrstuvxyz") || has_e_with_trailing_numbers(str) ) { ret = NA_REAL; @@ -294,4 +295,3 @@ double convert_lon(std::string& str) { } return ret; } - diff --git a/src/pz_parse_parts.cpp b/src/pz_parse_parts.cpp index a342729..52844fc 100644 --- a/src/pz_parse_parts.cpp +++ b/src/pz_parse_parts.cpp @@ -2,14 +2,15 @@ #include "latlong.h" // [[Rcpp::export]] -std::vector split_decimal_degree(const double& x, const std::string& fmt = "dms") { +Rcpp::List split_decimal_degree(const double& x, const std::string& fmt = "dms") { + //std::vector split_decimal_degree(const double& x, const std::string& fmt = "dms") { const double sixty = 60; const double thirtysixh = 3600; double dir_val = 1.0; - if ( R_IsNA(x)) { - std::vector{0, 0, 0}; // this has to be updated - // return Rcpp::List::create(NA_REAL, NA_REAL, NA_REAL); + if (std::isnan(x)) { + //std::vector{0, 0, 0}; // this has to be updated + return Rcpp::List::create(NA_REAL, NA_REAL, NA_REAL); } // auto x_str = Rcpp::toString(x); // Rcpp should be replaced here // if (is_negative(x)) { // does not use Rcpp but uses regex @@ -23,7 +24,8 @@ std::vector split_decimal_degree(const double& x, const std::string& fmt double s = ((x_abs - d) - (m/sixty)) * thirtysixh; d = d * dir_val; - return std::vector{d, m, s}; + return Rcpp::List::create(d, m, s); + // return std::vector{d, m, s}; } // [[Rcpp::export]] @@ -35,7 +37,8 @@ Rcpp::DataFrame pz_parse_parts_lat(std::vector& x) { for (int i=0; i < n; ++i) { double out = convert_lat(x[i]); // passed as a reference. - std::vector parts = split_decimal_degree(out); // passed as a const reference. + //std::vector = split_decimal_degree(out); // passed as a const reference. + Rcpp::List parts = split_decimal_degree(out); // passed as a const reference. deg[i] = parts[0]; min[i] = parts[1]; sec[i] = parts[2]; @@ -55,7 +58,8 @@ Rcpp::DataFrame pz_parse_parts_lon(std::vector& x) { for (int i=0; i < n; ++i) { double out = convert_lon(x[i]); // passed as a reference. - std::vector parts = split_decimal_degree(out); // passed as a const reference. + //std::vector parts = split_decimal_degree(out); // passed as a const reference. + Rcpp::List parts = split_decimal_degree(out); // passed as a const reference. deg[i] = parts[0]; min[i] = parts[1]; sec[i] = parts[2]; @@ -65,4 +69,3 @@ Rcpp::DataFrame pz_parse_parts_lon(std::vector& x) { Rcpp::_["sec"] = sec, Rcpp::_["stringsAsFactors"] = false); } - diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R new file mode 100644 index 0000000..98f28b3 --- /dev/null +++ b/tests/testthat/setup.R @@ -0,0 +1,6 @@ +base::options(digits = 7) +suppressWarnings({ + suppressMessages({ + if (isTRUE(require(paint))) paint::unmask_print() + }) +}) diff --git a/tests/testthat/test-dms.R b/tests/testthat/test-dms.R index cb2b4c2..79394b8 100644 --- a/tests/testthat/test-dms.R +++ b/tests/testthat/test-dms.R @@ -82,7 +82,8 @@ invalid_formats <- c( test_that("dms fxns fail as expected", { out <- data.frame( - input = invalid_formats, res = NA_real_, + input = invalid_formats, + res = NA_real_, stringsAsFactors = FALSE ) for (i in seq_along(invalid_formats)) { diff --git a/tests/testthat/test-parse_hemisphere.R b/tests/testthat/test-parse_hemisphere.R index 1917bdd..651954b 100644 --- a/tests/testthat/test-parse_hemisphere.R +++ b/tests/testthat/test-parse_hemisphere.R @@ -16,12 +16,18 @@ test_that("parse_hemisphere works", { # bad values ## one expect_equal( - suppressWarnings(parse_hemisphere("120", "-240.4183318")), "E") + suppressWarnings(parse_hemisphere("120", "-240.4183318")), + "E" + ) expect_equal( - suppressWarnings(parse_hemisphere("420", "-40.4183318")), "S") + suppressWarnings(parse_hemisphere("420", "-40.4183318")), + "S" + ) ## both expect_equal( - suppressWarnings(parse_hemisphere("-200", "-240.4183318")), "") + suppressWarnings(parse_hemisphere("-200", "-240.4183318")), + "" + ) }) test_that("parse_hemisphere - fails well", { @@ -32,5 +38,8 @@ test_that("parse_hemisphere - fails well", { expect_warning(parse_hemisphere("45", "190"), "not within -90") expect_warning(parse_hemisphere("45", "190"), "check that you did not invert") - expect_warning(parse_hemisphere("190", "45"), "longitude value within 180/360 range, got: 190") + expect_warning( + parse_hemisphere("190", "45"), + "longitude value within 180/360 range, got: 190" + ) }) diff --git a/tests/testthat/test-parse_lat.R b/tests/testthat/test-parse_lat.R index d2a5330..be04894 100644 --- a/tests/testthat/test-parse_lat.R +++ b/tests/testthat/test-parse_lat.R @@ -35,7 +35,8 @@ test_lats <- c( test_that("parse_lat works: run through test_lats", { out <- data.frame( - input = test_lats, res = NA_real_, + input = test_lats, + res = NA_real_, stringsAsFactors = FALSE ) for (i in seq_along(test_lats)) { @@ -77,13 +78,28 @@ invalid_formats <- c( # res column should all give NaN test_that("parse_lat works: invalid formats fail as expected", { out <- data.frame( - input = invalid_formats, res = NA_real_, + input = invalid_formats, + res = NA_real_, stringsAsFactors = FALSE ) for (i in seq_along(invalid_formats)) { - out[i, "res"] <- suppressWarnings({parse_lat(invalid_formats[i])}) - expect_warning({aa <- parse_lat(invalid_formats[i])}) + out[i, "res"] <- suppressWarnings({ + parse_lat(invalid_formats[i]) + }) + expect_warning({ + aa <- parse_lat(invalid_formats[i]) + }) expect_type(aa, "double") expect_equal(aa, NaN) } }) + + +test_that("parse_lat correctly processes NA values", { + expect_equal( + suppressWarnings( + parse_lat(c("W60.1", NA, NA_character_, "12' 30'")) + ), + c(NA_real_, NA_real_, NA_real_, 12.5) + ) +}) diff --git a/tests/testthat/test-parse_lat_lon.R b/tests/testthat/test-parse_lat_lon.R index 278bf31..d83e47b 100644 --- a/tests/testthat/test-parse_lat_lon.R +++ b/tests/testthat/test-parse_lat_lon.R @@ -21,3 +21,25 @@ test_that("parse_lon_lat - fails well", { expect_warning(parse_lon_lat("45", "190"), "not within -90") expect_warning(parse_lon_lat("45", "190"), "check that you did not invert") }) + + +test_that("parse_lon_lat correctly processes NA values", { + expect_equal( + suppressWarnings( + parse_lon_lat("S60.1", NA_character_) + ), + data.frame(lon = NA_real_, lat = NA_real_) + ) + expect_equal( + suppressWarnings( + parse_lon_lat("12' 30'", NA_character_) + ), + data.frame(lon = 12.5, lat = NA_real_) + ) + expect_equal( + suppressWarnings( + parse_lon_lat(NA_character_, "12' 30'") + ), + data.frame(lon = NA_real_, lat = 12.5) + ) +}) diff --git a/tests/testthat/test-parse_llstr.R b/tests/testthat/test-parse_llstr.R index e4c13c3..54e1cb6 100644 --- a/tests/testthat/test-parse_llstr.R +++ b/tests/testthat/test-parse_llstr.R @@ -39,3 +39,12 @@ test_that("parse_llstr - fails well", { expect_warning(parse_llstr("190, 45"), "not within -90") expect_warning(parse_llstr("190, 45"), "check that you did not invert") }) + +test_that("parse_llstr correctly processes NA values", { + expect_equal( + suppressWarnings( + parse_llstr(c(NA_character_, "12' 30', 12' 30'")) + ), + data.frame(lat = c(NA_real_, 12.5), lon = c(NA_real_, 12.5)) + ) +}) diff --git a/tests/testthat/test-parse_lon.R b/tests/testthat/test-parse_lon.R index ee878c3..98fd297 100644 --- a/tests/testthat/test-parse_lon.R +++ b/tests/testthat/test-parse_lon.R @@ -34,7 +34,8 @@ test_lons <- c( test_that("parse_lon works: run through test_lons", { out <- data.frame( - input = test_lons, res = NA_real_, + input = test_lons, + res = NA_real_, stringsAsFactors = FALSE ) for (i in seq_along(test_lons)) { @@ -42,6 +43,17 @@ test_that("parse_lon works: run through test_lons", { } }) +test_that("parse_lon works with cardinal letters among the numbers", { + expect_equal( + parse_lon(c("74W30'45\"", "74E30'45\"")), + c(-74.5125, 74.5125) + ) + expect_equal( + parse_lon(c("74 E30'45\"", "74E 30'45\"")), + c(74.5125, 74.5125) + ) +}) + test_that("parse_lon - fails well", { expect_error(parse_lon(), "argument \"lon\" is missing") expect_error(parse_lon(mtcars), "lon must be of class") @@ -79,13 +91,27 @@ invalid_formats <- c( # res column should all give NaN test_that("parse_lon works: invalid formats fail as expected", { out <- data.frame( - input = invalid_formats, res = NA_real_, + input = invalid_formats, + res = NA_real_, stringsAsFactors = FALSE ) for (i in seq_along(invalid_formats)) { - out[i, "res"] <- suppressWarnings({parse_lon(invalid_formats[i])}) - expect_warning({aa <- parse_lon(invalid_formats[i])}) + out[i, "res"] <- suppressWarnings({ + parse_lon(invalid_formats[i]) + }) + expect_warning({ + aa <- parse_lon(invalid_formats[i]) + }) expect_type(aa, "double") expect_equal(aa, NaN) } }) + +test_that("parse_lon correctly processes NA values", { + expect_equal( + suppressWarnings( + parse_lon(c("S60.1", NA, NA_character_, "12' 30'")) + ), + c(NA_real_, NA_real_, NA_real_, 12.5) + ) +}) diff --git a/tests/testthat/test-parse_parts.R b/tests/testthat/test-parse_parts.R index 8af06bf..58a398a 100644 --- a/tests/testthat/test-parse_parts.R +++ b/tests/testthat/test-parse_parts.R @@ -20,6 +20,19 @@ test_that("parse_parts_lat - fails well", { expect_error(parse_parts_lat(mtcars), "str must be of class") }) +test_that("parse_parts_lat correctly processes NA values", { + expect_equal( + suppressWarnings( + parse_parts_lat(c("45N54.30", NA_character_, "45N54.30")) + ), + data.frame( + deg = c(45, NA_integer_, 45), + min = c(54, NA_integer_, 54), + sec = c(18, NA_real_, 18) + ) + ) +}) + test_that("parse_parts_lon works", { aa <- parse_parts_lon("45W54.2356") @@ -40,3 +53,16 @@ test_that("parse_parts_lon - fails well", { expect_error(parse_parts_lon(), "argument \"str\" is missing") expect_error(parse_parts_lon(mtcars), "str must be of class") }) + +test_that("parse_parts_lon correctly processes NA values", { + expect_equal( + suppressWarnings( + parse_parts_lon(c("45W54.30", NA_character_, "45W54.30")) + ), + data.frame( + deg = c(-45, NA_integer_, -45), + min = c(54, NA_integer_, 54), + sec = c(18, NA_real_, 18) + ) + ) +}) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index c9b155e..6506053 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -12,8 +12,10 @@ test_that("scrub", { expect_equal(scrub("º"), "'") expect_equal(scrub("40º 25.0999"), "40' 25.0999") expect_equal(scrub("``º′″"), "'''''") - expect_equal(scrub(c("N4º51′36″, E101:34′7″","N4:51′36″, E101d34′7″")), - c("N4'51'36', E101'34'7'", "N4'51'36', E101'34'7'")) + expect_equal( + scrub(c("N4º51′36″, E101:34′7″", "N4:51′36″, E101d34′7″")), + c("N4'51'36', E101'34'7'", "N4'51'36', E101'34'7'") + ) expect_equal(as.character(round(parse_lat("40º 25.0999"), 5)), "40.41833") expect_equal(as.character(round(parse_lon("110º 33.6667"), 5)), "110.56111") })