From 4d4b4b6d932980fad524a23407a6a505282d6d48 Mon Sep 17 00:00:00 2001 From: SymbolixAU Date: Tue, 13 Nov 2018 17:36:12 +1100 Subject: [PATCH] z, m and tests, closes #49 --- R/RcppExports.R | 8 ++-- R/sf_geojson.R | 31 ++++++++++++--- man/df_geojson.Rd | 24 +++++++++-- src/RcppExports.cpp | 19 +++++---- src/df_geojson.cpp | 68 ++++++++++++++++---------------- tests/testthat/test-df_geojson.R | 23 +++++++++++ 6 files changed, 115 insertions(+), 58 deletions(-) diff --git a/R/RcppExports.R b/R/RcppExports.R index 3281627..12d3b2a 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -1,12 +1,12 @@ # Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 -rcpp_df_to_geojson_atomise <- function(df, lon, lat) { - .Call(`_geojsonsf_rcpp_df_to_geojson_atomise`, df, lon, lat) +rcpp_df_to_geojson_atomise <- function(df, geometry_columns) { + .Call(`_geojsonsf_rcpp_df_to_geojson_atomise`, df, geometry_columns) } -rcpp_df_to_geojson <- function(sf, geometry_columns) { - .Call(`_geojsonsf_rcpp_df_to_geojson`, sf, geometry_columns) +rcpp_df_to_geojson <- function(df, geometry_columns) { + .Call(`_geojsonsf_rcpp_df_to_geojson`, df, geometry_columns) } rcpp_geojson_to_sfc <- function(geojson, expand_geometries) { diff --git a/R/sf_geojson.R b/R/sf_geojson.R index 28995e4..b25b8b7 100644 --- a/R/sf_geojson.R +++ b/R/sf_geojson.R @@ -73,6 +73,9 @@ sfc_geojson.default <- function(sfc) stop("Expected an sfc object") #' @param df data.frame #' @param lon column of \code{df} containing the longitude data #' @param lat column of \code{df} containing the latitude data +#' @param z column of \code{df} containing the Z attribute of the GeoJSON +#' @param m column of \code{df} containing the M attribute of the GeoJSON. +#' If supplied, you must also supply \code{z} #' @param atomise logical indicating if the data.frame should be converted into a vector #' of GeoJSON objects #' @param simplify logical indicating if data.frame without property columns should simplify @@ -85,21 +88,37 @@ sfc_geojson.default <- function(sfc) stop("Expected an sfc object") #' #' df <- data.frame(lon = c(1:5, NA), lat = c(1:5, NA), id = 1:6, val = letters[1:6]) #' df_geojson( df, lon = "lon", lat = "lat") -#' df_geojson( df, lon = "lon", lat = "lat", atomise = T) +#' df_geojson( df, lon = "lon", lat = "lat", atomise = TRUE) #' #' df <- data.frame(lon = c(1:5, NA), lat = c(1:5, NA) ) #' df_geojson( df, lon = "lon", lat = "lat") -#' df_geojson( df, lon = "lon", lat = "lat", simplify = T) +#' df_geojson( df, lon = "lon", lat = "lat", simplify = FALSE) #' +#' df <- data.frame(lon = c(1:5), lat = c(1:5), elevation = c(1:5) ) +#' df_geojson( df, lon = "lon", lat = "lat", z = "elevation") +#' df_geojson( df, lon = "lon", lat = "lat", z = "elevation", simplify = FALSE) +#' +#' df <- data.frame(lon = c(1:5), lat = c(1:5), elevation = c(1:5), id = 1:5 ) +#' df_geojson( df, lon = "lon", lat = "lat", z = "elevation") +#' df_geojson( df, lon = "lon", lat = "lat", z = "elevation", atomise = TRUE) +#' +#' +#' ## to sf objects +#' geo <- df_geojson( df, lon = "lon", lat = "lat", z = "elevation") +#' sf <- geojson_sf( geo ) #' #' @export -df_geojson <- function(df, lon, lat, atomise = FALSE, simplify = TRUE) UseMethod("df_geojson") +df_geojson <- function(df, lon, lat, z = NULL, m = NULL, atomise = FALSE, simplify = TRUE) UseMethod("df_geojson") #' @export -df_geojson.data.frame <- function(df, lon, lat, atomise = FALSE, simplify = TRUE) { +df_geojson.data.frame <- function(df, lon, lat, z = NULL, m = NULL, atomise = FALSE, simplify = TRUE) { df <- handle_dates( df ) lon <- force( lon ) lat <- force( lat ) - if( atomise | ( ncol( df ) == 2 & simplify ) ) return( rcpp_df_to_geojson_atomise( df, lon, lat ) ) - return( rcpp_df_to_geojson( df, c(lon, lat) ) ) + z <- force( z ) + m <- force( m ) + if( is.null(z) && !is.null(m)) stop("z must be supplied when using m") + geometries <- c(lon, lat, z, m) + if( atomise | ( ncol( df ) == length( geometries ) & simplify ) ) return( rcpp_df_to_geojson_atomise( df, geometries ) ) + return( rcpp_df_to_geojson( df, geometries ) ) } diff --git a/man/df_geojson.Rd b/man/df_geojson.Rd index 77296de..e5e83c7 100644 --- a/man/df_geojson.Rd +++ b/man/df_geojson.Rd @@ -4,7 +4,8 @@ \alias{df_geojson} \title{df to GeoJSON} \usage{ -df_geojson(df, lon, lat, atomise = FALSE, simplify = TRUE) +df_geojson(df, lon, lat, z = NULL, m = NULL, atomise = FALSE, + simplify = TRUE) } \arguments{ \item{df}{data.frame} @@ -13,6 +14,11 @@ df_geojson(df, lon, lat, atomise = FALSE, simplify = TRUE) \item{lat}{column of \code{df} containing the latitude data} +\item{z}{column of \code{df} containing the Z attribute of the GeoJSON} + +\item{m}{column of \code{df} containing the M attribute of the GeoJSON. +If supplied, you must also supply \code{z}} + \item{atomise}{logical indicating if the data.frame should be converted into a vector of GeoJSON objects} @@ -30,11 +36,23 @@ Converts data.frame objects to GeoJSON. Each row is considerd a POINT df <- data.frame(lon = c(1:5, NA), lat = c(1:5, NA), id = 1:6, val = letters[1:6]) df_geojson( df, lon = "lon", lat = "lat") -df_geojson( df, lon = "lon", lat = "lat", atomise = T) +df_geojson( df, lon = "lon", lat = "lat", atomise = TRUE) df <- data.frame(lon = c(1:5, NA), lat = c(1:5, NA) ) df_geojson( df, lon = "lon", lat = "lat") -df_geojson( df, lon = "lon", lat = "lat", simplify = T) +df_geojson( df, lon = "lon", lat = "lat", simplify = FALSE) + +df <- data.frame(lon = c(1:5), lat = c(1:5), elevation = c(1:5) ) +df_geojson( df, lon = "lon", lat = "lat", z = "elevation") +df_geojson( df, lon = "lon", lat = "lat", z = "elevation", simplify = FALSE) + +df <- data.frame(lon = c(1:5), lat = c(1:5), elevation = c(1:5), id = 1:5 ) +df_geojson( df, lon = "lon", lat = "lat", z = "elevation") +df_geojson( df, lon = "lon", lat = "lat", z = "elevation", atomise = TRUE) + +## to sf objects +geo <- df_geojson( df, lon = "lon", lat = "lat", z = "elevation") +sf <- geojson_sf( geo ) } diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 224ad01..7557bad 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -6,27 +6,26 @@ using namespace Rcpp; // rcpp_df_to_geojson_atomise -Rcpp::StringVector rcpp_df_to_geojson_atomise(Rcpp::DataFrame& df, const char* lon, const char* lat); -RcppExport SEXP _geojsonsf_rcpp_df_to_geojson_atomise(SEXP dfSEXP, SEXP lonSEXP, SEXP latSEXP) { +Rcpp::StringVector rcpp_df_to_geojson_atomise(Rcpp::DataFrame& df, Rcpp::StringVector& geometry_columns); +RcppExport SEXP _geojsonsf_rcpp_df_to_geojson_atomise(SEXP dfSEXP, SEXP geometry_columnsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< Rcpp::DataFrame& >::type df(dfSEXP); - Rcpp::traits::input_parameter< const char* >::type lon(lonSEXP); - Rcpp::traits::input_parameter< const char* >::type lat(latSEXP); - rcpp_result_gen = Rcpp::wrap(rcpp_df_to_geojson_atomise(df, lon, lat)); + Rcpp::traits::input_parameter< Rcpp::StringVector& >::type geometry_columns(geometry_columnsSEXP); + rcpp_result_gen = Rcpp::wrap(rcpp_df_to_geojson_atomise(df, geometry_columns)); return rcpp_result_gen; END_RCPP } // rcpp_df_to_geojson -Rcpp::StringVector rcpp_df_to_geojson(Rcpp::DataFrame& sf, Rcpp::StringVector& geometry_columns); -RcppExport SEXP _geojsonsf_rcpp_df_to_geojson(SEXP sfSEXP, SEXP geometry_columnsSEXP) { +Rcpp::StringVector rcpp_df_to_geojson(Rcpp::DataFrame& df, Rcpp::StringVector& geometry_columns); +RcppExport SEXP _geojsonsf_rcpp_df_to_geojson(SEXP dfSEXP, SEXP geometry_columnsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< Rcpp::DataFrame& >::type sf(sfSEXP); + Rcpp::traits::input_parameter< Rcpp::DataFrame& >::type df(dfSEXP); Rcpp::traits::input_parameter< Rcpp::StringVector& >::type geometry_columns(geometry_columnsSEXP); - rcpp_result_gen = Rcpp::wrap(rcpp_df_to_geojson(sf, geometry_columns)); + rcpp_result_gen = Rcpp::wrap(rcpp_df_to_geojson(df, geometry_columns)); return rcpp_result_gen; END_RCPP } @@ -124,7 +123,7 @@ END_RCPP } static const R_CallMethodDef CallEntries[] = { - {"_geojsonsf_rcpp_df_to_geojson_atomise", (DL_FUNC) &_geojsonsf_rcpp_df_to_geojson_atomise, 3}, + {"_geojsonsf_rcpp_df_to_geojson_atomise", (DL_FUNC) &_geojsonsf_rcpp_df_to_geojson_atomise, 2}, {"_geojsonsf_rcpp_df_to_geojson", (DL_FUNC) &_geojsonsf_rcpp_df_to_geojson, 2}, {"_geojsonsf_rcpp_geojson_to_sfc", (DL_FUNC) &_geojsonsf_rcpp_geojson_to_sfc, 2}, {"_geojsonsf_rcpp_geojson_to_sf", (DL_FUNC) &_geojsonsf_rcpp_geojson_to_sf, 2}, diff --git a/src/df_geojson.cpp b/src/df_geojson.cpp index bca9674..cb9036d 100644 --- a/src/df_geojson.cpp +++ b/src/df_geojson.cpp @@ -47,27 +47,38 @@ void write_geojson(Writer& writer, SEXP sfg, std::string& geom_type, Rcpp::Chara // [[Rcpp::export]] Rcpp::StringVector rcpp_df_to_geojson_atomise( Rcpp::DataFrame& df, - const char* lon, - const char* lat ) { + Rcpp::StringVector& geometry_columns ) { size_t n_cols = df.ncol(); - size_t n_properties = n_cols - 2; // LON & LAT columns size_t n_rows = df.nrows(); size_t i, j; Rcpp::StringVector column_names = df.names(); - Rcpp::StringVector property_names(df.size() - 1); Rcpp::StringVector geojson( n_rows ); - // the sfc_POINT - Rcpp::NumericVector nv_lon = df[lon]; - Rcpp::NumericVector nv_lat = df[lat]; - Rcpp::CharacterVector cls = Rcpp::CharacterVector::create("XY", "POINT", "sfg"); + size_t n_geometry_columns = geometry_columns.size(); + Rcpp::List geometry_vectors( n_geometry_columns ); + + size_t n_properties = n_cols - n_geometry_columns; + Rcpp::StringVector property_names( n_properties ); + + for ( i = 0; i < n_geometry_columns; i++ ) { + Rcpp::String this_geometry = geometry_columns[i]; + geometry_vectors[i] = df[ this_geometry ]; + } + + std::string dim = geojsonsf::utils::make_dimension( n_geometry_columns ); + Rcpp::CharacterVector cls = Rcpp::CharacterVector::create( dim , "POINT", "sfg"); int property_counter = 0; for (int i = 0; i < df.length(); i++) { - if ( column_names[i] != lon && column_names[i] != lat ) { + + Rcpp::String this_column = column_names[i]; + int idx = geojsonsf::utils::where::where_is( this_column, geometry_columns ); + + if ( idx == -1 ) { // i.e. it's not in the vector + property_names[property_counter] = column_names[i]; property_counter++; } @@ -103,7 +114,12 @@ Rcpp::StringVector rcpp_df_to_geojson_atomise( writer.String("geometry"); } - SEXP sfg = Rcpp::NumericVector::create(nv_lon[i], nv_lat[i]); + Rcpp::NumericVector geom( n_geometry_columns ); + for ( j = 0; j < n_geometry_columns; j++ ) { + Rcpp::NumericVector this_geometry_vector = geometry_vectors[j]; + geom[j] = this_geometry_vector[i]; + } + SEXP sfg = geom; write_geometry( writer, sfg, cls ); if( n_properties > 0 ) { @@ -118,23 +134,16 @@ Rcpp::StringVector rcpp_df_to_geojson_atomise( // [[Rcpp::export]] Rcpp::StringVector rcpp_df_to_geojson( - Rcpp::DataFrame& sf, + Rcpp::DataFrame& df, Rcpp::StringVector& geometry_columns ) { - // TODO change the lon & lat to be character / string vector specifying the columns to use - // so we can dynmically use Z&M attributes where appropriate. - // and change the cls accordingly - rapidjson::StringBuffer sb; rapidjson::Writer < rapidjson::StringBuffer > writer( sb ); - //std::string geom_column = sf.attr("sf_column"); - - size_t n_cols = sf.ncol(); - //size_t n_properties = n_cols - 2; // LON & LAT columns - size_t n_rows = sf.nrows(); + size_t n_cols = df.ncol(); + size_t n_rows = df.nrows(); size_t i, j; - Rcpp::StringVector column_names = sf.names(); + Rcpp::StringVector column_names = df.names(); // the sfc_POINT size_t n_geometry_columns = geometry_columns.size(); @@ -143,30 +152,21 @@ Rcpp::StringVector rcpp_df_to_geojson( size_t n_properties = n_cols - n_geometry_columns; Rcpp::StringVector property_names( n_properties ); - // Rcpp::Rcout << "n_geometry_columns: " << n_geometry_columns << std::endl; - for ( i = 0; i < n_geometry_columns; i++ ) { Rcpp::String this_geometry = geometry_columns[i]; - geometry_vectors[i] = sf[ this_geometry ]; + geometry_vectors[i] = df[ this_geometry ]; } - // Rcpp::NumericVector nv_lon = sf[lon]; - // Rcpp::NumericVector nv_lat = sf[lat]; std::string dim = geojsonsf::utils::make_dimension( n_geometry_columns ); - // Rcpp::Rcout << "dim: " << dim << std::endl; Rcpp::CharacterVector cls = Rcpp::CharacterVector::create( dim , "POINT", "sfg"); int property_counter = 0; - for ( int i = 0; i < sf.length(); i++ ) { + for ( int i = 0; i < df.length(); i++ ) { Rcpp::String this_column = column_names[i]; int idx = geojsonsf::utils::where::where_is( this_column, geometry_columns ); - // Rcpp::Rcout << "this_column: " << this_column.get_cstring() << std::endl; - // Rcpp::Rcout << "idx: " << idx << std::endl; - - //if ( column_names[i] != lon && column_names[i] != lat ) { if ( idx == -1 ) { // i.e. it's not in the vector property_names[property_counter] = column_names[i]; property_counter++; @@ -189,7 +189,7 @@ Rcpp::StringVector rcpp_df_to_geojson( for( j = 0; j < n_properties; j++ ) { const char *h = property_names[ j ]; - SEXP this_vec = sf[ h ]; + SEXP this_vec = df[ h ]; jsonify::writers::write_value( writer, h ); jsonify::dataframe::dataframe_cell( writer, this_vec, i ); @@ -198,8 +198,6 @@ Rcpp::StringVector rcpp_df_to_geojson( writer.String("geometry"); - //SEXP sfg = Rcpp::NumericVector::create(nv_lon[i], nv_lat[i]); - Rcpp::NumericVector geom( n_geometry_columns ); for ( j = 0; j < n_geometry_columns; j++ ) { Rcpp::NumericVector this_geometry_vector = geometry_vectors[j]; diff --git a/tests/testthat/test-df_geojson.R b/tests/testthat/test-df_geojson.R index e0a1aa8..95a98e6 100644 --- a/tests/testthat/test-df_geojson.R +++ b/tests/testthat/test-df_geojson.R @@ -18,3 +18,26 @@ test_that("data.frame converted to GeoJSON", { expect_true(all(sapply(geo, jsonify::validate_json))) }) + +test_that("z and m handled", { + + n <- 1 + df <- data.frame(lon = c(1:n, NA), lat = c(1:n, NA), z = c(1:n, NA) ) + geo <- df_geojson( df, lon = "lon", lat = "lat", z = "z") + expect_true(length(geo) == 2) + expect_true(geo[1] == '{"type":"Point","coordinates":[1.0,1.0,1.0]}') + expect_true(geo[2] == 'null') + expect_true(all(sapply(geo, jsonify::validate_json))) + + df <- data.frame(lon = c(1:n, NA), lat = c(1:n, NA), z = c(1:n, NA), m = c(1:n, NA), id = 1:(n+1), val = letters[1:(n+1)]) + geo <- df_geojson( df, lon = "lon", lat = "lat", z = "z", m = "m") + expect_true(length(geo) == 1) + expect_true(jsonify::validate_json( geo ) ) + geo <- df_geojson( df, lon = "lon", lat = "lat", atomise = T) + expect_true(all(sapply(geo, jsonify::validate_json))) + + expect_error( + df_geojson( df, lon = "lon", lat = "lat", m = "m") + , "z must be supplied when using m" + ) +})