Spatialwidget

This package is designed to convert R data to JSON, ready for plotting on a map in an htmlwidget.

R Interface

Here I describe the R functions available to you. However, these are deliberately limited in their capability, as this library is not intended to be used directly at the R-level. Instead, it’s designed to be integrated into packages at the C++ level, where you will call the C++ functions directly.


There are 4 R functions you can call for creating POINTs, LINEs, POLYGONs or origin-destination shapes. Each of these functions returns a list with two elements, data and legend.

  • data : the R data.frame or sf object converted to pseudo-GeoJSON
  • legend : a summary of the values and colours suitable for a legend on the map

Pseudo-GeoJSON

The data is returned as pseudo-GeoJSON. Some plotting libraries can use more than one geometry, such as mapdeck::add_arc(), which uses an origin and destination. So spatialwidget needs to handle multiple geometries.

Typical GeoJSON will take the form

Whereas I’ve nested the geometries one-level deeper, so the pseudo-GeoJSON i’m using takes the form

Where the myGeometry object is defined on a per-application bases. You are free to call this whatever you want inside your library, and have as many as you want.

C++ API

The spatialwidget::api:: namespace has 5 functions for converting your data into pseudo-geojson. Here are their definitions, the input data they expect and the type of output they produce.

C++ arguments

This set of arguments are commong to all the C++ functions

Rcpp::DataFrame data

This will either be a data.frame with lon & lat columns, or an sf object.

Rcpp::List params

A named list. The names are the arguments of the calling R function which will be supplied to the javascript widget. These are typically columns of data, or a single value that will be applied to all rows of data.

For example, an R function will look like

And the list passed to c++ will be

In this case, the another_argument is not passed to the javascript widget as part of the data, so we don’t include it in our list.

The javascript function inside a htmlwidget will then access the stroke_colour and fill_colour properties from the data.

This example code is taken from the javascript binding of mapdeck::add_polygon() to show you how I use it.

std::unordered_map< std::string, std::string > layer_colours

A c++ unorderd_map specifying colours and their associated opacity.

These values will match the colour parameters used in the params list

But you don’t have to supply the opacity, it will be set to ‘opaque’ by default.

Rcpp::StringVector layer_legend

A vector of the colour values you want to use in a lenged.

In this example, both fill_colour and stroke_colour will be returned in the legend data.

int data_rows

The number of rows of data.

Rcpp::StringVector parameter_exclusions

A vector describing the elements of params which will be excluded from the final JSON data.

bool jsonify_legend

A logical value indicating if you want the legend data returned as JSON (TRUE) or a a list (FALSE)

Function-dependent arguments


geometry_columns

Either an Rcpp::List or Rcpp::StringVector.

The List is used for data.frames with lon & lat columns.

df <- data.frame(lon = 0, lat = 0)
geometry_column <- list( geometry = c("lon","lat") )

The StringVector is used for sf objects to specify the geometry columns.

sf <- sf::st_sf( origin = sf::st_sfc( sf::st_point(c(0,0 ) ) ) )
geometry_column <- c( "origin" )

bool elevation

The elevation argument is used when the data.frame has a column of elevation data. When using the elevation you also need to supply this column in the geometry_column list.

geometry_column <- list( geometry = c("lon","lat","elevation") )

Example

Here’s an example implementation of the R, cpp and hpp files required to convert R data to pseudo-GeoJSON

widgetpoint.R

widgetpoint.cpp

/layers/widgetpoint.hpp

Atomising geojson

As well as creating pseudo-GeoJSON, most of the functions also atomise the data.

When converting an sf object to GeoJSON it will typically create a FeatureCollection. ‘Atomising’ means it treats each row of the sf as it’s own Feature, and stores each one in a separate JSON object inside a JSON array (i.e., without combining them into a Feature Collection).

For example, we can create a GeoJSON FeatureCollection, convert it to sf and back again

and going back the other way completes the round-trip and creates a FeatureCollection.

If we set it to ‘atomise’ when converting to geojson, an array of Features is returned

This structure is useful for sending to an htmlwidget because each object in the array can be parsed independently, without having to worry about iterating or parsing the entire Featurecollection.

Therefore, most of the GeoJSON functions inside spatialwidget will return the ‘atomised’ form.

GeoJSON C++ API

