diff --git a/R/geojson.R b/R/geojson.R
index cda0647..f067ef7 100644
--- a/R/geojson.R
+++ b/R/geojson.R
@@ -3,26 +3,32 @@
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Options for reading in GeoJSON
#'
+#' @param type 'sf' or 'sfc'
#' @param property_promotion What is the most general container type to use when
#' properties differ across a FEATURECOLLECTION? E.g. if the property
#' exists both as a numeric and a string, should all values be promoted
#' to a 'string', or contained as different types in a 'list'.
-#' Default: 'string' will behave like 'geojsonsf'
-#' @param type 'sf' or 'sfc'
-#' @param property_promotion_lgl_as_int when promoting properties into a string,
-#' should logical values become strings e.g. "TRUE" or integers
-#' e.g. "1". Default: "integer" in order to match `geojsonsf` packages
+#' Default: 'string' will behave like \code{geojsonsf} package.
+#' @param property_promotion_lgl when \code{property_promotion = "string"}
+#' should logical values become words (i.e. \code{"TRUE"}/\code{"FALSE"})
+#' or integers (i.e. \code{"1"}/\code{"0"}).
+#' Default: "integer" in order to match \code{geojsonsf} package
#'
-#' @return named list
+#' @return Named list of options specific to reading GeoJSON
#' @export
+#'
+#' @examples
+#' # Create a set of options to use when reading geojson
+#' opts_read_geojson()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-opts_read_geojson <- function(type = c('sf', 'sfc'),
- property_promotion = c('string', 'list'),
- property_promotion_lgl_as_int = c('integer', 'string')) {
+opts_read_geojson <- function(type = c('sf', 'sfc'),
+ property_promotion = c('string', 'list'),
+ property_promotion_lgl = c('integer', 'string')) {
structure(
list(
- type = match.arg(type),
- property_promotion = match.arg(property_promotion)
+ type = match.arg(type),
+ property_promotion = match.arg(property_promotion),
+ property_promotion_lgl = match.arg(property_promotion_lgl)
),
class = "opts_read_geojson"
)
@@ -33,8 +39,12 @@ opts_read_geojson <- function(type = c('sf', 'sfc'),
#'
#' Currently no options available.
#'
-#' @return named list of options
+#' @return Named list of options specific to writing GeoJSON
#' @export
+#'
+#' @examples
+#' # Create a set of options to use when writing geojson
+#' opts_write_geojson()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
opts_write_geojson <- function() {
structure(
@@ -49,23 +59,33 @@ opts_write_geojson <- function() {
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Load GeoJSON as \code{sf} object
#'
-#' @param filename filename
-#' @param str single character string containing GeoJSON
-#' @param opts named list of options. Usually created with \code{opts_read_geojson()}.
+#' @param filename Filename
+#' @param str Single string containing GeoJSON
+#' @param opts Named list of GeoJSON-specific options. Usually created
+#' with \code{opts_read_geojson()}.
#' Default: empty \code{list()} to use the default options.
-#' @param ... any extra named options override those in \code{opts}
+#' @param ... Any extra named options override those in GeoJSON-specific options
+#' - \code{opts}
+#' @param json_opts Named list of vanilla JSON options as used by \code{read_json_str()}.
+#' This is usually created with \code{opts_read_json()}. Default value is
+#' an empty \code{list()} which means to use all the default JSON parsing
+#' options which is usually the correct thing to do when reading GeoJSON.
#'
#' @return \code{sf} object
#' @export
+#'
+#' @examples
+#' geojson_file <- system.file("geojson-example.json", package = 'yyjsonr')
+#' read_geojson_file(geojson_file)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-read_geojson_str <- function(str, opts = list(), ...) {
+read_geojson_str <- function(str, opts = list(), ..., json_opts = list()) {
opts <- modify_list(opts, list(...))
.Call(
parse_geojson_str_,
str,
opts, # geojson parse opts
- list(yyjson_read_flag = 0L) # general parse opts
+ json_opts
)
}
@@ -74,14 +94,14 @@ read_geojson_str <- function(str, opts = list(), ...) {
#' @rdname read_geojson_str
#' @export
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-read_geojson_file <- function(filename, opts = list(), ...) {
+read_geojson_file <- function(filename, opts = list(), ..., json_opts = list()) {
opts <- modify_list(opts, list(...))
.Call(
parse_geojson_file_,
- filename,
+ normalizePath(filename),
opts, # geojson parse opts
- list(yyjson_read_flag = 0L) # general parse opts
+ json_opts
)
}
@@ -93,22 +113,28 @@ read_geojson_file <- function(filename, opts = list(), ...) {
#' @param opts named list of options. Usually created with \code{opts_write_geojson()}.
#' Default: empty \code{list()} to use the default options.
#' @param ... any extra named options override those in \code{opts}
-#' @param digits decimal places to keep for floating point numbers. Default: -1.
-#' Positive values specify number of decimal places. Using zero will
-#' write the numeric value as an integer. Values less than zero mean that
-#' the floating point value should be written as-is (the default).
+#' @param json_opts Named list of vanilla JSON options as used by \code{write_json_str()}.
+#' This is usually created with \code{opts_write_json()}. Default value is
+#' an empty \code{list()} which means to use all the default JSON writing
+#' options which is usually the correct thing to do when writing GeoJSON.
#'
-#' @return character string containing json
+#' @return Character string containing GeoJSON, or \code{NULL} if GeoJSON
+#' written to file.
#' @export
+#'
+#' @examples
+#' geojson_file <- system.file("geojson-example.json", package = 'yyjsonr')
+#' sf <- read_geojson_file(geojson_file)
+#' cat(write_geojson_str(sf, json_opts = opts_write_json(pretty = TRUE)))
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-write_geojson_str <- function(x, opts = list(), ..., digits = -1) {
+write_geojson_str <- function(x, opts = list(), ..., json_opts = list()) {
opts <- modify_list(opts, list(...))
.Call(
serialize_sf_to_str_,
x,
- opts, # geojson serialize opts
- list(yyjson_write_flag = 0L, digits = digits) # general serialize opts
+ opts, # geojson serialize opts
+ json_opts # general serialize opts
)
}
@@ -116,15 +142,15 @@ write_geojson_str <- function(x, opts = list(), ..., digits = -1) {
#' @rdname write_geojson_str
#' @export
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-write_geojson_file <- function(x, filename, opts = list(), ..., digits = -1) {
+write_geojson_file <- function(x, filename, opts = list(), ..., json_opts = list()) {
opts <- modify_list(opts, list(...))
.Call(
serialize_sf_to_file_,
x,
- filename,
- opts, # geojson serialize opts
- list(yyjson_write_flag = 0L, digits = digits) # general serialize opts
+ normalizePath(filename, mustWork = FALSE),
+ opts, # geojson serialize opts
+ json_opts # general serialize opts
)
invisible()
diff --git a/README.Rmd b/README.Rmd
index 1cfe93c..baf3a28 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -58,10 +58,10 @@ It is a wrapper for the [`yyjson`](https://github.com/ibireme/yyjson) C library
### What's in the box
-This package contains specialised functions for each type of operation (read/write/validate) and the
+This package contains specialized functions for each type of operation (read/write/validate) and the
storage location of the JSON (string/file/raw vector/connection).
-The matrix of available operations and storage is shown below:
+#### Vanilla JSON
| | string | file | raw | conn | options |
|----------|---------------------|----------------------|-----------------|------------------|----------------------|
@@ -70,19 +70,40 @@ The matrix of available operations and storage is shown below:
| validate | validate_json_str() | validate_json_file() | | | |
+#### NDJSON
+
+| | string | file | raw | conn | options |
+|----------|---------------------|----------------------|-----------------|------------------|----------------------|
+| read | read_ndjson_str() | read_ndjson_file() | | | opts_read_json() |
+| write | write_ndjson_str() | write_ndjson_file() | | | opts_write_json() |
+
+
+#### GeoJSON
+
+| | string | file | raw | conn | options |
+|----------|---------------------|----------------------|-----------------|------------------|----------------------|
+| read | read_geojson_str() | read_geojson_file() | | | opts_read_geojson() |
+| write | write_geojson_str() | write_geojson_file() | | | opts_write_geojson() |
-### Comparison to other packages with read/write JSON
-| | Write JSON| Read JSON |
-|--------------|-----------|-----------|
-| yyjsonr | Fast! | Fast! |
-| jsonlite | Yes | Yes |
-| jsonify | Yes | Yes |
+### Speed
+In the following plots, bigger is better, with `yyjsonr` results in blue.
+
+
+#### JSON
-Note: Benchmarks were run on Apple M2 Mac. See file "man/benchmark/benchmark.Rmd" for details.
+#### NDJSON
+
+
+
+#### GeoJSON
+
+
+
+Note: Benchmarks were run on Apple M2 Mac. See files `man/benchmark/benchmark*.Rmd` for details.
## Installation
@@ -108,15 +129,6 @@ read_json_str(str)
```
-
-## Future
-
-* Re-introduce NDJSON support
- * NDJSON support was removed for the initial CRAN release for the sake of my sanity.
- * See the `ndjson` branch of this repository
-* Re-introduce GeoJSON support
- * GeoJSON support was removed for the initial CRAN release for the sake of my sanity.
- * See the `geojson` branch of this repository
## Limitations
diff --git a/README.md b/README.md
index da77e2c..881a3c6 100644
--- a/README.md
+++ b/README.md
@@ -23,11 +23,11 @@ this package for more details.
### What’s in the box
-This package contains specialised functions for each type of operation
+This package contains specialized functions for each type of operation
(read/write/validate) and the storage location of the JSON
(string/file/raw vector/connection).
-The matrix of available operations and storage is shown below:
+#### Vanilla JSON
| | string | file | raw | conn | options |
|----------|---------------------|----------------------|-----------------|------------------|-------------------|
@@ -35,18 +35,39 @@ The matrix of available operations and storage is shown below:
| write | write_json_str() | write_json_file() | | | opts_write_json() |
| validate | validate_json_str() | validate_json_file() | | | |
-### Comparison to other packages with read/write JSON
+#### NDJSON
-| | Write JSON | Read JSON |
-|----------|------------|-----------|
-| yyjsonr | Fast! | Fast! |
-| jsonlite | Yes | Yes |
-| jsonify | Yes | Yes |
+| | string | file | raw | conn | options |
+|-------|--------------------|---------------------|-----|------|-------------------|
+| read | read_ndjson_str() | read_ndjson_file() | | | opts_read_json() |
+| write | write_ndjson_str() | write_ndjson_file() | | | opts_write_json() |
+
+#### GeoJSON
+
+| | string | file | raw | conn | options |
+|-------|---------------------|----------------------|-----|------|----------------------|
+| read | read_geojson_str() | read_geojson_file() | | | opts_read_geojson() |
+| write | write_geojson_str() | write_geojson_file() | | | opts_write_geojson() |
+
+### Speed
+
+In the following plots, bigger is better, with `yyjsonr` results in
+blue.
+
+#### JSON
-Note: Benchmarks were run on Apple M2 Mac. See file
-“man/benchmark/benchmark.Rmd” for details.
+#### NDJSON
+
+
+
+#### GeoJSON
+
+
+
+Note: Benchmarks were run on Apple M2 Mac. See files
+`man/benchmark/benchmark*.Rmd` for details.
## Installation
@@ -96,17 +117,6 @@ read_json_str(str)
#> 3 4.7 3.2 1.3 0.2 setosa
```
-## Future
-
-- Re-introduce NDJSON support
- - NDJSON support was removed for the initial CRAN release for the sake
- of my sanity.
- - See the `ndjson` branch of this repository
-- Re-introduce GeoJSON support
- - GeoJSON support was removed for the initial CRAN release for the
- sake of my sanity.
- - See the `geojson` branch of this repository
-
## Limitations
- Some datatypes not currently supported. Please file an issue on GitHub
diff --git a/inst/geojson-example.json b/inst/geojson-example.json
new file mode 100644
index 0000000..04e817f
--- /dev/null
+++ b/inst/geojson-example.json
@@ -0,0 +1,35 @@
+{ "type": "FeatureCollection",
+ "features": [
+ { "type": "Feature",
+ "geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
+ "properties": {"prop0": "value0"}
+ },
+ { "type": "Feature",
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
+ ]
+ },
+ "properties": {
+ "prop0": "value0",
+ "prop1": 0.0
+ }
+ },
+ { "type": "Feature",
+ "geometry": {
+ "type": "Polygon",
+ "coordinates": [
+ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
+ [100.0, 1.0], [100.0, 0.0] ]
+ ]
+
+ },
+ "properties": {
+ "prop0": "value0",
+ "prop1": {"this": "that"}
+ }
+ }
+ ]
+ }
+
diff --git a/man/benchmark/benchmark-geoson.Rmd b/man/benchmark/benchmark-geoson.Rmd
new file mode 100644
index 0000000..343f179
--- /dev/null
+++ b/man/benchmark/benchmark-geoson.Rmd
@@ -0,0 +1,136 @@
+---
+title: "Benchmarks"
+output: rmarkdown::html_vignette
+vignette: >
+ %\VignetteIndexEntry{Benchmarks}
+ %\VignetteEngine{knitr::rmarkdown}
+ %\VignetteEncoding{UTF-8}
+---
+
+```{r, include = FALSE}
+knitr::opts_chunk$set(
+ collapse = TRUE,
+ comment = "#>",
+ fig.width = 6,
+ fig.height = 4
+)
+```
+
+```{r setup}
+library(yyjsonr)
+library(bench)
+library(ggplot2)
+library(tidyr)
+library(ggbeeswarm)
+library(geojsonsf)
+library(sf)
+
+library(ndjson)
+```
+
+
+Benchmark overview
+==============================================================================
+
+* Benchmarking was done an Apple M2 Silicon.
+* Test-cases were drawn from other packages and other examples seen in the wild
+
+
+
+From GeoJSON String
+----------------------------------------------------------------------------
+
+```{r}
+res13 <- bench::mark(
+ geojsonsf = geojsonsf::geojson_sf(geojsonsf::geo_melbourne),
+ yyjsonr = yyjsonr::read_geojson_str(geojsonsf::geo_melbourne),
+ check = FALSE
+)
+
+```
+
+```{r echo=FALSE}
+res13$benchmark <- 'from GeoJSON string'
+knitr::kable(res13[,1:5])
+plot(res13) + theme_bw() + theme(legend.position = 'none')
+```
+
+
+To GeoJSON String
+----------------------------------------------------------------------------
+
+```{r}
+sf_obj <- sf::st_read(system.file("shape/nc.shp", package="sf"), quiet = TRUE)
+
+res14 <- bench::mark(
+ geojsonsf = geojsonsf::sf_geojson(sf_obj),
+ yyjsonr = yyjsonr::write_geojson_str(sf_obj),
+ check = FALSE
+)
+
+```
+
+```{r echo=FALSE}
+res14$benchmark <- 'to GeoJSON string'
+knitr::kable(res14[,1:5])
+plot(res14) + theme_bw() + theme(legend.position = 'none')
+```
+
+
+
+
+
+Summary
+===============================================================================
+
+```{r echo = FALSE, fig.width = 8, fig.height = 6}
+library(dplyr)
+plot_df <- bind_rows(
+ res13, res14
+)
+
+plot_df$benchmark <- factor(
+ plot_df$benchmark,
+ levels = unique(plot_df$benchmark)
+)
+
+plot_df <- plot_df %>%
+ mutate(
+ package = as.character(expression),
+ iters = `itr/sec`,
+ speed = iters
+ ) %>%
+ select(benchmark, package, iters, speed)
+
+plot_df <- plot_df %>%
+ group_by(benchmark) %>%
+ mutate(
+ ref_speed = speed[which(package %in% c('jsonlite', 'geojsonsf'))],
+ speed = speed / ref_speed
+ ) %>%
+ ungroup()
+
+ggplot(plot_df) +
+ geom_col(aes(package, speed, fill = package),
+ position = position_dodge2(preserve = "single")) +
+ facet_wrap(~benchmark, scales = 'free_y', ncol = 3) +
+ theme_bw(15) +
+ theme(legend.position = 'none') +
+ scale_fill_manual(values = c(rep(grey(0.5), 1), 'dodgerblue3')) +
+ geom_hline(yintercept = 1, color = 'red', alpha = 0.5, linetype = 2) +
+ labs(
+ x = NULL,
+ y = "Factor speed increase\nover reference implementation",
+ title = "Speed-up compared to reference implementation",
+ subtitle = "Red line indicates reference implementation {geojsonsf}"
+ ) +
+ scale_y_continuous(breaks = scales::pretty_breaks())
+
+if (FALSE) {
+ ggsave("./man/figures/benchmark-geojson.png", width = 7, height = 5)
+ # saveRDS(plot_df, "man/benchmark/cache-df-types.rds")
+}
+```
+
+
+
diff --git a/man/benchmark/benchmarks.Rmd b/man/benchmark/benchmark-json.Rmd
similarity index 97%
rename from man/benchmark/benchmarks.Rmd
rename to man/benchmark/benchmark-json.Rmd
index 416285f..1024c7d 100644
--- a/man/benchmark/benchmarks.Rmd
+++ b/man/benchmark/benchmark-json.Rmd
@@ -280,8 +280,8 @@ plot_df <- plot_df %>%
ggplot(plot_df) +
geom_col(aes(package, speed, fill = package),
position = position_dodge2(preserve = "single")) +
- facet_wrap(~benchmark, scales = 'free_y', ncol = 3) +
- theme_bw() +
+ facet_wrap(~benchmark, scales = 'free_y', nrow = 2) +
+ theme_bw(15) +
theme(legend.position = 'none') +
scale_fill_manual(values = c(rep(grey(0.5), 2), 'dodgerblue3')) +
geom_hline(yintercept = 1, color = 'red', alpha = 0.5, linetype = 2) +
@@ -290,11 +290,12 @@ ggplot(plot_df) +
y = "Factor speed increase over reference implementation",
title = "Speed-up compared to reference implementation",
subtitle = "Red line indicates reference implementation {jsonlite}"
- )
+ ) +
+ scale_y_continuous(breaks = scales::pretty_breaks())
if (FALSE) {
- ggsave("./man/figures/benchmark-summary.png", width = 12, height = 10)
- saveRDS(plot_df, "man/benchmark/cache-df-types.rds")
+ ggsave("./man/figures/benchmark-summary.png", width = 12, height = 7)
+ # saveRDS(plot_df, "man/benchmark/cache-df-types.rds")
}
```
diff --git a/man/benchmark/benchmark-ndjson.Rmd b/man/benchmark/benchmark-ndjson.Rmd
new file mode 100644
index 0000000..88f6b40
--- /dev/null
+++ b/man/benchmark/benchmark-ndjson.Rmd
@@ -0,0 +1,193 @@
+---
+title: "Benchmarks"
+output: rmarkdown::html_vignette
+vignette: >
+ %\VignetteIndexEntry{Benchmarks}
+ %\VignetteEngine{knitr::rmarkdown}
+ %\VignetteEncoding{UTF-8}
+---
+
+```{r, include = FALSE}
+knitr::opts_chunk$set(
+ collapse = TRUE,
+ comment = "#>",
+ fig.width = 6,
+ fig.height = 4
+)
+```
+
+```{r setup}
+library(yyjsonr)
+library(bench)
+library(ggplot2)
+library(tidyr)
+library(ggbeeswarm)
+library(geojsonsf)
+library(sf)
+
+library(ndjson)
+```
+
+
+Benchmark overview
+==============================================================================
+
+* Benchmarking was done an Apple M2 Silicon.
+* Test-cases were drawn from other packages and other examples seen in the wild
+
+
+
+From NDJSON file
+-------------------------------------------------------------------------------
+
+
+```{r include = FALSE}
+ndjson_filename <- tempfile()
+df <- head( nycflights13::flights, 1000)
+jsonlite::stream_out(df, file(ndjson_filename), verbose = FALSE)
+
+res06 <- bench::mark(
+ ndjson = ndjson::stream_in(ndjson_filename),
+ jsonlite = jsonlite::stream_in(file(ndjson_filename), verbose = FALSE),
+ jsonify = jsonify::from_ndjson(ndjson_filename),
+ # arrow = arrow::read_json_arrow(ndjson_filename),
+ yyjsonr = yyjsonr::read_ndjson_file (ndjson_filename),
+ # relative = TRUE,
+ check = FALSE
+)
+```
+
+```{r echo=FALSE}
+res06$benchmark <- 'From NDJSON File'
+knitr::kable(res06[, 1:5])
+plot(res06) + theme_bw() + theme(legend.position = 'none')
+```
+
+
+```{r echo=FALSE, eval=FALSE}
+library(dplyr)
+plot_df <- res06 %>%
+ mutate(package = as.character(expression)) %>%
+ rename(speed = `itr/sec`)
+
+ggplot(plot_df) +
+ geom_col(aes(package, speed, fill = package),
+ position = position_dodge2(preserve = "single")) +
+ theme_bw(20) +
+ theme(legend.position = 'none') +
+ scale_fill_manual(values = c(rep(grey(0.5), 4), 'dodgerblue3')) +
+ geom_hline(yintercept = 1, color = 'red', alpha = 0.5, linetype = 2) +
+ labs(
+ x = NULL,
+ y = "Factor speed increase over {jsonlite}",
+ title = "Reading NDJSON. Speed-up compared to {jsonlite}",
+ subtitle = "Red line indicates reference implementation {jsonlite}"
+ )
+```
+
+
+
+
+To NDJSON File
+-------------------------------------------------------------------------------
+
+
+```{r include = FALSE}
+ndjson_filename <- tempfile()
+df <- head( nycflights13::flights, 1000)
+
+res07 <- bench::mark(
+ jsonlite = jsonlite::stream_out(df, file(ndjson_filename), verbose = FALSE),
+ yyjsonr = yyjsonr::write_ndjson_file(df, ndjson_filename),
+ check = FALSE
+)
+```
+
+
+```{r echo=FALSE}
+res07$benchmark <- 'To NDJSON File'
+knitr::kable(res07[, 1:5])
+plot(res07) + theme_bw() + theme(legend.position = 'none')
+```
+
+
+To NDJSON String
+-------------------------------------------------------------------------------
+
+
+```{r message=FALSE, include = FALSE}
+ndjson_filename <- tempfile()
+df <- head( nycflights13::flights, 1000)
+
+res08 <- bench::mark(
+ jsonify = jsonify::to_ndjson(df),
+ jsonlite = jsonlite::stream_out(df, con = textConnection(NULL, "w"), name = "greg", local = TRUE, verbose = FALSE),
+ yyjsonr = yyjsonr::write_ndjson_str(df),
+ check = FALSE
+)
+```
+
+```{r echo=FALSE}
+res08$benchmark <- 'To NDJSON String'
+knitr::kable(res08[, 1:5])
+plot(res08) + theme_bw() + theme(legend.position = 'none')
+```
+
+
+
+
+
+Summary
+===============================================================================
+
+```{r echo = FALSE, fig.width = 8, fig.height = 6}
+library(dplyr)
+plot_df <- bind_rows(
+ res06, res07, res08
+)
+
+plot_df$benchmark <- factor(
+ plot_df$benchmark,
+ levels = unique(plot_df$benchmark)
+)
+
+plot_df <- plot_df %>%
+ mutate(
+ package = as.character(expression),
+ iters = `itr/sec`,
+ speed = iters
+ ) %>%
+ select(benchmark, package, iters, speed)
+
+plot_df <- plot_df %>%
+ group_by(benchmark) %>%
+ mutate(
+ ref_speed = speed[which(package %in% c('jsonlite', 'geojsonsf'))],
+ speed = speed / ref_speed
+ ) %>%
+ ungroup()
+
+ggplot(plot_df) +
+ geom_col(aes(package, speed, fill = package),
+ position = position_dodge2(preserve = "single")) +
+ facet_wrap(~benchmark, scales = 'free_y', ncol = 3) +
+ theme_bw(15) +
+ theme(legend.position = 'none') +
+ scale_fill_manual(values = c(rep(grey(0.5), 3), 'dodgerblue3')) +
+ geom_hline(yintercept = 1, color = 'red', alpha = 0.5, linetype = 2) +
+ labs(
+ x = NULL,
+ y = "Factor speed increase\nover reference implementation",
+ title = "Speed-up compared to reference implementation",
+ subtitle = "Red line indicates reference implementation {jsonlite}"
+ ) +
+ scale_y_continuous(breaks = scales::pretty_breaks())
+
+if (FALSE) {
+ ggsave("./man/figures/benchmark-ndjson.png", width = 9, height = 5)
+ # saveRDS(plot_df, "man/benchmark/cache-df-types.rds")
+}
+```
+
+
+
diff --git a/man/figures/benchmark-geojson.png b/man/figures/benchmark-geojson.png
new file mode 100644
index 0000000..0a591ad
Binary files /dev/null and b/man/figures/benchmark-geojson.png differ
diff --git a/man/figures/benchmark-ndjson.png b/man/figures/benchmark-ndjson.png
new file mode 100644
index 0000000..e91cbbb
Binary files /dev/null and b/man/figures/benchmark-ndjson.png differ
diff --git a/man/figures/benchmark-summary.png b/man/figures/benchmark-summary.png
index 233d29a..12a2311 100644
Binary files a/man/figures/benchmark-summary.png and b/man/figures/benchmark-summary.png differ
diff --git a/man/opts_read_geojson.Rd b/man/opts_read_geojson.Rd
index 6fc476b..f93912f 100644
--- a/man/opts_read_geojson.Rd
+++ b/man/opts_read_geojson.Rd
@@ -7,7 +7,7 @@
opts_read_geojson(
type = c("sf", "sfc"),
property_promotion = c("string", "list"),
- property_promotion_lgl_as_int = c("integer", "string")
+ property_promotion_lgl = c("integer", "string")
)
}
\arguments{
@@ -17,15 +17,20 @@ opts_read_geojson(
properties differ across a FEATURECOLLECTION? E.g. if the property
exists both as a numeric and a string, should all values be promoted
to a 'string', or contained as different types in a 'list'.
-Default: 'string' will behave like 'geojsonsf'}
+Default: 'string' will behave like \code{geojsonsf} package.}
-\item{property_promotion_lgl_as_int}{when promoting properties into a string,
-should logical values become strings e.g. "TRUE" or integers
-e.g. "1". Default: "integer" in order to match \code{geojsonsf} packages}
+\item{property_promotion_lgl}{when \code{property_promotion = "string"}
+should logical values become words (i.e. \code{"TRUE"}/\code{"FALSE"})
+or integers (i.e. \code{"1"}/\code{"0"}).
+Default: "integer" in order to match \code{geojsonsf} package}
}
\value{
-named list
+Named list of options specific to reading GeoJSON
}
\description{
Options for reading in GeoJSON
}
+\examples{
+# Create a set of options to use when reading geojson
+opts_read_geojson()
+}
diff --git a/man/opts_write_geojson.Rd b/man/opts_write_geojson.Rd
index 4185595..e435790 100644
--- a/man/opts_write_geojson.Rd
+++ b/man/opts_write_geojson.Rd
@@ -7,8 +7,12 @@
opts_write_geojson()
}
\value{
-named list of options
+Named list of options specific to writing GeoJSON
}
\description{
Currently no options available.
}
+\examples{
+# Create a set of options to use when writing geojson
+opts_write_geojson()
+}
diff --git a/man/read_geojson_str.Rd b/man/read_geojson_str.Rd
index 748110b..a71146c 100644
--- a/man/read_geojson_str.Rd
+++ b/man/read_geojson_str.Rd
@@ -5,19 +5,26 @@
\alias{read_geojson_file}
\title{Load GeoJSON as \code{sf} object}
\usage{
-read_geojson_str(str, opts = list(), ...)
+read_geojson_str(str, opts = list(), ..., json_opts = list())
-read_geojson_file(filename, opts = list(), ...)
+read_geojson_file(filename, opts = list(), ..., json_opts = list())
}
\arguments{
-\item{str}{single character string containing GeoJSON}
+\item{str}{Single string containing GeoJSON}
-\item{opts}{named list of options. Usually created with \code{opts_read_geojson()}.
+\item{opts}{Named list of GeoJSON-specific options. Usually created
+with \code{opts_read_geojson()}.
Default: empty \code{list()} to use the default options.}
-\item{...}{any extra named options override those in \code{opts}}
+\item{...}{Any extra named options override those in GeoJSON-specific options
+- \code{opts}}
-\item{filename}{filename}
+\item{json_opts}{Named list of vanilla JSON options as used by \code{read_json_str()}.
+This is usually created with \code{opts_read_json()}. Default value is
+an empty \code{list()} which means to use all the default JSON parsing
+options which is usually the correct thing to do when reading GeoJSON.}
+
+\item{filename}{Filename}
}
\value{
\code{sf} object
@@ -25,3 +32,7 @@ Default: empty \code{list()} to use the default options.}
\description{
Load GeoJSON as \code{sf} object
}
+\examples{
+geojson_file <- system.file("geojson-example.json", package = 'yyjsonr')
+read_geojson_file(geojson_file)
+}
diff --git a/man/write_geojson_str.Rd b/man/write_geojson_str.Rd
index 4475c07..f956c7d 100644
--- a/man/write_geojson_str.Rd
+++ b/man/write_geojson_str.Rd
@@ -5,9 +5,9 @@
\alias{write_geojson_file}
\title{Write SF to GeoJSON string}
\usage{
-write_geojson_str(x, opts = list(), ..., digits = -1)
+write_geojson_str(x, opts = list(), ..., json_opts = list())
-write_geojson_file(x, filename, opts = list(), ..., digits = -1)
+write_geojson_file(x, filename, opts = list(), ..., json_opts = list())
}
\arguments{
\item{x}{\code{sf} object. Supports \code{sf} or \code{sfc}}
@@ -17,16 +17,22 @@ Default: empty \code{list()} to use the default options.}
\item{...}{any extra named options override those in \code{opts}}
-\item{digits}{decimal places to keep for floating point numbers. Default: -1.
-Positive values specify number of decimal places. Using zero will
-write the numeric value as an integer. Values less than zero mean that
-the floating point value should be written as-is (the default).}
+\item{json_opts}{Named list of vanilla JSON options as used by \code{write_json_str()}.
+This is usually created with \code{opts_write_json()}. Default value is
+an empty \code{list()} which means to use all the default JSON writing
+options which is usually the correct thing to do when writing GeoJSON.}
\item{filename}{filename}
}
\value{
-character string containing json
+Character string containing GeoJSON, or \code{NULL} if GeoJSON
+written to file.
}
\description{
Write SF to GeoJSON string
}
+\examples{
+geojson_file <- system.file("geojson-example.json", package = 'yyjsonr')
+sf <- read_geojson_file(geojson_file)
+cat(write_geojson_str(sf, json_opts = opts_write_json(pretty = TRUE)))
+}
diff --git a/src/geojson-parse.c b/src/geojson-parse.c
index 987107b..384cbdb 100644
--- a/src/geojson-parse.c
+++ b/src/geojson-parse.c
@@ -56,7 +56,7 @@ SEXP make_crs(void) {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef struct {
bool property_promotion;
- bool property_promotion_lgl_as_int; // When promoting properties to string, should lgl be '1' or 'TRUE"?
+ bool property_promotion_lgl; // When promoting properties to string, should lgl be '1' or 'TRUE"?
unsigned int type;
unsigned int yyjson_read_flag;
parse_options *parse_opt;
@@ -76,7 +76,7 @@ typedef struct {
geo_parse_options create_geo_parse_options(SEXP geo_opts_) {
geo_parse_options opt = {
.property_promotion = PROP_TYPE_STRING, // emulate 'geojsonsf' behaviour
- .property_promotion_lgl_as_int = PROP_LGL_AS_INT, // emulate 'geojsonsf' behaviour
+ .property_promotion_lgl = PROP_LGL_AS_INT, // emulate 'geojsonsf' behaviour
.type = SF_TYPE,
.yyjson_read_flag = 0,
.xmin = INFINITY,
@@ -109,14 +109,14 @@ geo_parse_options create_geo_parse_options(SEXP geo_opts_) {
if (strcmp(opt_name, "property_promotion") == 0) {
const char *val = CHAR(STRING_ELT(val_, 0));
opt.property_promotion = strcmp(val, "string") == 0 ? PROP_TYPE_STRING : PROP_TYPE_LIST;
- } else if (strcmp(opt_name, "property_promotion_lgl_as_int") == 0) {
+ } else if (strcmp(opt_name, "property_promotion_lgl") == 0) {
const char *val = CHAR(STRING_ELT(val_, 0));
- opt.property_promotion_lgl_as_int = strcmp(val, "string") == 0 ? PROP_LGL_AS_STR : PROP_LGL_AS_INT;
+ opt.property_promotion_lgl = strcmp(val, "string") == 0 ? PROP_LGL_AS_STR : PROP_LGL_AS_INT;
} else if (strcmp(opt_name, "type") == 0) {
const char *val = CHAR(STRING_ELT(val_, 0));
opt.type = strcmp(val, "sf") == 0 ? SF_TYPE : SFC_TYPE;
} else {
- warning("geo_opt: Unknown option ignored: '%s'\n", opt_name);
+ warning("opt_geojson_read(): Unknown option ignored: '%s'\n", opt_name);
}
}
@@ -622,7 +622,7 @@ SEXP prop_to_rchar(yyjson_val *prop_val, geo_parse_options *opt) {
break;
case YYJSON_TYPE_BOOL:
{
- if (opt->property_promotion_lgl_as_int == PROP_LGL_AS_INT) {
+ if (opt->property_promotion_lgl == PROP_LGL_AS_INT) {
int tmp = yyjson_get_bool(prop_val);
return mkChar(bool_int[tmp]);
} else {
diff --git a/src/geojson-serialize.c b/src/geojson-serialize.c
index 4d76448..af81e0f 100644
--- a/src/geojson-serialize.c
+++ b/src/geojson-serialize.c
@@ -19,7 +19,6 @@
// - current bounding box accumulations
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef struct {
- unsigned int yyjson_write_flag;
serialize_options *serialize_opt;
} geo_serialize_options;
@@ -28,7 +27,6 @@ typedef struct {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
geo_serialize_options create_geo_serialize_options(SEXP to_geo_opts_) {
geo_serialize_options opt = {
- .yyjson_write_flag = YYJSON_WRITE_NOFLAG,
.serialize_opt = NULL
};
@@ -47,24 +45,8 @@ geo_serialize_options create_geo_serialize_options(SEXP to_geo_opts_) {
for (unsigned int i = 0; i < length(to_geo_opts_); i++) {
const char *opt_name = CHAR(STRING_ELT(nms_, i));
- SEXP val_ = VECTOR_ELT(to_geo_opts_, i);
-
- // if (strcmp(opt_name, "property_promotion") == 0) {
- // const char *val = CHAR(STRING_ELT(val_, 0));
- // opt.property_promotion = strcmp(val, "string") == 0 ? PROP_TYPE_STRING : PROP_TYPE_LIST;
- // } else if (strcmp(opt_name, "property_promotion_lgl_as_int") == 0) {
- // const char *val = CHAR(STRING_ELT(val_, 0));
- // opt.property_promotion_lgl_as_int = strcmp(val, "string") == 0 ? PROP_LGL_AS_STR : PROP_LGL_AS_INT;
- // } else if (strcmp(opt_name, "type") == 0) {
- // const char *val = CHAR(STRING_ELT(val_, 0));
- // opt.type = strcmp(val, "sf") == 0 ? SF_TYPE : SFC_TYPE;
- if (strcmp(opt_name, "pretty") == 0) {
- if (asLogical(val_)) {
- opt.yyjson_write_flag |= YYJSON_WRITE_PRETTY_TWO_SPACES;
- }
- } else {
- warning("geo_opt: Unknown option ignored: '%s'\n", opt_name);
- }
+ // SEXP val_ = VECTOR_ELT(to_geo_opts_, i);
+ warning("opt_geojson_write(): Unknown option ignored: '%s'\n", opt_name);
}
return opt;
}
@@ -148,7 +130,7 @@ SEXP sfc_to_str(SEXP sfc_, geo_serialize_options *opt) {
yyjson_mut_doc_set_root(doc, val);
yyjson_write_err err;
- char *json = yyjson_mut_write_opts(doc, opt->yyjson_write_flag, NULL, NULL, &err);
+ char *json = yyjson_mut_write_opts(doc, opt->serialize_opt->yyjson_write_flag, NULL, NULL, &err);
if (json == NULL) {
yyjson_mut_doc_free(doc);
error("Write to string error: %s code: %u\n", err.msg, err.code);
@@ -251,7 +233,7 @@ SEXP sf_to_str(SEXP sf_, geo_serialize_options *opt) {
// Write the doc to a string
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
yyjson_write_err err;
- char *json = yyjson_mut_write_opts(doc, opt->yyjson_write_flag, NULL, NULL, &err);
+ char *json = yyjson_mut_write_opts(doc, opt->serialize_opt->yyjson_write_flag, NULL, NULL, &err);
if (json == NULL) {
yyjson_mut_doc_free(doc);
error("serialize_sf() Write to string error: %s code: %u\n", err.msg, err.code);
@@ -278,7 +260,7 @@ SEXP sf_to_file(SEXP sf_, SEXP filename_, geo_serialize_options *opt) {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const char *filename = CHAR(STRING_ELT(filename_, 0));
yyjson_write_err err;
- bool success = yyjson_mut_write_file(filename, doc, opt->yyjson_write_flag, NULL, &err);
+ bool success = yyjson_mut_write_file(filename, doc, opt->serialize_opt->yyjson_write_flag, NULL, &err);
if (!success) {
yyjson_mut_doc_free(doc);
error("Write to file error '%s': %s code: %u\n", filename, err.msg, err.code);
diff --git a/tests/testthat/geojson/standard-example.json.gz b/tests/testthat/geojson/standard-example.json.gz
new file mode 100644
index 0000000..ed6d041
Binary files /dev/null and b/tests/testthat/geojson/standard-example.json.gz differ
diff --git a/tests/testthat/test-basic.R b/tests/testthat/test-basic.R
index fa6be87..6c3b5a1 100644
--- a/tests/testthat/test-basic.R
+++ b/tests/testthat/test-basic.R
@@ -58,6 +58,11 @@ test_that("write_json_str works", {
# Vectors with specials
+
+ # Version string is sensible
+ version <- yyjson_version()
+ expect_length(version, 1)
+ expect_true(is.character(version))
})
diff --git a/tests/testthat/test-geojson-read-write.R b/tests/testthat/test-geojson-read-write.R
index c1a785e..98cfb94 100644
--- a/tests/testthat/test-geojson-read-write.R
+++ b/tests/testthat/test-geojson-read-write.R
@@ -97,6 +97,8 @@ test_that("Compare parsing of GeoJSON to {geojsonsf}", {
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test_that("Basic writing works for lots of different geojson", {
+ tmp <- tempfile()
+
for (i in seq_along(json)) {
# Take the JSON
@@ -113,8 +115,63 @@ test_that("Basic writing works for lots of different geojson", {
# Test 'sf' representations are equal
expect_identical(geojson1, geojson2, label = names(json)[i])
+
+ # write the json to file
+ write_geojson_file(geojson1, tmp)
+
+ geojson3 <- read_geojson_file(tmp)
+
+ expect_identical(geojson1, geojson3)
}
})
+test_that("opts", {
+
+ opts <- opts_read_geojson()
+ expect_true(is.list(opts))
+ expect_true(inherits(opts, "opts_read_geojson"))
+
+ opts <- opts_write_geojson()
+ expect_true(is.list(opts))
+ expect_true(inherits(opts, "opts_write_geojson"))
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ # geojson read options
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ geo <- read_geojson_file(
+ testthat::test_path("geojson/standard-example.json"),
+ opts = opts_read_geojson(property_promotion = 'string')
+ )
+ expect_true(is.character(geo$prop1))
+
+ geo <- read_geojson_file(
+ testthat::test_path("geojson/standard-example.json"),
+ opts = opts_read_geojson(property_promotion = 'list')
+ )
+ expect_true(is.list(geo$prop1))
+
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ # unknown options spark warning
+ #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ expect_warning(
+ write_geojson_str(geo, bad = TRUE),
+ "Unknown option ignored"
+ )
+
+ expect_warning(
+ write_geojson_str(geo, pretty = TRUE),
+ "Unknown option ignored"
+ )
+
+ if (FALSE) {
+ write_geojson_str(geo, json_opts = opts_write_json(pretty = TRUE)) |> cat()
+ }
+
+
+})
+
+
+
+