diff --git a/.Rbuildignore b/.Rbuildignore index 46a8026..a48b34d 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -6,3 +6,4 @@ ^docs$ ^pkgdown$ ^codecov\.yml$ +^.vscode$ diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 521fd25..cd808f2 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -1,7 +1,6 @@ on: - push: - branches: - - refs/tags/* + release: + types: [created, edited] name: pkgdown @@ -24,7 +23,9 @@ jobs: libgdal-dev \ libgeos-dev \ libproj-dev \ - libcurl4-openssl-dev + libcurl4-openssl-dev \ + libharfbuzz-dev \ + libfribidi-dev - name: Query dependencies run: | install.packages('remotes') diff --git a/.gitignore b/.gitignore index 2cdc2bd..434f468 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ vignettes/*.pdf *.DS_Store inst/doc docs + +# vscode +.vscode diff --git a/DESCRIPTION b/DESCRIPTION index 8430835..62eedfc 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: flexpolyline Type: Package Title: Flexible Polyline Encoding -Version: 0.1.1.9000 +Version: 0.2.0 Authors@R: c(person(given = "Merlin", family = "Unterfinger", @@ -20,8 +20,8 @@ Description: Binding to the C++ implementation of the flexible polyline (3) using variable length for each coordinate delta; and (4) using 64 URL-safe characters to display the result. License: GPL-3 -URL: https://munterfinger.github.io/flexpolyline, https://github.com/munterfinger/flexpolyline -BugReports: https://github.com/munterfinger/flexpolyline/issues +URL: https://munterfinger.github.io/flexpolyline/, https://github.com/munterfinger/flexpolyline/ +BugReports: https://github.com/munterfinger/flexpolyline/issues/ LinkingTo: Rcpp Imports: diff --git a/NAMESPACE b/NAMESPACE index fabcab6..6ff725b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,5 +9,7 @@ export(decode) export(decode_sf) export(encode) export(encode_sf) +export(get_third_dimension) +export(set_third_dimension) importFrom(Rcpp,sourceCpp) useDynLib(flexpolyline, .registration = TRUE) diff --git a/NEWS.md b/NEWS.md index d7dd1bc..0b27d79 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ -# flexpolyline 0.1.1.9000 +# flexpolyline 0.2.0 +* Fix clang range-loop-analysis warning on macOS in `flexpolyline.h` (Apple clang version 12.0.0). +* Support for geometry types `"POLYGON"` and `"POINT"` in `encode_sf()` and `decode_sf()`, closes #31. +* Added functions to get (`get_third_dimension()`) and set (`set_third_dimension()`) the third dimension type of a flexible polyline encoded string. * Sign in to CodeFactor.io and add badge to continuously track code quality. * Use exception classes when throwing an exception in C++. * Improve coverage of tests. diff --git a/R/RcppExports.R b/R/RcppExports.R index c1485b3..2f4247d 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -3,9 +3,10 @@ #' Decode a flexible polyline encoded string #' -#' This function calls \code{hf::polyline_decode} and \code{hf::get_third_dimension} -#' of the C++ implementation of the flexible polyline encoding by HERE. Depending -#' on the dimensions of the encoded line, a two or three dimensional line is decoded. +#' This function calls \code{hf::polyline_decode} and +#' \code{hf::get_third_dimension} of the C++ implementation of the flexible +#' polyline encoding by HERE. Depending on the dimensions of the encoded line, +#' a two or three dimensional line is decoded. #' #' @param encoded character, encoded flexible polyline string. #' @@ -30,10 +31,14 @@ decode <- function(encoded) { #' the flexible polyline encoding by HERE. Depending on the dimensions of the #' input coordinates, a two or three dimensional line is encoded. #' -#' @param line matrix, coordinates of the line in 2d or 3d (column order: LNG, LAT, DIM3). -#' @param precision integer, precision to use in encoding (between 0 and 15, \code{default=5}). -#' @param third_dim integer, type of the third dimension (0: ABSENT, 1: LEVEL, 2: ALTITUDE, 3: ELEVATION, 4, 6: CUSTOM1, 7: CUSTOM2, \code{default=3}). -#' @param third_dim_precision integer, precision to use in encoding for the third dimension (between 1 and 15, \code{default=5}). +#' @param line matrix, coordinates of the line in 2d or 3d (column order: LNG, +#' LAT, DIM3). +#' @param precision integer, precision to use in encoding (between 0 and 15, +#' \code{default=5}). +#' @param third_dim integer, type of the third dimension (0: ABSENT, 1: LEVEL, +#' 2: ALTITUDE, 3: ELEVATION, 4, 6: CUSTOM1, 7: CUSTOM2, \code{default=3}). +#' @param third_dim_precision integer, precision to use in encoding for the +#' third dimension (between 1 and 15, \code{default=5}). #' #' @return #' The line as string in the flexible polyline encoding format. @@ -64,3 +69,59 @@ encode <- function(line, precision = 5L, third_dim = 3L, third_dim_precision = 5 .Call(`_flexpolyline_encode`, line, precision, third_dim, third_dim_precision) } +#' Get third dimension of a flexible polyline encoded string +#' +#' This function calls \code{hf::get_third_dimension} of the C++ implementation +#' of the flexible polyline encoding by HERE and return the type of the third +#' dimension. +#' +#' @param encoded character, encoded flexible polyline string. +#' +#' @return +#' A string describing the third dimension. +#' +#' @export +#' +#' @examples +#' # 2d line +#' get_third_dimension("BFoz5xJ67i1B1B7PzIhaxL7Y") +#' +#' # 3d line +#' get_third_dimension("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU") +get_third_dimension <- function(encoded) { + .Call(`_flexpolyline_get_third_dimension`, encoded) +} + +#' Set third dimension of a flexible polyline encoded string +#' +#' This function decodes the flexible polyline encoded line, changes the third +#' dimension and encodes the line again. +#' +#' @note +#' The precision is not read from the header of the encoded line. Therefore it +#' must be provided as a parameter for re-encoding. +#' +#' @param encoded character, encoded flexible polyline string. +#' @param third_dim_name character, name of the third dimension to set (ABSENT, +#' LEVEL, ALTITUDE, ELEVATION, CUSTOM1, CUSTOM2). +#' @param precision integer, precision to use in encoding (between 0 and 15, +#' \code{default=5}). +#' @param third_dim_precision integer, precision to use in encoding for the +#' third dimension (between 1 and 15, \code{default=5}). +#' +#' @return +#' The line with the new third dimension as string in the flexible polyline +#' encoding format. +#' +#' @export +#' +#' @examples +#' # 2d line (nothing happens...) +#' set_third_dimension("BFoz5xJ67i1B1B7PzIhaxL7Y", "ELEVATION") +#' +#' # 3d line +#' set_third_dimension("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU", "ELEVATION") +set_third_dimension <- function(encoded, third_dim_name, precision = 5L, third_dim_precision = 5L) { + .Call(`_flexpolyline_set_third_dimension`, encoded, third_dim_name, precision, third_dim_precision) +} + diff --git a/R/decode_sf.R b/R/decode_sf.R index ef803e8..9545bb1 100644 --- a/R/decode_sf.R +++ b/R/decode_sf.R @@ -18,22 +18,18 @@ #' @export #' #' @examples -#' # 2d line -#' decode_sf("BFoz5xJ67i1B1B7PzIhaxL7Y") -#' -#' # 3d line -#' decode_sf("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU") +#' decode_sf("B1Voz5xJ67i1Bgkh9B") +#' decode_sf("BFoz5xJ67i1B1B7PlU9yB") +#' decode_sf("BlXoz5xJ67i1Bgkh9B1B7Pgkh9BzIhagkh9BqK-pB_ni6D") decode_sf <- function(encoded, crs = sf::NA_crs_) { UseMethod("decode_sf", encoded) } #' @export decode_sf.character <- function(encoded, crs = sf::NA_crs_) { - dim3 <- character(length(encoded)) ind3 <- 2 sfdi <- "XY" - geom <- sf::st_sfc( lapply(1:length(encoded), function(x) { m <- decode(encoded[[x]]) @@ -51,13 +47,19 @@ decode_sf.character <- function(encoded, crs = sf::NA_crs_) { sfdi <<- "XYM" } } - sf::st_linestring(m, dim = sfdi) + if (nrow(m) <= 1) { + sf::st_point(m, dim = sfdi) + } else { + if (all(m[1, ] == m[nrow(m), ])) { + sf::st_polygon(list(m), dim = sfdi) + } else { + sf::st_linestring(m, dim = sfdi) + } + } }), crs = crs ) - dim3[is.na(dim3)] <- "ABSENT" - return( sf::st_as_sf( data.frame( diff --git a/R/encode_sf.R b/R/encode_sf.R index 3347fc1..1d4ffa5 100644 --- a/R/encode_sf.R +++ b/R/encode_sf.R @@ -3,7 +3,7 @@ #' A wrapper function for \code{\link{encode}} that converts simple feature geometries #' of the sf package to flexible polyline encoded strings. #' -#' @param line simple feature, \code{sf}, \code{sfc} or \code{sfg} object with geometry type \code{"LINESTRING"}. +#' @param geom simple feature, \code{sf}, \code{sfc} or \code{sfg} object with geometry type \code{"POINT"}, \code{"LINESTRING"} or \code{"POLYGON"}. #' @param precision integer, precision to use in encoding (between 0 and 15, \code{default=5}). #' @param third_dim integer, type of the third dimension (0: ABSENT, 1: LEVEL, 2: ALTITUDE, 3: ELEVATION, 4, 6: CUSTOM1, 7: CUSTOM2, \code{default=NULL}). #' @param third_dim_precision integer, precision to use in encoding for the third dimension (between 1 and 15, \code{default=precision}). @@ -14,61 +14,57 @@ #' @export #' #' @examples -#' # 2D +#' # 3D point +#' point3d <- sf::st_point( +#' matrix(c(8.69821, 50.10228, 10), ncol = 3, byrow = TRUE), dim = "XYZ") +#' encode_sf(point3d) +#' +#' # 2D linestring #' line2d <- sf::st_linestring( -#' matrix( -#' c(8.69821, 50.10228, -#' 8.69567, 50.10201, -#' 8.69150, 50.10063, -#' 8.68752, 50.09878), -#' ncol = 2, byrow = TRUE -#' ) -#' ) +#' matrix(c(8.69821, 50.10228, +#' 8.69567, 50.10201, +#' 8.68752, 50.09878), ncol = 2, byrow = TRUE)) #' encode_sf(line2d) #' -#' # 3D -#' line3d <- sf::st_linestring( -#' matrix( -#' c(8.69821, 50.10228, 10, -#' 8.69567, 50.10201, 20, -#' 8.69150, 50.10063, 30, -#' 8.68752, 50.09878, 40), -#' ncol = 3, byrow = TRUE -#' ) -#' ) -#' encode_sf(line3d) -encode_sf <- function(line, precision = 5, third_dim = NULL, +#' # 3D polygon +#' poly3d <- sf::st_polygon(list( +#' matrix(c(8.69821, 50.10228, 10, +#' 8.69567, 50.10201, 20, +#' 8.69150, 50.10063, 30, +#' 8.69821, 50.10228, 10), ncol = 3, byrow = TRUE)), dim = "XYM") +#' encode_sf(poly3d) +encode_sf <- function(geom, precision = 5, third_dim = NULL, third_dim_precision = precision) { - UseMethod("encode_sf", line) + UseMethod("encode_sf", geom) } #' @export -encode_sf.sfg <- function(line, precision = 5, third_dim = NULL, +encode_sf.sfg <- function(geom, precision = 5, third_dim = NULL, third_dim_precision = precision) { - if(sf::st_geometry_type(line) != "LINESTRING"){ + if(!sf::st_geometry_type(geom) %in% c("POINT", "LINESTRING", "POLYGON")){ stop( sprintf( - "Invalid geometry type '%s' of input, only 'LINESTRING' is supported.", - sf::st_geometry_type(line) + "Invalid geometry type '%s' of input, only 'POINT', 'LINESTRING' and 'POLYGON' is supported.", + sf::st_geometry_type(geom) ) ) } - if (class(line)[1] == "XY") { + if (class(geom)[1] == "XY") { third_dim <- 0 encoded <- encode( - sf::st_coordinates(line)[, c(1:2)], + sf::st_coordinates(geom)[, c(1:2), drop = FALSE], precision, third_dim, third_dim_precision ) } else { if (is.null(third_dim)) { - if (class(line)[1] == "XYZ") { + if (class(geom)[1] == "XYZ") { third_dim <- 3 } else { third_dim <- 6 } } encoded <- encode( - sf::st_coordinates(line)[, c(1:3)], + sf::st_coordinates(geom)[, c(1:3), drop = FALSE], precision, third_dim, third_dim_precision ) } @@ -76,21 +72,21 @@ encode_sf.sfg <- function(line, precision = 5, third_dim = NULL, } #' @export -encode_sf.sfc <- function(line, precision = 5, third_dim = NULL, +encode_sf.sfc <- function(geom, precision = 5, third_dim = NULL, third_dim_precision = precision) { return( - sapply(line, function(x) { + sapply(geom, function(x) { encode_sf.sfg(x, precision, third_dim, third_dim_precision) }) ) } #' @export -encode_sf.sf <- function(line, precision = 5, third_dim = NULL, +encode_sf.sf <- function(geom, precision = 5, third_dim = NULL, third_dim_precision = precision) { return( encode_sf.sfc( - sf::st_geometry(line), + sf::st_geometry(geom), precision, third_dim, third_dim_precision ) ) diff --git a/inst/include/hf/flexpolyline.h b/inst/include/hf/flexpolyline.h index d7cbffd..c94fd82 100644 --- a/inst/include/hf/flexpolyline.h +++ b/inst/include/hf/flexpolyline.h @@ -1,6 +1,6 @@ /* * Copy from :https://github.com/heremaps/flexible-polyline/blob/master/cpp/ - * Modified :Line 93, 211 + * Modified :Line 93, 210, 278 * Date :2020-06-09, Merlin Unterfinger * * Copyright (C) 2019 HERE Europe B.V. @@ -201,7 +201,6 @@ class Encoder { std::string get_encoded() { return m_result; } - }; class Decoder { @@ -276,9 +275,9 @@ template std::string polyline_encode(Iter iter, int precision, ThirdDim third_dim, int third_dim_precision) { auto enc = encoder::Encoder(precision, third_dim, third_dim_precision); - for (const auto item : iter) { - enc.add(&item); - } + for (const auto& item : iter) { // Mod: Return by reference to avoid clang (Apple clang version 12.0.0) range-loop-analysis + enc.add(&item); // warning on MacOS: "loop variable 'item' of type 'const std::__1::pair' + } // creates a copy from type 'const std::__1::pair'" return enc.get_encoded(); } diff --git a/man/decode.Rd b/man/decode.Rd index 00f2726..76254f5 100644 --- a/man/decode.Rd +++ b/man/decode.Rd @@ -13,9 +13,10 @@ decode(encoded) A matrix containing the coordinates of the decoded line. } \description{ -This function calls \code{hf::polyline_decode} and \code{hf::get_third_dimension} -of the C++ implementation of the flexible polyline encoding by HERE. Depending -on the dimensions of the encoded line, a two or three dimensional line is decoded. +This function calls \code{hf::polyline_decode} and +\code{hf::get_third_dimension} of the C++ implementation of the flexible +polyline encoding by HERE. Depending on the dimensions of the encoded line, +a two or three dimensional line is decoded. } \examples{ # 2d line diff --git a/man/decode_sf.Rd b/man/decode_sf.Rd index 15333d5..a0e1e37 100644 --- a/man/decode_sf.Rd +++ b/man/decode_sf.Rd @@ -25,9 +25,7 @@ to meet the requirements of the constructor of sf objects. For mixed dimensions use the \code{\link{decode}} function directly. } \examples{ -# 2d line -decode_sf("BFoz5xJ67i1B1B7PzIhaxL7Y") - -# 3d line -decode_sf("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU") +decode_sf("B1Voz5xJ67i1Bgkh9B") +decode_sf("BFoz5xJ67i1B1B7PlU9yB") +decode_sf("BlXoz5xJ67i1Bgkh9B1B7Pgkh9BzIhagkh9BqK-pB_ni6D") } diff --git a/man/encode.Rd b/man/encode.Rd index e665060..610ea44 100644 --- a/man/encode.Rd +++ b/man/encode.Rd @@ -7,13 +7,17 @@ encode(line, precision = 5L, third_dim = 3L, third_dim_precision = 5L) } \arguments{ -\item{line}{matrix, coordinates of the line in 2d or 3d (column order: LNG, LAT, DIM3).} +\item{line}{matrix, coordinates of the line in 2d or 3d (column order: LNG, +LAT, DIM3).} -\item{precision}{integer, precision to use in encoding (between 0 and 15, \code{default=5}).} +\item{precision}{integer, precision to use in encoding (between 0 and 15, +\code{default=5}).} -\item{third_dim}{integer, type of the third dimension (0: ABSENT, 1: LEVEL, 2: ALTITUDE, 3: ELEVATION, 4, 6: CUSTOM1, 7: CUSTOM2, \code{default=3}).} +\item{third_dim}{integer, type of the third dimension (0: ABSENT, 1: LEVEL, +2: ALTITUDE, 3: ELEVATION, 4, 6: CUSTOM1, 7: CUSTOM2, \code{default=3}).} -\item{third_dim_precision}{integer, precision to use in encoding for the third dimension (between 1 and 15, \code{default=5}).} +\item{third_dim_precision}{integer, precision to use in encoding for the +third dimension (between 1 and 15, \code{default=5}).} } \value{ The line as string in the flexible polyline encoding format. diff --git a/man/encode_sf.Rd b/man/encode_sf.Rd index 10edcb4..50fbe6f 100644 --- a/man/encode_sf.Rd +++ b/man/encode_sf.Rd @@ -5,14 +5,14 @@ \title{Wrapper function for encoding simple features} \usage{ encode_sf( - line, + geom, precision = 5, third_dim = NULL, third_dim_precision = precision ) } \arguments{ -\item{line}{simple feature, \code{sf}, \code{sfc} or \code{sfg} object with geometry type \code{"LINESTRING"}.} +\item{geom}{simple feature, \code{sf}, \code{sfc} or \code{sfg} object with geometry type \code{"POINT"}, \code{"LINESTRING"} or \code{"POLYGON"}.} \item{precision}{integer, precision to use in encoding (between 0 and 15, \code{default=5}).} @@ -28,27 +28,23 @@ A wrapper function for \code{\link{encode}} that converts simple feature geometr of the sf package to flexible polyline encoded strings. } \examples{ -# 2D +# 3D point +point3d <- sf::st_point( + matrix(c(8.69821, 50.10228, 10), ncol = 3, byrow = TRUE), dim = "XYZ") +encode_sf(point3d) + +# 2D linestring line2d <- sf::st_linestring( - matrix( - c(8.69821, 50.10228, - 8.69567, 50.10201, - 8.69150, 50.10063, - 8.68752, 50.09878), - ncol = 2, byrow = TRUE - ) -) + matrix(c(8.69821, 50.10228, + 8.69567, 50.10201, + 8.68752, 50.09878), ncol = 2, byrow = TRUE)) encode_sf(line2d) -# 3D -line3d <- sf::st_linestring( - matrix( - c(8.69821, 50.10228, 10, - 8.69567, 50.10201, 20, - 8.69150, 50.10063, 30, - 8.68752, 50.09878, 40), - ncol = 3, byrow = TRUE - ) -) -encode_sf(line3d) +# 3D polygon +poly3d <- sf::st_polygon(list( + matrix(c(8.69821, 50.10228, 10, + 8.69567, 50.10201, 20, + 8.69150, 50.10063, 30, + 8.69821, 50.10228, 10), ncol = 3, byrow = TRUE)), dim = "XYM") +encode_sf(poly3d) } diff --git a/man/flexpolyline-package.Rd b/man/flexpolyline-package.Rd index 8c49fdb..68ed6fc 100644 --- a/man/flexpolyline-package.Rd +++ b/man/flexpolyline-package.Rd @@ -20,9 +20,9 @@ Binding to the C++ implementation of the flexible polyline \seealso{ Useful links: \itemize{ - \item \url{https://munterfinger.github.io/flexpolyline} - \item \url{https://github.com/munterfinger/flexpolyline} - \item Report bugs at \url{https://github.com/munterfinger/flexpolyline/issues} + \item \url{https://munterfinger.github.io/flexpolyline/} + \item \url{https://github.com/munterfinger/flexpolyline/} + \item Report bugs at \url{https://github.com/munterfinger/flexpolyline/issues/} } } diff --git a/man/get_third_dimension.Rd b/man/get_third_dimension.Rd new file mode 100644 index 0000000..6ab133b --- /dev/null +++ b/man/get_third_dimension.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/RcppExports.R +\name{get_third_dimension} +\alias{get_third_dimension} +\title{Get third dimension of a flexible polyline encoded string} +\usage{ +get_third_dimension(encoded) +} +\arguments{ +\item{encoded}{character, encoded flexible polyline string.} +} +\value{ +A string describing the third dimension. +} +\description{ +This function calls \code{hf::get_third_dimension} of the C++ implementation +of the flexible polyline encoding by HERE and return the type of the third +dimension. +} +\examples{ +# 2d line +get_third_dimension("BFoz5xJ67i1B1B7PzIhaxL7Y") + +# 3d line +get_third_dimension("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU") +} diff --git a/man/set_third_dimension.Rd b/man/set_third_dimension.Rd new file mode 100644 index 0000000..b4b50f2 --- /dev/null +++ b/man/set_third_dimension.Rd @@ -0,0 +1,44 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/RcppExports.R +\name{set_third_dimension} +\alias{set_third_dimension} +\title{Set third dimension of a flexible polyline encoded string} +\usage{ +set_third_dimension( + encoded, + third_dim_name, + precision = 5L, + third_dim_precision = 5L +) +} +\arguments{ +\item{encoded}{character, encoded flexible polyline string.} + +\item{third_dim_name}{character, name of the third dimension to set (ABSENT, +LEVEL, ALTITUDE, ELEVATION, CUSTOM1, CUSTOM2).} + +\item{precision}{integer, precision to use in encoding (between 0 and 15, +\code{default=5}).} + +\item{third_dim_precision}{integer, precision to use in encoding for the +third dimension (between 1 and 15, \code{default=5}).} +} +\value{ +The line with the new third dimension as string in the flexible polyline +encoding format. +} +\description{ +This function decodes the flexible polyline encoded line, changes the third +dimension and encodes the line again. +} +\note{ +The precision is not read from the header of the encoded line. Therefore it +must be provided as a parameter for re-encoding. +} +\examples{ +# 2d line (nothing happens...) +set_third_dimension("BFoz5xJ67i1B1B7PzIhaxL7Y", "ELEVATION") + +# 3d line +set_third_dimension("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU", "ELEVATION") +} diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index a91c85f..726f63d 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -30,10 +30,37 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// get_third_dimension +std::string get_third_dimension(SEXP encoded); +RcppExport SEXP _flexpolyline_get_third_dimension(SEXP encodedSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< SEXP >::type encoded(encodedSEXP); + rcpp_result_gen = Rcpp::wrap(get_third_dimension(encoded)); + return rcpp_result_gen; +END_RCPP +} +// set_third_dimension +std::string set_third_dimension(SEXP encoded, SEXP third_dim_name, int precision, int third_dim_precision); +RcppExport SEXP _flexpolyline_set_third_dimension(SEXP encodedSEXP, SEXP third_dim_nameSEXP, SEXP precisionSEXP, SEXP third_dim_precisionSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< SEXP >::type encoded(encodedSEXP); + Rcpp::traits::input_parameter< SEXP >::type third_dim_name(third_dim_nameSEXP); + Rcpp::traits::input_parameter< int >::type precision(precisionSEXP); + Rcpp::traits::input_parameter< int >::type third_dim_precision(third_dim_precisionSEXP); + rcpp_result_gen = Rcpp::wrap(set_third_dimension(encoded, third_dim_name, precision, third_dim_precision)); + return rcpp_result_gen; +END_RCPP +} static const R_CallMethodDef CallEntries[] = { {"_flexpolyline_decode", (DL_FUNC) &_flexpolyline_decode, 1}, {"_flexpolyline_encode", (DL_FUNC) &_flexpolyline_encode, 4}, + {"_flexpolyline_get_third_dimension", (DL_FUNC) &_flexpolyline_get_third_dimension, 1}, + {"_flexpolyline_set_third_dimension", (DL_FUNC) &_flexpolyline_set_third_dimension, 4}, {NULL, NULL, 0} }; diff --git a/src/flexpolyline.cpp b/src/flexpolyline.cpp index 6ca231f..576b43a 100644 --- a/src/flexpolyline.cpp +++ b/src/flexpolyline.cpp @@ -1,16 +1,19 @@ +#include "hf/flexpolyline.h" + #include + +#include #include -#include #include -#include -#include "hf/flexpolyline.h" +#include using namespace Rcpp; //' Decode a flexible polyline encoded string //' -//' This function calls \code{hf::polyline_decode} and \code{hf::get_third_dimension} -//' of the C++ implementation of the flexible polyline encoding by HERE. Depending -//' on the dimensions of the encoded line, a two or three dimensional line is decoded. +//' This function calls \code{hf::polyline_decode} and +//' \code{hf::get_third_dimension} of the C++ implementation of the flexible +//' polyline encoding by HERE. Depending on the dimensions of the encoded line, +//' a two or three dimensional line is decoded. //' //' @param encoded character, encoded flexible polyline string. //' @@ -27,58 +30,52 @@ using namespace Rcpp; //' decode("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU") // [[Rcpp::export]] NumericMatrix decode(SEXP encoded) { - - // Convert from R SEXP to std::string - std::string encoded_str = Rcpp::as(encoded); - - // Initialize decoder - std::vector> polyline; - auto res = hf::polyline_decode( - encoded_str, [&polyline](double lat, double lng, double z) { - polyline.push_back(std::make_tuple(lat, lng, z)); - }); - - // Check valid encoding - if (!res) { - throw std::invalid_argument("Invalid encoding"); - } - - // Extract third dimension type - const char * dim_name[] = { - "ABSENT", "LEVEL", "ALTITUDE", "ELEVATION", - "RESERVED1", "RESERVED2", // Should not be used... - "CUSTOM1", "CUSTOM2" - }; - hf::ThirdDim thrd = hf::get_third_dimension(encoded_str); - int index = static_cast::type>(thrd); - - // Get line coordinates - size_t n = polyline.size(); - NumericMatrix coords(n, 2 + !!index); - - if (!!index) { - - // 3d case (index > 0) - for (size_t i = 0; i < n; ++i) { - coords( i, 0 ) = std::get<1>(polyline[i]); - coords( i, 1 ) = std::get<0>(polyline[i]); - coords( i, 2 ) = std::get<2>(polyline[i]); + // Convert from R SEXP to std::string + std::string encoded_str = Rcpp::as(encoded); + + // Initialize decoder + std::vector> polyline; + auto res = hf::polyline_decode( + encoded_str, [&polyline](double lat, double lng, double z) { + polyline.push_back(std::make_tuple(lat, lng, z)); + }); + + // Check valid encoding + if (!res) { + throw std::invalid_argument("Invalid encoding"); } - colnames(coords) = CharacterVector({"LNG", "LAT", dim_name[index]}); - } else { - - // 2d case, third dimension ABSENT (index == 0) - for (size_t i = 0; i < n; ++i) { - coords( i, 0 ) = std::get<1>(polyline[i]); - coords( i, 1 ) = std::get<0>(polyline[i]); + // Extract third dimension type + const char* dim_name[] = { + "ABSENT", "LEVEL", "ALTITUDE", + "ELEVATION", "RESERVED1", "RESERVED2", // Should not be used... + "CUSTOM1", "CUSTOM2"}; + hf::ThirdDim thrd = hf::get_third_dimension(encoded_str); + int index = static_cast::type>(thrd); + + // Get line coordinates + size_t n = polyline.size(); + NumericMatrix coords(n, 2 + !!index); + + if (!!index) { + // 3d case (index > 0) + for (size_t i = 0; i < n; ++i) { + coords(i, 0) = std::get<1>(polyline[i]); + coords(i, 1) = std::get<0>(polyline[i]); + coords(i, 2) = std::get<2>(polyline[i]); + } + colnames(coords) = CharacterVector({"LNG", "LAT", dim_name[index]}); + + } else { + // 2d case, third dimension ABSENT (index == 0) + for (size_t i = 0; i < n; ++i) { + coords(i, 0) = std::get<1>(polyline[i]); + coords(i, 1) = std::get<0>(polyline[i]); + } + colnames(coords) = CharacterVector({"LNG", "LAT"}); } - colnames(coords) = CharacterVector({"LNG", "LAT"}); - - } - - return coords; + return coords; } //' Encode a line in the flexible polyline encoding format @@ -87,10 +84,14 @@ NumericMatrix decode(SEXP encoded) { //' the flexible polyline encoding by HERE. Depending on the dimensions of the //' input coordinates, a two or three dimensional line is encoded. //' -//' @param line matrix, coordinates of the line in 2d or 3d (column order: LNG, LAT, DIM3). -//' @param precision integer, precision to use in encoding (between 0 and 15, \code{default=5}). -//' @param third_dim integer, type of the third dimension (0: ABSENT, 1: LEVEL, 2: ALTITUDE, 3: ELEVATION, 4, 6: CUSTOM1, 7: CUSTOM2, \code{default=3}). -//' @param third_dim_precision integer, precision to use in encoding for the third dimension (between 1 and 15, \code{default=5}). +//' @param line matrix, coordinates of the line in 2d or 3d (column order: LNG, +//' LAT, DIM3). +//' @param precision integer, precision to use in encoding (between 0 and 15, +//' \code{default=5}). +//' @param third_dim integer, type of the third dimension (0: ABSENT, 1: LEVEL, +//' 2: ALTITUDE, 3: ELEVATION, 4, 6: CUSTOM1, 7: CUSTOM2, \code{default=3}). +//' @param third_dim_precision integer, precision to use in encoding for the +//' third dimension (between 1 and 15, \code{default=5}). //' //' @return //' The line as string in the flexible polyline encoding format. @@ -118,43 +119,135 @@ NumericMatrix decode(SEXP encoded) { //' ) //' encode(line3d) // [[Rcpp::export]] -String encode(NumericMatrix line, int precision = 5, - int third_dim = 3, int third_dim_precision = 5) { - - String encoded; - size_t n = line.rows(); +String encode(NumericMatrix line, int precision = 5, int third_dim = 3, + int third_dim_precision = 5) { + String encoded; + size_t n = line.rows(); + + if (line.cols() == 2) { + // 2d case: Set third dimension to ABSENT and third dimension precision + // to 0 + std::vector> input; + for (size_t i = 0; i < n; ++i) { + input.push_back(std::make_pair(line(i, 1), line(i, 0))); + } + encoded = + hf::polyline_encode(input, precision, hf::ThirdDim::ABSENT, 0); + + } else if (line.cols() == 3) { + // 3d case: Use third dimension with third dimension precision + std::vector> input; + for (size_t i = 0; i < n; ++i) { + input.push_back( + std::make_tuple(line(i, 1), line(i, 0), line(i, 2))); + } + encoded = hf::polyline_encode(input, precision, + static_cast(third_dim), + third_dim_precision); + + } else { + throw std::invalid_argument("Invalid input dimensions"); + } - if (line.cols() == 2) { + return encoded; +} - // 2d case: Set third dimension to ABSENT and third dimension precision to 0 - std::vector> input; - for (size_t i = 0; i < n; ++i) { - input.push_back( - std::make_pair(line( i, 1 ), line( i, 0 )) - ); - } - encoded = hf::polyline_encode(input, precision, hf::ThirdDim::ABSENT, 0); +//' Get third dimension of a flexible polyline encoded string +//' +//' This function calls \code{hf::get_third_dimension} of the C++ implementation +//' of the flexible polyline encoding by HERE and return the type of the third +//' dimension. +//' +//' @param encoded character, encoded flexible polyline string. +//' +//' @return +//' A string describing the third dimension. +//' +//' @export +//' +//' @examples +//' # 2d line +//' get_third_dimension("BFoz5xJ67i1B1B7PzIhaxL7Y") +//' +//' # 3d line +//' get_third_dimension("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU") +// [[Rcpp::export]] +std::string get_third_dimension(SEXP encoded) { + const char* dim_name[] = { + "ABSENT", "LEVEL", "ALTITUDE", + "ELEVATION", "RESERVED1", "RESERVED2", // Should not be used... + "CUSTOM1", "CUSTOM2"}; - } else if (line.cols() == 3) { + // Convert from R SEXP to std::string + std::string encoded_str = Rcpp::as(encoded); - // 3d case: Use third dimension with third dimension precision - std::vector> input; - for (size_t i = 0; i < n; ++i) { - input.push_back( - std::make_tuple(line( i, 1 ), line( i, 0 ), line( i, 2 )) - ); - } - encoded = hf::polyline_encode( - input, precision, static_cast(third_dim), third_dim_precision - ); + // Extract third dimension type + hf::ThirdDim thrd = hf::get_third_dimension(encoded_str); + int index = static_cast::type>(thrd); - } else { + return dim_name[index]; +} - throw std::invalid_argument("Invalid input dimensions"); +//' Set third dimension of a flexible polyline encoded string +//' +//' This function decodes the flexible polyline encoded line, changes the third +//' dimension and encodes the line again. +//' +//' @note +//' The precision is not read from the header of the encoded line. Therefore it +//' must be provided as a parameter for re-encoding. +//' +//' @param encoded character, encoded flexible polyline string. +//' @param third_dim_name character, name of the third dimension to set (ABSENT, +//' LEVEL, ALTITUDE, ELEVATION, CUSTOM1, CUSTOM2). +//' @param precision integer, precision to use in encoding (between 0 and 15, +//' \code{default=5}). +//' @param third_dim_precision integer, precision to use in encoding for the +//' third dimension (between 1 and 15, \code{default=5}). +//' +//' @return +//' The line with the new third dimension as string in the flexible polyline +//' encoding format. +//' +//' @export +//' +//' @examples +//' # 2d line (nothing happens...) +//' set_third_dimension("BFoz5xJ67i1B1B7PzIhaxL7Y", "ELEVATION") +//' +//' # 3d line +//' set_third_dimension("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU", "ELEVATION") +// [[Rcpp::export]] +std::string set_third_dimension(SEXP encoded, SEXP third_dim_name, + int precision = 5, + int third_dim_precision = 5) { + int third_dim_ind = -1; + const char* dim_name[] = { + "ABSENT", "LEVEL", "ALTITUDE", + "ELEVATION", "RESERVED1", "RESERVED2", // Should not be used... + "CUSTOM1", "CUSTOM2"}; + + // Convert from R SEXP to std::string + std::string third_dim_str = Rcpp::as(third_dim_name); + + // Decode + NumericMatrix decoded = decode(encoded); + + // Match third dimension type + for (size_t i = 0; i != (sizeof dim_name / sizeof *dim_name); i++) { + if (dim_name[i] == third_dim_str) { + third_dim_ind = i; + } + } - } + // Check if dimension is valid. + if (third_dim_ind == -1) { + throw std::invalid_argument("Invalid input name of third dimension"); + } - return encoded; + // Encode with new third dimension + String encoded_new = + encode(decoded, precision, third_dim_ind, third_dim_precision); + return encoded_new; } - diff --git a/tests/testthat/test-cpp_binding.R b/tests/testthat/test-cpp_binding.R index 00ac643..8777460 100644 --- a/tests/testthat/test-cpp_binding.R +++ b/tests/testthat/test-cpp_binding.R @@ -6,6 +6,11 @@ test_that("Cpp binding to 'flexpolyline.h' en- and decodes correctly", { expect_error(encode(matrix(1,2,3), third_dim = -1), "third_dim out of range", class = "std::out_of_range") expect_error(encode(matrix(1,2,3), third_dim_precision = -1), "third_dim_precision out of range", class = "std::out_of_range") expect_error(decode("123"), "Invalid encoding", class = "std::invalid_argument") + expect_error(set_third_dimension("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU", "NOT A DIM"), "Invalid input name of third dimension", class = "std::invalid_argument") + + # Get and set third dimension + expect_equal(get_third_dimension("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU"), "ALTITUDE") + expect_equal(get_third_dimension(set_third_dimension("BlBoz5xJ67i1BU1B7PUzIhaUxL7YU", "ELEVATION")), "ELEVATION") # Read and preprocess test data parse_test_examples <- function(input) { @@ -77,7 +82,7 @@ test_that("Cpp binding to 'flexpolyline.h' en- and decodes correctly", { # Test encoding expect_equal( any( - sapply(1:length(enc), function(i) { + vapply(seq_along(enc), function(i) { # Omit reserved dimensions and encoding with precision higher than 7 if ( @@ -101,7 +106,7 @@ test_that("Cpp binding to 'flexpolyline.h' en- and decodes correctly", { # Test equality enc[i] != encoded - }) + }, logical(1)) ), FALSE ) @@ -109,7 +114,7 @@ test_that("Cpp binding to 'flexpolyline.h' en- and decodes correctly", { # Test decoding expect_equal( any( - sapply(1:length(dec), function(i) { + vapply(seq_along(dec), function(i) { # Omit reserved dimensions and decoding with precision higher than 7 if ( @@ -130,7 +135,7 @@ test_that("Cpp binding to 'flexpolyline.h' en- and decodes correctly", { # Test equality any(dec[[i]]$coords != decoded) - }) + }, logical(1)) ), FALSE ) diff --git a/tests/testthat/test-decode_sf.R b/tests/testthat/test-decode_sf.R index 48aedb0..7117fe2 100644 --- a/tests/testthat/test-decode_sf.R +++ b/tests/testthat/test-decode_sf.R @@ -2,24 +2,25 @@ test_that("decode_sf works", { # Encoded lines n <- 5 - encodedXY <- "BFoz5xJ67i1B1B7PzIhaxL7Y" - encodedXYZ <- "BlBoz5xJ67i1BU1B7PUzIhaUxL7YU" - encodedXYM <- as.factor("BlXoz5xJ67i1Bgkh9B1B7Pgkh9BzIhagkh9BxL7Ygkh9B") + encodedXYZ <- "B1Voz5xJ67i1Bgkh9B" + encodedXY <- "BFoz5xJ67i1B1B7PlU9yB" + encodedXYM <- as.factor("BlXoz5xJ67i1Bgkh9B1B7Pgkh9BzIhagkh9BqK-pB_ni6D") # Decode + pointXYZ <- decode_sf(rep(encodedXYZ, n)) lineXY <- decode_sf(rep(encodedXY, n)) - lineXYZ <- decode_sf(rep(encodedXYZ, n)) - lineXYM <- decode_sf(rep(encodedXYM, n)) + polyXYM <- decode_sf(rep(encodedXYM, n)) # Test decode_sf() + expect_s3_class(pointXYZ, c("sf", "data.frame"), exact = TRUE) expect_s3_class(lineXY, c("sf", "data.frame"), exact = TRUE) - expect_s3_class(lineXYZ, c("sf", "data.frame"), exact = TRUE) - expect_s3_class(lineXYM, c("sf", "data.frame"), exact = TRUE) + expect_s3_class(polyXYM, c("sf", "data.frame"), exact = TRUE) + expect_s3_class(decode_sf(c("BlXoz5xJ67i1Bgkh9B", "BlXoz5xJ67i1Bgkh9B1B7Pgkh9BzIhagkh9B", "BlXoz5xJ67i1Bgkh9B1B7Pgkh9BzIhagkh9BqK-pB_ni6D")), c("sf", "data.frame"), exact = TRUE) + expect_true(all(sf::st_geometry_type(pointXYZ) == "POINT")) expect_true(all(sf::st_geometry_type(lineXY) == "LINESTRING")) - expect_true(all(sf::st_geometry_type(lineXYZ) == "LINESTRING")) - expect_true(all(sf::st_geometry_type(lineXYM) == "LINESTRING")) + expect_true(all(sf::st_geometry_type(polyXYM) == "POLYGON")) + expect_equal(nrow(pointXYZ), n) expect_equal(nrow(lineXY), n) - expect_equal(nrow(lineXYZ), n) - expect_equal(nrow(lineXYM), n) + expect_equal(nrow(polyXYM), n) }) diff --git a/tests/testthat/test-encode_sf.R b/tests/testthat/test-encode_sf.R index a152a86..e0249f4 100644 --- a/tests/testthat/test-encode_sf.R +++ b/tests/testthat/test-encode_sf.R @@ -1,47 +1,39 @@ test_that("encode_sf works", { - # 2d line - line2d <- matrix( - c(8.69821, 50.10228, - 8.69567, 50.10201, - 8.69150, 50.10063, - 8.68752, 50.09878), - ncol = 2, byrow = TRUE - ) + # 3D point + point3d <- sf::st_point( + matrix(c(8.69821, 50.10228, 10), ncol = 3, byrow = TRUE), dim = "XYZ") - # 3d line - line3d <- matrix( - c(8.69821, 50.10228, 10.11111, - 8.69567, 50.10201, 20.22222, - 8.69150, 50.10063, 30.33333, - 8.68752, 50.09878, 40.44444), - ncol = 3, byrow = TRUE - ) + # 2D linestring + line2d <- sf::st_linestring( + matrix(c(8.69821, 50.10228, + 8.69567, 50.10201, + 8.68752, 50.09878), ncol = 2, byrow = TRUE)) - # Test encode_sf() - expect_error( - encode_sf(sf::st_point(c(1,2,3))), - "Invalid geometry type 'POINT' of input, only 'LINESTRING' is supported." - ) + # 3D polygon + poly3d <- sf::st_polygon(list( + matrix(c(8.69821, 50.10228, 10, + 8.69567, 50.10201, 20, + 8.69150, 50.10063, 30, + 8.69821, 50.10228, 10), ncol = 3, byrow = TRUE)), dim = "XYM") + # Test encode_sf() + expect_error(encode_sf(sf::st_multilinestring()), + "Invalid geometry type 'MULTILINESTRING' of input, only 'POINT', 'LINESTRING' and 'POLYGON' is supported.") ## sfg (XY, XYZ and XYM) - line2d_sfg <- sf::st_linestring(line2d) - line3d_sfg <- sf::st_linestring(line3d, dim = "XYZ") - line3dm_sfg <- sf::st_linestring(line3d, dim = "XYM") - expect_type(encode_sf(line2d_sfg), "character") - expect_type(encode_sf(line3d_sfg), "character") - expect_type(encode_sf(line3dm_sfg), "character") - + expect_type(encode_sf(point3d), "character") + expect_type(encode_sf(line2d), "character") + expect_type(encode_sf(poly3d), "character") ## sfc (XY and XYZ) - line2d_sfc <- sf::st_as_sfc(list(line2d_sfg, line2d_sfg), crs = 4326) - line3d_sfc <- sf::st_as_sfc(list(line3d_sfg, line3d_sfg), crs = 4326) + point3d_sfc <- sf::st_as_sfc(list(point3d, point3d), crs = 4326) + line2d_sfc <- sf::st_as_sfc(list(line2d, line2d), crs = 4326) + poly3d_sfc <- sf::st_as_sfc(list(poly3d, poly3d), crs = 4326) + expect_type(encode_sf(point3d_sfc), "character") expect_type(encode_sf(line2d_sfc), "character") - expect_type(encode_sf(line3d_sfc), "character") - + expect_type(encode_sf(poly3d_sfc), "character") ## sf (XY and XYZ) - line2d_sf <- sf::st_as_sf(line2d_sfc) - line3d_sf <- sf::st_as_sf(line3d_sfc) - expect_type(encode_sf(line2d_sf), "character") - expect_type(encode_sf(line3d_sf), "character") + expect_type(encode_sf(sf::st_as_sf(point3d_sfc)), "character") + expect_type(encode_sf(sf::st_as_sf(line2d_sfc)), "character") + expect_type(encode_sf(sf::st_as_sf(poly3d_sfc)), "character") }) diff --git a/vignettes/sf-support.Rmd b/vignettes/sf-support.Rmd index 14f34c7..a9bb8c2 100644 --- a/vignettes/sf-support.Rmd +++ b/vignettes/sf-support.Rmd @@ -25,8 +25,8 @@ from databases, and which geometrical operations should be defined for them."* The most common geometry types of simple features are: POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON. All geometry types are based on POINTs. -This package only supports the encoding and decoding of the geometry type -LINESTRING. +This package supports the encoding and decoding of the geometry types POINT, +LINESTRING and POLYGON. There are four possible dimension combinations of geometries in the sf package. In the `flexpolyine` package the first three dimension combinations are