plotKML: Visualization of Spatial and Spatio-Temporal Objects in Google Earth
How to cite:
- Hengl, T., Roudier, P., Beaudette, D., & Pebesma, E. (2015). plotKML: Scientific visualization of spatio-temporal data. Journal of Statistical Software, 63(5), 1-25. https://www.jstatsoft.org/article/view/v063i05
Install development versions from github:
library(devtools)
install_github("envirometrix/plotKML")
# install_github("envirometrix/plotKML", ref = "stars")
We will now present new sf methods and compare them with current sp
approach. Start with POINTS
suppressPackageStartupMessages({
library(plotKML)
library(sp)
library(rgdal)
library(sf)
})
# Load data
data(eberg)
coordinates(eberg) <- ~ X + Y
proj4string(eberg) <- CRS("+init=epsg:31467")
#> Warning in showSRID(uprojargs, format = "PROJ", multiline = "NO"): Discarded
#> datum Deutsches_Hauptdreiecksnetz in CRS definition
## subset to 20 percent:
eberg <- eberg[runif(nrow(eberg)) < .1, ]
# sp methods
plotKML(eberg["CLYMHT_A"], open.kml = FALSE)
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Warning in proj4string(obj): CRS object has comment, which is lost in output
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs ...
#> Writing to KML...
#> Closing eberg__CLYMHT_A__.kml
#> Object written to: eberg__CLYMHT_A__.kml
plotKML(eberg["CLYMHT_A"], colour_scale = rep("#FFFF00", 2), points_names = "", open.kml = FALSE)
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Warning in proj4string(obj): CRS object has comment, which is lost in output
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs ...
#> Writing to KML...
#> Closing eberg__CLYMHT_A__.kml
#> Object written to: eberg__CLYMHT_A__.kml
# convert eberg to sf format
eberg_sf <- st_as_sf(eberg)
# and apply sf methods
plotKML(eberg_sf["CLYMHT_A"], open.kml = FALSE)
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_sf__CLYMHT_A__.kml
#> Object written to: eberg_sf__CLYMHT_A__.kml
plotKML(eberg_sf["CLYMHT_A"], points_names = "", colour_scale = SAGA_pal[[2]], open.kml = FALSE) #Change palette
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_sf__CLYMHT_A__.kml
#> Object written to: eberg_sf__CLYMHT_A__.kml
plotKML(eberg_sf["CLYMHT_A"], colour_scale = rep("#FFFF00", 2), points_names = "", open.kml = FALSE) # Degenerate palette
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_sf__CLYMHT_A__.kml
#> Object written to: eberg_sf__CLYMHT_A__.kml
Recent version of sf could show a warning message relative to the old
PROJ4string specified by get("ref_CRS", envir = plotKML.opts).
We can now compare the two implementations:
all.equal(readLines("eberg__CLYMHT_A__.kml"), readLines("eberg_sf__CLYMHT_A__.kml"))
#> Warning in readLines("eberg__CLYMHT_A__.kml"): incomplete final line found on
#> 'eberg__CLYMHT_A__.kml'
#> Warning in readLines("eberg_sf__CLYMHT_A__.kml"): incomplete final line found on
#> 'eberg_sf__CLYMHT_A__.kml'
#> [1] "2 string mismatches"
The two string mismatches are simply caused by the different names and classes:
id_mismatches <- readLines("eberg__CLYMHT_A__.kml") != readLines("eberg_sf__CLYMHT_A__.kml")
readLines("eberg__CLYMHT_A__.kml")[id_mismatches]
#> [1] " <name>eberg__CLYMHT_A__</name>"
#> [2] " <name>SpatialPointsDataFrame</name>"
readLines("eberg_sf__CLYMHT_A__.kml")[id_mismatches]
#> [1] " <name>eberg_sf__CLYMHT_A__</name>"
#> [2] " <name>sfdata.frame</name>"
We can also use kml_layer functions:
data(eberg_grid)
gridded(eberg_grid) <- ~ x + y
proj4string(eberg_grid) <- CRS("+init=epsg:31467")
#> Warning in showSRID(uprojargs, format = "PROJ", multiline = "NO"): Discarded
#> datum Deutsches_Hauptdreiecksnetz in CRS definition
eberg_grid_sf <- st_as_sf(eberg_grid)
# sfc objects
kml_open("eberg_grids.kml")
#> KML file opened for writing...
kml_layer(st_geometry(eberg_grid_sf))
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
kml_close("eberg_grids.kml")
#> Closing eberg_grids.kml
# sf objects
kml_open("eberg_grid_sf.kml")
#> KML file opened for writing...
kml_layer(eberg_grid_sf, colour = DEMSRT6, colour_scale = R_pal[["terrain_colors"]])
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
kml_layer(eberg_grid_sf, colour = TWISRT6, colour_scale = SAGA_pal[[1]])
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
kml_close("eberg_grid_sf.kml")
#> Closing eberg_grid_sf.kml
This is also a more complex example where we add a legend to the plot:
shape = "http://maps.google.com/mapfiles/kml/pal2/icon18.png"
kml_file = "kml_file_point.kml"
legend_file = "kml_legend.png"
kml_open(kml_file)
kml_layer(
eberg_sf["CLYMHT_A"],
colour = CLYMHT_A,
points_names = eberg_sf[["CLYMHT_A"]],
size = 1,
shape = shape,
alpha = 0.75,
colour_scale = SAGA_pal[[2]]
)
kml_legend.bar(eberg_sf$CLYMHT_A, legend.file = legend_file, legend.pal = SAGA_pal[[1]])
kml_screen(image.file = legend_file)
kml_close(kml_file)
kml_View(kml_file)
Then we present sf methods for MULTIPOINT objects. The idea is exactly
the same, but MULTIPOINT object are converted to POINT.
eberg_sf_MULTIPOINT <- eberg_sf %>%
dplyr::mutate(random_ID = sample(1:4, size = dplyr::n(), replace = TRUE)) %>%
dplyr::group_by(random_ID) %>%
dplyr::summarise()
#> `summarise()` ungrouping output (override with `.groups` argument)
plotKML(eberg_sf_MULTIPOINT["random_ID"], open.kml = FALSE)
#> Casting the input MULTIPOINT objct into POINT object.
#> Warning in st_cast.sf(obj, "POINT"): repeating attributes for all sub-geometries
#> for which they may not be constant
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_sf_MULTIPOINT__random_ID__.kml
#> Object written to: eberg_sf_MULTIPOINT__random_ID__.kml
Then we present LINESTRING methods, starting from the sp example:
# sp
data(eberg_contours)
plotKML(eberg_contours, open.kml = FALSE)
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs ...
#> Warning in spTransform(xSP, CRSobj, ...): NULL source CRS comment, falling back
#> to PROJ string
#> Warning in spTransform(xSP, CRSobj, ...): +init dropped in PROJ string
#> Writing to KML...
#> Closing eberg_contours.kml
#> Object written to: eberg_contours.kml
plotKML(eberg_contours, colour = Z, altitude = Z, open.kml = FALSE)
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs ...
#> Warning in spTransform(xSP, CRSobj, ...): NULL source CRS comment, falling back
#> to PROJ string
#> Warning in spTransform(xSP, CRSobj, ...): +init dropped in PROJ string
#> Writing to KML...
#> Closing eberg_contours.kml
#> Object written to: eberg_contours.kml
# sf
eberg_contours_sf <- st_as_sf(eberg_contours)
#> Warning in CPL_crs_from_input(x): GDAL Message 1: +init=epsg:XXXX syntax is
#> deprecated. It might return a CRS with a non-EPSG compliant axis order.
plotKML(eberg_contours_sf, open.kml = FALSE)
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_contours_sf.kml
#> Object written to: eberg_contours_sf.kml
plotKML(eberg_contours_sf, colour = Z, altitude = Z, open.kml = FALSE)
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_contours_sf.kml
#> Object written to: eberg_contours_sf.kml
Again, the kml files are identical but for super small differences (if they are present) due to rounding errors:
all.equal(readLines("eberg_contours.kml"), readLines("eberg_contours_sf.kml"))
#> [1] "2 string mismatches"
id_mismatches <- which(readLines("eberg_contours.kml") != readLines("eberg_contours_sf.kml"))
readLines("eberg_contours.kml")[id_mismatches[1:5]]
#> [1] " <name>eberg_contours</name>"
#> [2] " <name>SpatialLinesDataFrame</name>"
#> [3] NA
#> [4] NA
#> [5] NA
readLines("eberg_contours_sf.kml")[id_mismatches[1:5]]
#> [1] " <name>eberg_contours_sf</name>" " <name>sfdata.frame</name>"
#> [3] NA NA
#> [5] NA
The same ideas can be applied to MULTILINESTRING objects. The only
problem is that some of the features in eberg_contous_sf are not valid
according to the simple feature definition of LINESTRING. For example
eberg_contours@lines[[4]]
#> An object of class "Lines"
#> Slot "Lines":
#> [[1]]
#> An object of class "Line"
#> Slot "coords":
#> [,1] [,2]
#> [1,] 3579413 5714807
#>
#>
#>
#> Slot "ID":
#> [1] "3"
since it would represent a LINESTRING with only 1 point:
st_is_valid(st_geometry(eberg_contours_sf)[[4]], NA_on_exception = FALSE, reason = TRUE)
#> Error in CPL_geos_is_valid_reason(x): Evaluation error: IllegalArgumentException: point array must contain 0 or >1 elements.
So we need to exclude these elements and create a MULTILINESTRING object,
ID_valid <- vapply(
st_geometry(eberg_contours_sf),
function(x) isTRUE(st_is_valid(x)),
logical(1)
)
eberg_contous_sf_multi <- eberg_contours_sf %>%
dplyr::filter(ID_valid) %>%
dplyr::group_by(Z) %>%
dplyr::summarise()
#> `summarise()` ungrouping output (override with `.groups` argument)
plotKML(eberg_contous_sf_multi, colour = Z, altitude = Z, open.kml = FALSE)
#> Casting the input MULTILINESTRING objct into LINESTRING object.
#> Warning in st_cast.sf(obj, "LINESTRING"): repeating attributes for all sub-
#> geometries for which they may not be constant
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_contous_sf_multi.kml
#> Object written to: eberg_contous_sf_multi.kml
We can also use kml_layer functions:
# sfc LINESTRING object
kml_open("eberg_contours_sfc.kml")
#> KML file opened for writing...
kml_layer(st_geometry(eberg_contours_sf))
#> Casting the input MULTILINESTRING object into LINESTRING.
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
kml_close("eberg_contours_sfc.kml")
#> Closing eberg_contours_sfc.kml
Then we can work with POLYGON geometry, starting from sp example:
# sp
set.seed(1) # I set the seed to compare the kml files
data(eberg_zones)
plotKML(eberg_zones["ZONES"], open.kml = FALSE)
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs ...
#> Warning in spTransform(xSP, CRSobj, ...): NULL source CRS comment, falling back
#> to PROJ string
#> Warning in spTransform(xSP, CRSobj, ...): +init dropped in PROJ string
#> Writing to KML...
#> Closing eberg_zones__ZONES__.kml
#> Object written to: eberg_zones__ZONES__.kml
## add altitude:
zmin = 230
plotKML(eberg_zones["ZONES"], altitude = zmin + runif(length(eberg_zones)) * 500, open.kml = FALSE)
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs ...
#> Warning in spTransform(xSP, CRSobj, ...): NULL source CRS comment, falling back
#> to PROJ string
#> Warning in spTransform(xSP, CRSobj, ...): +init dropped in PROJ string
#> Writing to KML...
#> Closing eberg_zones__ZONES__.kml
#> Object written to: eberg_zones__ZONES__.kml
# sf objects with sfc_POLYGON geometry
eberg_zones_sf <- st_as_sf(eberg_zones)
set.seed(1)
plotKML(eberg_zones_sf["ZONES"], open.kml = FALSE)
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_zones_sf__ZONES__.kml
#> Object written to: eberg_zones_sf__ZONES__.kml
plotKML(eberg_zones_sf["ZONES"], altitude = zmin + runif(length(eberg_zones)) * 500, open.kml = FALSE)
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_zones_sf__ZONES__.kml
#> Object written to: eberg_zones_sf__ZONES__.kml
and compare the results:
all.equal(readLines("eberg_zones__ZONES__.kml"), readLines("eberg_zones_sf__ZONES__.kml"))
#> [1] "2 string mismatches"
The differences here (if they are present) are caused by extremely small rounding problems:
id_mismatches <- readLines("eberg_zones__ZONES__.kml") != readLines("eberg_zones_sf__ZONES__.kml")
readLines("eberg_zones__ZONES__.kml")[id_mismatches][1:5]
#> [1] " <name>eberg_zones__ZONES__</name>"
#> [2] " <name>SpatialPolygonsDataFrame</name>"
#> [3] NA
#> [4] NA
#> [5] NA
readLines("eberg_zones_sf__ZONES__.kml")[id_mismatches][42]
#> [1] NA
Again, the same ideas can be applied to MULTIPOLYGON objects:
eberg_zones_sf_MULTI <- eberg_zones_sf %>%
dplyr::group_by(ZONES) %>%
dplyr::summarise() %>%
st_cast("MULTIPOLYGON")
#> `summarise()` ungrouping output (override with `.groups` argument)
plotKML(eberg_zones_sf_MULTI["ZONES"], open.kml = FALSE)
#> Casting the input MULTIPOLYGON objct into POLYGON object.
#> Warning in st_cast.sf(obj, "POLYGON"): repeating attributes for all sub-
#> geometries for which they may not be constant
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs
#> Writing to KML...
#> Closing eberg_zones_sf_MULTI__ZONES__.kml
#> Object written to: eberg_zones_sf_MULTI__ZONES__.kml
We will now present plotKML methods applied to stars objects. We
extended the plotKML() function to stars objects representing Raster
Data Cubes creating a wrapper to RasterLayer method:
library(stars)
#> Loading required package: abind
# Start with a raster data cube
(g <- read_stars(system.file("external/test.grd", package = "raster")))
#> stars object with 2 dimensions and 1 attribute
#> attribute(s):
#> test.grd
#> Min. : 138.7
#> 1st Qu.: 294.0
#> Median : 371.9
#> Mean : 425.6
#> 3rd Qu.: 501.0
#> Max. :1736.1
#> NA's :6022
#> dimension(s):
#> from to offset delta refsys point values x/y
#> x 1 80 178400 40 +proj=sterea +lat_0=52.15... NA NULL [x]
#> y 1 115 334000 -40 +proj=sterea +lat_0=52.15... NA NULL [y]
plotKML(g, open.kml = FALSE)
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Warning in proj4string(obj): CRS object has comment, which is lost in output
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs ...
#> Writing to KML...
#> Closing obj.kml
#> Object written to: obj.kml
Default methods do not work if the third dimension does not represent a
Date or POSIXct object:
# Test with another example:
(L7 <- read_stars(system.file("tif/L7_ETMs.tif", package = "stars")))
#> stars object with 3 dimensions and 1 attribute
#> attribute(s):
#> L7_ETMs.tif
#> Min. : 1.00
#> 1st Qu.: 54.00
#> Median : 69.00
#> Mean : 68.91
#> 3rd Qu.: 86.00
#> Max. :255.00
#> dimension(s):
#> from to offset delta refsys point values x/y
#> x 1 349 288776 28.5 UTM Zone 25, Southern Hem... FALSE NULL [x]
#> y 1 352 9120761 -28.5 UTM Zone 25, Southern Hem... FALSE NULL [y]
#> band 1 6 NA NA NA NA NULL
plotKML(L7, open.kml = FALSE)
#> Warning in showSRID(uprojargs, format = "PROJ", multiline = "NO"): Discarded datum Unknown based on GRS80 ellipsoid in CRS definition,
#> but +towgs84= values preserved
#> Error in .local(obj, ...): Select only one Raster layer or provide a Time series
It fails. The problems is that there is no method definition for
plotKML() function for objects of class RasterBrick. The code behind
as(x, "Raster") is defined
here
and it says that if length(dim(x)) > 2 (i.e. there is an extra
dimension other than x and y), then it creates a RasterBrick
object. We can still plot the raster associated to one of the layers in
the RasterBrick with a little bit of extra work:
plotKML(raster::raster(as(L7, "Raster"), layer = 1), open.kml = FALSE)
#> Warning in showSRID(uprojargs, format = "PROJ", multiline = "NO"): Discarded datum Unknown based on GRS80 ellipsoid in CRS definition,
#> but +towgs84= values preserved
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Warning in proj4string(obj): CRS object has comment, which is lost in output
#> Reprojecting to +proj=longlat +datum=WGS84 +no_defs ...
#> Writing to KML...
#> Closing raster__raster(as(L7,__Raster_),_layer_=_1).kml
#> Object written to: raster__raster(as(L7,__Raster_),_layer_=_1).kml
We are working on defining an extension to stars object with a
temporal dimension as a wrapper around RasterBrickTimeSeries methods.
Let’s focus now on Vector Data cubes:
# Vector data cube
data(air, package = "spacetime")
d = st_dimensions(station = st_as_sfc(stations), time = dates)
(aq = st_as_stars(list(PM10 = air), dimensions = d))
#> stars object with 2 dimensions and 1 attribute
#> attribute(s):
#> PM10
#> Min. : 0.00
#> 1st Qu.: 9.92
#> Median : 14.79
#> Mean : 17.70
#> 3rd Qu.: 21.99
#> Max. :274.33
#> NA's :157659
#> dimension(s):
#> from to offset delta refsys point
#> station 1 70 NA NA +proj=longlat +datum=WGS84 TRUE
#> time 1 4383 1998-01-01 1 days Date FALSE
#> values
#> station POINT (9.585911 53.67057),...,POINT (9.446661 49.24068)
#> time NULL
(agg = aggregate(aq, "months", mean, na.rm = TRUE))
#> stars object with 2 dimensions and 1 attribute
#> attribute(s):
#> PM10
#> Min. : 2.99
#> 1st Qu.:13.01
#> Median :16.43
#> Mean :17.68
#> 3rd Qu.:20.83
#> Max. :68.55
#> NA's :4926
#> dimension(s):
#> from to offset delta refsys point
#> time 1 144 NA NA Date NA
#> station 1 70 NA NA +proj=longlat +datum=WGS84 TRUE
#> values
#> time 1998-01-01,...,2009-12-01
#> station POINT (9.585911 53.67057),...,POINT (9.446661 49.24068)
plotKML(agg, open.kml = FALSE)
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Warning in proj4string(obj): CRS object has comment, which is lost in output
#> Warning in proj4string(obj): CRS object has comment, which is lost in output
#> Writing to KML...
#> Closing obj.kml
#> Object written to: obj.kml
Unfortunately there is a known bug in plotKML/spacetime and the
following example fails:
# Spatial aggregation:
(a = aggregate(agg, st_as_sf(DE_NUTS1), mean, na.rm = TRUE))
#> although coordinates are longitude/latitude, st_intersects assumes that they are planar
#> although coordinates are longitude/latitude, st_intersects assumes that they are planar
#> stars object with 2 dimensions and 1 attribute
#> attribute(s):
#> PM10
#> Min. : 6.037
#> 1st Qu.:14.072
#> Median :16.704
#> Mean :17.897
#> 3rd Qu.:20.378
#> Max. :59.218
#> NA's :832
#> dimension(s):
#> from to offset delta refsys point
#> geometry 1 16 NA NA +proj=longlat +datum=WGS84 FALSE
#> time 1 144 NA NA Date NA
#> values
#> geometry MULTIPOLYGON (((9.65046 49....,...,MULTIPOLYGON (((10.77189 51...
#> time 1998-01-01,...,2009-12-01
plotKML(a) # error
#> Error in from@sp[from@index[, 1], ]: SpatialPolygons selection: can't find plot order if polygons are replicated
# The error is in as(obj, "STIDF"), i.e. the conversion between STFDF and STIDF
Moreover, all the examples fail if the third dimension is not a temporal object (since the methods we defined as wrappers around STFDF class):
nc = read_sf(system.file("gpkg/nc.gpkg", package="sf"))
nc.df = st_set_geometry(nc, NULL)
mat = as.matrix(nc.df[c("BIR74", "SID74", "NWBIR74", "BIR79", "SID79", "NWBIR79")])
dim(mat) = c(county = 100, var = 3, year = 2) # make it a 3-dimensional array
dimnames(mat) = list(county = nc$NAME, var = c("BIR", "SID", "NWBIR"), year = c(1974, 1979))
(nc.st = st_as_stars(pop = mat))
#> stars object with 3 dimensions and 1 attribute
#> attribute(s):
#> pop
#> Min. : 0
#> 1st Qu.: 8
#> Median : 538
#> Mean : 1657
#> 3rd Qu.: 1784
#> Max. :30757
#> dimension(s):
#> from to offset delta refsys point values
#> county 1 100 NA NA NA NA Ashe,...,Brunswick
#> var 1 3 NA NA NA NA BIR , SID , NWBIR
#> year 1 2 NA NA NA NA 1974, 1979
(nc.geom <- st_set_dimensions(nc.st, 1, st_geometry(nc)))
#> stars object with 3 dimensions and 1 attribute
#> attribute(s):
#> pop
#> Min. : 0
#> 1st Qu.: 8
#> Median : 538
#> Mean : 1657
#> 3rd Qu.: 1784
#> Max. :30757
#> dimension(s):
#> from to offset delta refsys point
#> sfc 1 100 NA NA NAD27 FALSE
#> var 1 3 NA NA NA NA
#> year 1 2 NA NA NA NA
#> values
#> sfc MULTIPOLYGON (((-81.47276 3...,...,MULTIPOLYGON (((-78.65572 3...
#> var BIR , SID , NWBIR
#> year 1974, 1979
plotKML(nc.geom)
#> Error in SpatialPolygonsDataFrame(x, y, match.ID = match.ID, ...): Object length mismatch:
#> x has 100 Polygons objects, but y has 200 rows
We can somehow fix this problem converting the stars object into sf
format:
# We can use the following approach
plotKML(st_as_sf(nc.geom), open.kml = FALSE)
#> Casting the input MULTIPOLYGON objct into POLYGON object.
#> Warning in st_cast.sf(obj, "POLYGON"): repeating attributes for all sub-
#> geometries for which they may not be constant
#> Plotting the first variable on the list
#> KML file opened for writing...
#> Writing to KML...
#> Closing st_as_sf(nc.geom).kml
#> Object written to: st_as_sf(nc.geom).kml
In some cases, it is still possible to redefine a temporal dimension (but unfortunately the example fails for the same bug as the previous case):
(nc.geom <- st_set_dimensions(nc.geom, 3, as.Date(c("1974-01-01","1979-01-01"))))
#> stars object with 3 dimensions and 1 attribute
#> attribute(s):
#> pop
#> Min. : 0
#> 1st Qu.: 8
#> Median : 538
#> Mean : 1657
#> 3rd Qu.: 1784
#> Max. :30757
#> dimension(s):
#> from to offset delta refsys point
#> sfc 1 100 NA NA NAD27 FALSE
#> var 1 3 NA NA NA NA
#> year 1 2 1974-01-01 1826 days Date NA
#> values
#> sfc MULTIPOLYGON (((-81.47276 3...,...,MULTIPOLYGON (((-78.65572 3...
#> var BIR , SID , NWBIR
#> year NULL
(nc.geom <- split(aperm(nc.geom, c(1,3,2))))
#> stars object with 2 dimensions and 3 attributes
#> attribute(s):
#> BIR SID NWBIR
#> Min. : 248 Min. : 0.000 Min. : 1
#> 1st Qu.: 1177 1st Qu.: 2.000 1st Qu.: 206
#> Median : 2265 Median : 5.000 Median : 742
#> Mean : 3762 Mean : 7.515 Mean : 1202
#> 3rd Qu.: 4451 3rd Qu.: 9.000 3rd Qu.: 1316
#> Max. :30757 Max. :57.000 Max. :11631
#> dimension(s):
#> from to offset delta refsys point
#> sfc 1 100 NA NA NAD27 FALSE
#> year 1 2 1974-01-01 1826 days Date NA
#> values
#> sfc MULTIPOLYGON (((-81.47276 3...,...,MULTIPOLYGON (((-78.65572 3...
#> year NULL
plotKML(nc.geom)
#> Error in from@sp[from@index[, 1], ]: SpatialPolygons selection: can't find plot order if polygons are replicated