spatialwidget.Rmd
This package is designed to convert R data to JSON, ready for plotting on a map in an htmlwidget
.
The basic idea of this package is to take an sf
object or data.frame
head( widget_capitals )
# Simple feature collection with 6 features and 2 fields
# geometry type: POINT
# dimension: XY
# bbox: xmin: -170.43 ymin: -14.16 xmax: 69.11 ymax: 42.31
# epsg (SRID): NA
# proj4string: NA
# country capital geometry
# 1 Afghanistan Kabul POINT (69.11 34.28)
# 2 Albania Tirane POINT (19.49 41.18)
# 3 Algeria Algiers POINT (3.08 36.42)
# 4 American Samoa Pago Pago POINT (-170.43 -14.16)
# 5 Andorra Andorra la Vella POINT (1.32 42.31)
# 6 Angola Luanda POINT (13.15 -8.5)
And convert it into pseudo-geojson ready to be parsed by javascript inside an htmlwidget
js <- spatialwidget::widget_point( data = widget_capitals, fill_colour = "country", legend = TRUE)
substr( js$data, 1, 200 )
# [1] "[{\"type\":\"Feature\",\"properties\":{\"fill_colour\":\"#440154FF\"},\"geometry\":{\"geometry\":{\"type\":\"Point\",\"coordinates\":[69.11,34.28]}}},{\"type\":\"Feature\",\"properties\":{\"fill_colour\":\"#450356FF\"},\"geometry\":"
# attr(,"class")
# [1] "json"
substr( js$legend, 1, 100 )
# [1] "{\"fill_colour\":{\"colour\":[\"#440154FF\",\"#450356FF\",\"#450458FF\",\"#45065AFF\",\"#46085CFF\",\"#460A5EFF\",\"#"
# attr(,"class")
# [1] "json"
Notice the fill_colour
column is now a hex colour, and the geometry
column has been converted into Point
coordinates.
This is basically it. The R object is now represented as JSON, having had a column of data changed into hex colours.
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.frame
or sf
object converted to pseudo-GeoJSONThe 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
[{"type":"Feature", "properties":{},"geometry":{"myGeometry":{"type":"Point","coordinates":[0,0]}}}]
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.
l <- widget_point( widget_capitals[1:2, ], fill_colour = "country", legend = T )
substr( l$data, 1, 200 )
# [1] "[{\"type\":\"Feature\",\"properties\":{\"fill_colour\":\"#440154FF\"},\"geometry\":{\"geometry\":{\"type\":\"Point\",\"coordinates\":[69.11,34.28]}}},{\"type\":\"Feature\",\"properties\":{\"fill_colour\":\"#FDE725FF\"},\"geometry\":"
# attr(,"class")
# [1] "json"
l <- widget_line( widget_roads[1:2, ], stroke_colour = "ROAD_NAME", legend = T )
substr( l$data, 1, 200 )
# [1] "[{\"type\":\"Feature\",\"properties\":{\"stroke_colour\":\"#440154FF\",\"stroke_width\":1.0},\"geometry\":{\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[145.014291,-37.830458],[145.014345,-37.830574],[145.01449,-"
# attr(,"class")
# [1] "json"
l <- widget_polygon( widget_melbourne[1:2, ], fill_colour = "AREASQKM16", legend = F)
substr( l$data, 1, 200 )
# [1] "[{\"type\":\"Feature\",\"properties\":{\"stroke_colour\":\"#440154FF\",\"stroke_width\":1.0,\"fill_colour\":\"#440154FF\"},\"geometry\":{\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[144.9925232,-37.8024902],[144.99264"
# attr(,"class")
# [1] "json"
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.
sfc
-column sf
to pseudo-geojson/*
* sf object with one or many sfc columns
*
* expects `data` to be an sf object, where the geometry_columns is a string vector
* containing the sfc colunm names (of sf) you want to use as the geometry objects
* inside the GeoJSON
*/
inline Rcpp::List create_geojson(
Rcpp::DataFrame& data,
Rcpp::List& params,
Rcpp::List& lst_defaults,
std::unordered_map< std::string, std::string >& layer_colours,
Rcpp::StringVector& layer_legend,
int& data_rows,
Rcpp::StringVector& parameter_exclusions,
Rcpp::StringVector& geometry_columns,
bool jsonify_legend
)
in - sf
object with one or many sfc
columns
out - geometries left as-is, returned in pseudo-geojson
sfc
-column sf
to standard geojson/*
* expects `data` to be an sf object, where the geometry_column is a string vector
* of the sfc column names (of sf) you want to use as the geometry object inside the GeoJSON.
*
*/
inline Rcpp::List create_geojson(
Rcpp::DataFrame& data,
Rcpp::List& params,
Rcpp::List& lst_defaults,
std::unordered_map< std::string, std::string >& layer_colours,
Rcpp::StringVector& layer_legend,
int& data_rows,
Rcpp::StringVector& parameter_exclusions,
std::string& geometry_column, // single geometry column from sf object
bool jsonify_legend
)
in - sf
object with one sfc
column
out - returns standard geojson
sfc
-column sf
to down-casted geometries in pseudo-geojson/*
* expects `data` to be an sf object, where the geometry_column is a string
* of the sfc column names (of sf) you want to use as the geometry object inside the GeoJSON.
* This function will down-cast MULTI* objects to their single form
*
*/
inline Rcpp::List create_geojson_downcast(
Rcpp::DataFrame& data,
Rcpp::List& params,
Rcpp::List& lst_defaults,
std::unordered_map< std::string, std::string >& layer_colours,
Rcpp::StringVector& layer_legend,
int& data_rows,
Rcpp::StringVector& parameter_exclusions,
std::string& geometry_column,
bool jsonify_legend
)
in - sf
object with one sfc
column
out - MULTI* geometries down-cast to simpler form, in pseudo-geojson
sfc
-column sf
to down-casted psuedo-geojson /*
* expects `data` to be an sf object, where the geometry_column is a string vector
* of the sfc column name (of sf) you want to use as the geometry object inside the GeoJSON.
* This function will down-cast MULTI* objects to their single form
*
*/
inline Rcpp::List create_geojson_downcast(
Rcpp::DataFrame& data,
Rcpp::List& params,
Rcpp::List& lst_defaults,
std::unordered_map< std::string, std::string >& layer_colours,
Rcpp::StringVector& layer_legend,
int& data_rows,
Rcpp::StringVector& parameter_exclusions,
Rcpp::StringVector& geometry_column,
bool jsonify_legend
)
in - sf
object with two sfc
columns
out - MULTI* geometries down-cast to simpler form, in pseudo-geojson
data.frame
with lon & lat columns to pseudo-geojson/*
* expects `data` to be data.frame withn lon & lat columns. The geometry_columns
* argument is a named list, list(myGeometry = c("lon","lat")), where 'myGeometry'
* will be returned inside the 'geometry' object of the GeoJSON
*/
inline Rcpp::List create_geojson(
Rcpp::DataFrame& data,
Rcpp::List& params,
Rcpp::List& lst_defaults,
std::unordered_map< std::string, std::string >& layer_colours,
Rcpp::StringVector& layer_legend,
int& data_rows,
Rcpp::StringVector& parameter_exclusions,
Rcpp::List& geometry_columns,
bool jsonify_legend
)
in - data.frame
with lon & lat columns (each row is a POINT)
out - pseudo-geojson
data.frame
with lon, lat & elevation columns to pseudo-geojson/*
* expects `data` to be data.frame withn lon & lat & elev columns. The 'bool elevation'
* argument must be set to 'true', and the 'geometry_columns' should contain an 'elevation'
* value - 'geometry_column <- list( geometry = c("lon","lat","elevation") )'
*/
inline Rcpp::List create_geojson(
Rcpp::DataFrame& data,
Rcpp::List& params,
Rcpp::List& lst_defaults,
std::unordered_map< std::string, std::string >& layer_colours,
Rcpp::StringVector& layer_legend,
int& data_rows,
Rcpp::StringVector& parameter_exclusions,
Rcpp::List& geometry_columns,
bool jsonify_legend,
bool elevation
)
in - data.frame
with lon, lat and elevation columns (each row is a POINT)
out - pseudo-gejson
This set of arguments are commong to all the C++ functions
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
l <- list()
l[["fill_colour"]] <- force( fill_colour )
l[["stroke_colour"]] <- force( stroke_colour )
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.
Either a named list, or an empty list.
You can use this list to supply default values to the widget.
A c++ unorderd_map
specifying colours and their associated opacity.
std::unordered_map< std::string, std::string > polygon_colours = {
{ "fill_colour", "fill_opacity" },
{ "stroke_colour", "stroke_opacity"}
};
These values will match the colour parameters used in the params
list
l <- list()
l[["fill_colour"]] <- force( fill_colour )
l[["stroke_colour"]] <- force( stroke_colour )
But you don’t have to supply the opacity, it will be set to ‘opaque’ by default.
A vector of the colour values you want to use in a lenged.
const Rcpp::StringVector polygon_legend = Rcpp::StringVector::create(
"fill_colour", "stroke_colour"
);
In this example, both fill_colour
and stroke_colour
will be returned in the legend data.
Either an Rcpp::List
or Rcpp::StringVector
.
The List
is used for data.frame
s with lon & lat columns.
The StringVector
is used for sf
objects to specify the geometry columns.
Here’s an example implementation of the R, cpp and hpp files required to convert R data to pseudo-GeoJSON
widgetpoint.R
#' Widget Point
#'
#' Converts an `sf` object with POINT geometriers into JSON for plotting in an htmlwidget
#'
#' @param data `sf` object with POINT geometries
#' @param fill_colour string specifying column of `sf` to use for the fill colour
#' @param legend logical indicating if legend data will be returned
#' @param json_legend logical indicating if the lgend will be returned as JSON or a list
#'
#' @examples
#'
#' l <- widget_point( data = capitals, fill_colour = "country", legend = FALSE )
#'
#' @export
widget_point <- function( data,
fill_colour,
legend = TRUE,
json_legend = TRUE ) {
l <- list()
l[["fill_colour"]] <- force( fill_colour )
l[["legend"]] <- legend
js_data <- rcpp_widget_point( data, l, c("geometry"), json_legend )
return( js_data )
}
widgetpoint.cpp
#include <Rcpp.h>
#include "spatialwidget/spatialwidget.hpp"
#include "spatialwidget/spatialwidget_defaults.hpp"
#include "spatialwidget/layers/widgetpoint.hpp"
// [[Rcpp::export]]
Rcpp::List rcpp_widget_point(
Rcpp::DataFrame data,
Rcpp::List params,
Rcpp::StringVector geometry_columns,
bool jsonify_legend ) {
int data_rows = data.nrows();
Rcpp::List defaults = point_defaults( data_rows );
std::unordered_map< std::string, std::string > point_colours = spatialwidget::widgetpoint::point_colours;
Rcpp::StringVector point_legend = spatialwidget::widgetpoint::point_legend;
Rcpp::StringVector parameter_exclusions = Rcpp::StringVector::create("legend","legend_options","palette","na_colour");
return spatialwidget::api::create_geojson(
data,
params,
defaults,
point_colours,
point_legend,
data_rows,
parameter_exclusions,
geometry_columns,
jsonify_legend
);
}
/layers/widgetpoint.hpp
#ifndef SPATIALWIDGET_WIDGETPOINT_H
#define SPATIALWIDGET_WIDGETPOINT_H
#include <Rcpp.h>
namespace spatialwidget {
namespace widgetpoint {
// map between colour and opacity values
std::unordered_map< std::string, std::string > point_colours = {
{ "fill_colour", "fill_opacity" }
};
// vector of possible legend components
Rcpp::StringVector point_legend = Rcpp::StringVector::create(
"fill_colour"
);
} // namespace widgetpoint
} // namespace spatialwidget
#endif
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
feat1 <- '{"type":"Feature","properties":{"id":1},"geometry":{"type":"Point","coordinates":[0,0]}}'
feat2 <- '{"type":"Feature","properties":{"id":2},"geometry":{"type":"Point","coordinates":[1,1]}}'
geojson <- paste0('[{"type":"FeatureCollection","features":[',feat1,',',feat2,']}]')
sf <- geojsonsf::geojson_sf( geojson )
sf
# Simple feature collection with 2 features and 1 field
# geometry type: POINT
# dimension: XY
# bbox: xmin: 0 ymin: 0 xmax: 1 ymax: 1
# epsg (SRID): 4326
# proj4string: +proj=longlat +datum=WGS84 +no_defs
# id geometry
# 1 1 POINT (0 0)
# 2 2 POINT (1 1)
and going back the other way completes the round-trip and creates a FeatureCollection.
geo <- geojsonsf::sf_geojson( sf )
geo
# {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":1.0},"geometry":{"type":"Point","coordinates":[0.0,0.0]}},{"type":"Feature","properties":{"id":2.0},"geometry":{"type":"Point","coordinates":[1.0,1.0]}}]}
If we set it to ‘atomise’ when converting to geojson, an array of Features
is returned
geojsonsf::sf_geojson( sf, atomise = TRUE )
# {"type":"Feature","properties":{"id":1.0},"geometry":{"type":"Point","coordinates":[0.0,0.0]}}
# {"type":"Feature","properties":{"id":2.0},"geometry":{"type":"Point","coordinates":[1.0,1.0]}}
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.
Downcasting is the process of converting MULTI objects to their simpler form. For example, a MULTIPOINT will be down-cast to POINTs (one row/value for each of the multipoints).
js <- '{"type":"Feature","properties":{"id":1,"val":"a"},"geometry":{"type":"MultiPoint","coordinates":[[1,4],[2,5],[3,6]]}}'
sf <- geojsonsf::geojson_sf( js )
geo_down <- spatialwidget:::rcpp_sf_to_geojson_downcast( sf, "geometry" )
jsonify::pretty_json( geo_down )
# [
# {
# "type": "Feature",
# "properties": {
# "id": 1.0,
# "val": "a"
# },
# "geometry": {
# "geometry": {
# "type": "Point",
# "coordinates": [
# 1.0,
# 4.0
# ]
# }
# }
# },
# {
# "type": "Feature",
# "properties": {
# "id": 1.0,
# "val": "a"
# },
# "geometry": {
# "geometry": {
# "type": "Point",
# "coordinates": [
# 2.0,
# 5.0
# ]
# }
# }
# },
# {
# "type": "Feature",
# "properties": {
# "id": 1.0,
# "val": "a"
# },
# "geometry": {
# "geometry": {
# "type": "Point",
# "coordinates": [
# 3.0,
# 6.0
# ]
# }
# }
# }
# ]
See here we started with a single MULTIPOINT (containing three coordinates), and we are returned three POINTs. Also notice the id
and val
fields are repeated across the new POINTs.
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.
sfc
-column sf
to atomised pseudo-geojson /*
* a variation on the atomise function to return an array of atomised features
*/
inline Rcpp::StringVector to_geojson_atomise(
Rcpp::DataFrame& sf,
Rcpp::StringVector& geometries ) {
geojson <- spatialwidget:::rcpp_geojson_sf(sf = widget_arcs, geometries = c("origin","destination"))
substr( geojson, 1, 500)
# [{"type":"Feature","properties":{"country_from":"Australia","capital_from":"Canberra","country_to":"Afghanistan","capital_to":"Kabul"},"geometry":{"origin":{"type":"Point","coordinates":[149.08,-35.15]},"destination":{"type":"Point","coordinates":[69.11,34.28]}}},{"type":"Feature","properties":{"country_from":"Australia","capital_from":"Canberra","country_to":"Albania","capital_to":"Tirane"},"geometry":{"origin":{"type":"Point","coordinates":[149.08,-35.15]},"destination":{"type":"Point","coordi
in - sf
object with one or more sfc
columns
out - atomised pseudo-geojson
sfc
-column sf
to downcasted, atomised pseudo-geojson // down-casts MULTIGEOMETRIES to their simpler geometry
// only for one-column sfc objects
inline Rcpp::StringVector to_geojson_downcast_atomise(
Rcpp::DataFrame& sf,
std::string geometry ) {
geojson <- spatialwidget:::rcpp_sf_to_geojson_downcast( sf = widget_capitals, geometry_column = "geometry" )
substr( geojson, 1, 500)
# [{"type":"Feature","properties":{"country":"Afghanistan","capital":"Kabul"},"geometry":{"geometry":{"type":"Point","coordinates":[69.11,34.28]}}},{"type":"Feature","properties":{"country":"Albania","capital":"Tirane"},"geometry":{"geometry":{"type":"Point","coordinates":[19.49,41.18]}}},{"type":"Feature","properties":{"country":"Algeria","capital":"Algiers"},"geometry":{"geometry":{"type":"Point","coordinates":[3.08,36.42]}}},{"type":"Feature","properties":{"country":"American Samoa","capital":"
in - sf
object with one sfc
column
out - pseudo-geojson downcast to simpler form
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.
sfc
-column sf
to standard geojsoninline Rcpp::StringVector to_geojson( Rcpp::DataFrame& sf, std::string geom_column )
geojson <- spatialwidget:::rcpp_geojson( sf = widget_capitals, geometry = "geometry")
substr( geojson, 1, 300)
# {"type":"FeatureCollection","features":[{"type":"Feature","properties":{"country":"Afghanistan","capital":"Kabul"},"geometry":{"type":"Point","coordinates":[69.11,34.28]}},{"type":"Feature","properties":{"country":"Albania","capital":"Tirane"},"geometry":{"type":"Point","coordinates":[19.49,41.18]}}
in - sf
object with one sfc
column
out - standard GeoJSON
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")))
{
df <- as.data.frame( widget_capitals )
coords <- sf::st_coordinates( widget_capitals )
df[, c("lon","lat")] <- coords
df$geometry <- NULL
geojson <- spatialwidget:::rcpp_geojson_df(df = df, list(geometry = c("lon","lat")) )
substr( geojson, 1, 500 )
# [{"type":"Feature","properties":{"country":"Afghanistan","capital":"Kabul"},"geometry":{"geometry":{"type":"Point","coordinates":[69.11,34.28]}}},{"type":"Feature","properties":{"country":"Albania","capital":"Tirane"},"geometry":{"geometry":{"type":"Point","coordinates":[19.49,41.18]}}},{"type":"Feature","properties":{"country":"Algeria","capital":"Algiers"},"geometry":{"geometry":{"type":"Point","coordinates":[3.08,36.42]}}},{"type":"Feature","properties":{"country":"American Samoa","capital":"
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")))
{
df$z <- sample(1:500, size = nrow(df), replace = TRUE )
geojson <- spatialwidget:::rcpp_geojson_dfz( df, geometries = list(geometry = c("lon","lat","z") ) )
substr( geojson, 1, 500 )
# [{"type":"Feature","properties":{"country":"Afghanistan","capital":"Kabul"},"geometry":{"geometry":{"type":"Point","coordinates":[69.11,34.28,220.0]}}},{"type":"Feature","properties":{"country":"Albania","capital":"Tirane"},"geometry":{"geometry":{"type":"Point","coordinates":[19.49,41.18,205.0]}}},{"type":"Feature","properties":{"country":"Algeria","capital":"Algiers"},"geometry":{"geometry":{"type":"Point","coordinates":[3.08,36.42,361.0]}}},{"type":"Feature","properties":{"country":"American
in - data.frame
with lon, lat and elevation columns
out - pseudo-GeoJSON atomised