Search code examples
rgeojsonr-sf

Export nested simple feature collection to geoJSON in R


I have used R to calculate a Voronoi tessellation I now want to use in an OSM umap. Because I want different areas to be easily distinguishable, following some hint at their github, I need a nested field, in this case _umap_options$color

But when I try to export such a simple feature collection to an geoJSON file, I either loose the nested column or the export won't work at all. Exporting to JSON works fine, but I would prefer R to output a geoJSON file I could use directly.

Here is a MWE:

library(osmdata)
library(sf)
library(geojsonsf)

df1 <- data.frame(osm_id=c(1:3), name=c("foo","bar","test"),lon=c(7,8,9),lat=52)
sf1 <- st_as_sf(df1,coords=c("lon","lat"))
df2 <- data.frame(fillColor=c("red","green","blue"))
df1$"_umap_options" <- df2
sf2 <- st_as_sf(df1,coords=c("lon","lat"))

# a simple JSON has the nested information correctly
toJSON(sf2, pretty=TRUE)

# won't accept the object as input
sf_geojson(sf2)
# while the sf without nested columns goes through
sf_geojson(sf1)

# ignores the nested column
st_write(sf2,"/tmp/test.geojson")

st_write also issues a warning:

In clean_columns(as.data.frame(obj), factorsAsCharacter) : Dropping column(s) _umap_options of class(es) data.frame

Am I missing some neccessary modification in my data.frames to allow this to be possible?


Solution

  • With st_write() / GDAL it should be enough if you just store JSON strings in your frame.

    From GDAL GeoJSON driver doc:

    AUTODETECT_JSON_STRINGS=[YES/NO]: (GDAL >= 3.8) Defaults to YES. Whether to try to interpret string fields as JSON arrays or objects if they start and end with brackets and braces, even if they do not have their subtype set to JSON.

    library(sf)
    #> Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE
    library(dplyr)
    library(jsonlite)
    
    df1 <- data.frame(osm_id=c(1:3), name=c("foo","bar","test"),lon=c(7,8,9),lat=52)
    sf1 <- st_as_sf(df1,coords=c("lon","lat"), crs = "WGS84")
    df2 <- data.frame(fillColor=c("red","green","blue"))
    
    # use JSON string in _umap_options
    sf2 <- 
      bind_cols(sf1, df2) |> 
      rowwise() |> 
      mutate(
        `_umap_options` = toJSON(
          list(fillColor = fillColor), 
          auto_unbox = TRUE), 
        .keep = "unused") |> 
      ungroup()
    
    sf2
    #> Simple feature collection with 3 features and 3 fields
    #> Geometry type: POINT
    #> Dimension:     XY
    #> Bounding box:  xmin: 7 ymin: 52 xmax: 9 ymax: 52
    #> Geodetic CRS:  WGS 84
    #> # A tibble: 3 × 4
    #>   osm_id name     geometry `_umap_options`            
    #>    <int> <chr> <POINT [°]> <json>                     
    #> 1      1 foo        (7 52) "{\"fillColor\":\"red\"}"  
    #> 2      2 bar        (8 52) "{\"fillColor\":\"green\"}"
    #> 3      3 test       (9 52) "{\"fillColor\":\"blue\"}"
    
    st_write(sf2, "test.geojson")
    #> Writing layer `test' to data source `test.geojson' using driver `GeoJSON'
    #> Writing 3 features with 3 fields and geometry type Point.
    
    readr::read_file("test.geojson") |> prettify() 
    

    Resulting GeoJSON:

    {
        "type": "FeatureCollection",
        "name": "test",
        "crs": {
            "type": "name",
            "properties": {
                "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
            }
        },
        "features": [
            {
                "type": "Feature",
                "properties": {
                    "osm_id": 1,
                    "name": "foo",
                    "_umap_options": {
                        "fillColor": "red"
                    }
                },
                "geometry": {
                    "type": "Point",
                    "coordinates": [
                        7.0,
                        52.0
                    ]
                }
            },
            {
                "type": "Feature",
                "properties": {
                    "osm_id": 2,
                    "name": "bar",
                    "_umap_options": {
                        "fillColor": "green"
                    }
                },
                "geometry": {
                    "type": "Point",
                    "coordinates": [
                        8.0,
                        52.0
                    ]
                }
            },
            {
                "type": "Feature",
                "properties": {
                    "osm_id": 3,
                    "name": "test",
                    "_umap_options": {
                        "fillColor": "blue"
                    }
                },
                "geometry": {
                    "type": "Point",
                    "coordinates": [
                        9.0,
                        52.0
                    ]
                }
            }
        ]
    }
    

    Created on 2024-07-27 with reprex v2.1.0