You can by-pass the spatialwidget::api:: namepsace and call the spatialwidget::geojson:: api directly. However, doing so will only convert your data to pseudo-geojson, it won’t create colours or legends.

Here are the function definitions, the input data they expect and the type of output they produce.


two-sfc-column sf to downcasted, atomised pseudo-geojson

  inline Rcpp::StringVector to_geojson_downcast_atomise(
      Rcpp::DataFrame& sf,
      Rcpp::StringVector geometries )
    {
feat1 <- '{"type":"Feature","properties":{"id":1},"geometry":{"type":"MultiPoint","coordinates":[[0,0],[2,2],[4,4]]}}'
feat2 <- '{"type":"Feature","properties":{"id":2},"geometry":{"type":"MultiPoint","coordinates":[[1,1],[3,3],[5,5]]}}'
sf1 <- geojsonsf::geojson_sf( feat1 )
sf2 <- geojsonsf::geojson_sf( feat2 )

sf <- cbind( sf1, sf2)

geojson <- spatialwidget:::rcpp_sf_to_geojson_multi_column_downcast( sf = sf, geometries = c("geometry","geometry.1"))
geojson
#  [{"type":"Feature","properties":{"id":1.0,"id.1":2.0},"geometry":{"geometry":{"type":"Point","coordinates":[0.0,0.0]},"geometry.1":{"type":"Point","coordinates":[1.0,1.0]}}},{"type":"Feature","properties":{"id":1.0,"id.1":2.0},"geometry":{"geometry":{"type":"Point","coordinates":[2.0,2.0]},"geometry.1":{"type":"Point","coordinates":[1.0,1.0]}}},{"type":"Feature","properties":{"id":1.0,"id.1":2.0},"geometry":{"geometry":{"type":"Point","coordinates":[4.0,4.0]},"geometry.1":{"type":"Point","coordinates":[1.0,1.0]}}},{"type":"Feature","properties":{"id":1.0,"id.1":2.0},"geometry":{"geometry":{"type":"Point","coordinates":[0.0,0.0]},"geometry.1":{"type":"Point","coordinates":[3.0,3.0]}}},{"type":"Feature","properties":{"id":1.0,"id.1":2.0},"geometry":{"geometry":{"type":"Point","coordinates":[2.0,2.0]},"geometry.1":{"type":"Point","coordinates":[3.0,3.0]}}},{"type":"Feature","properties":{"id":1.0,"id.1":2.0},"geometry":{"geometry":{"type":"Point","coordinates":[4.0,4.0]},"geometry.1":{"type":"Point","coordinates":[3.0,3.0]}}},{"type":"Feature","properties":{"id":1.0,"id.1":2.0},"geometry":{"geometry":{"type":"Point","coordinates":[0.0,0.0]},"geometry.1":{"type":"Point","coordinates":[5.0,5.0]}}},{"type":"Feature","properties":{"id":1.0,"id.1":2.0},"geometry":{"geometry":{"type":"Point","coordinates":[2.0,2.0]},"geometry.1":{"type":"Point","coordinates":[5.0,5.0]}}},{"type":"Feature","properties":{"id":1.0,"id.1":2.0},"geometry":{"geometry":{"type":"Point","coordinates":[4.0,4.0]},"geometry.1":{"type":"Point","coordinates":[5.0,5.0]}}}]

in - sf object with two sfc columns

out - pseudo-geojson down-cast to simpler form. It will repeat the other columns to the correct length.


data.frame with lon & lat columsn to atomised pseudo-geojson

  inline Rcpp::StringVector to_geojson_atomise(
      Rcpp::DataFrame& df,
      Rcpp::List& geometries ) // i.e., list(origin = c("start_lon", "start_lat", destination = c("end_lon", "end_lat")))
  {

in - data.frame with lon & lat columns

out - pseudo-GeoJSON atomised


data.frame with lon, lat and elevation columns to atomised pseudo-geojson

  // list of geometries is designed for lon & lat columns of data
  inline Rcpp::StringVector to_geojson_z_atomise(
      Rcpp::DataFrame& df,
      Rcpp::List& geometries ) // i.e., list(origin = c("start_lon", "start_lat", destination = c("end_lon", "end_lat")))
  {

in - data.frame with lon, lat and elevation columns

out - pseudo-GeoJSON atomised