Skip to content

Commit

Permalink
GeoJSON support refactored
Browse files Browse the repository at this point in the history
  • Loading branch information
coolbutuseless committed Mar 17, 2024
1 parent 88700bc commit 2e37a31
Show file tree
Hide file tree
Showing 19 changed files with 609 additions and 126 deletions.
92 changes: 59 additions & 33 deletions R/geojson.R
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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(
Expand All @@ -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
)
}

Expand All @@ -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
)
}

Expand All @@ -93,38 +113,44 @@ 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
)
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' @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()
Expand Down
48 changes: 30 additions & 18 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|----------|---------------------|----------------------|-----------------|------------------|----------------------|
Expand All @@ -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

<img src="man/figures/benchmark-summary.png">

Note: Benchmarks were run on Apple M2 Mac. See file "man/benchmark/benchmark.Rmd" for details.
#### NDJSON

<img src="man/figures/benchmark-ndjson.png" width="75%">

#### GeoJSON

<img src="man/figures/benchmark-geojson.png" width="50%">

Note: Benchmarks were run on Apple M2 Mac. See files `man/benchmark/benchmark*.Rmd` for details.

## Installation

Expand All @@ -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

Expand Down
52 changes: 31 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,51 @@ 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 |
|----------|---------------------|----------------------|-----------------|------------------|-------------------|
| read | read_json_str() | read_json_file() | read_json_raw() | read_json_conn() | opts_read_json() |
| 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

<img src="man/figures/benchmark-summary.png">

Note: Benchmarks were run on Apple M2 Mac. See file
“man/benchmark/benchmark.Rmd” for details.
#### NDJSON

<img src="man/figures/benchmark-ndjson.png" width="75%">

#### GeoJSON

<img src="man/figures/benchmark-geojson.png" width="50%">

Note: Benchmarks were run on Apple M2 Mac. See files
`man/benchmark/benchmark*.Rmd` for details.

## Installation

Expand Down Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions inst/geojson-example.json
Original file line number Diff line number Diff line change
@@ -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"}
}
}
]
}

Loading

0 comments on commit 2e37a31

Please sign in to comment.