[R-sig-Geo] rgdal::writeOGR with driver='ESRI Shapefile' converts Polygon object into a hole

Roger Bivand Roger@B|v@nd @end|ng |rom nhh@no
Fri Aug 16 21:27:11 CEST 2019


On Fri, 16 Aug 2019, Roger Bivand wrote:

> On Tue, 13 Aug 2019, Phil Haines wrote:
>
>>  Dear list,
>>
>>  I have a single Polygons object containing multiple Polygon objects
>>  that share a common border. When I output this using writeOGR() one of
>>  the Polygon objects becomes a hole, as the following example shows.
>>
>>  Create a Polygons object containing two adjoining Polygon objects
>>>  library(rgdal)
>>>  r1 <- rbind(c(1,1),c(1,2),c(2,2),c(2,1),c(1,1))
>>>  r2 <- r1; r2[,1] <- r2[,1]+1
>>>  Ps1 = Polygons(list(Polygon(r1),Polygon(r2)),ID=1)
>>>  SPDF = SpatialPolygonsDataFrame( SpatialPolygons(list(Ps1)),
>>>  data.frame(Example=c("Minimal")))
>>
>>  Perform a write/readOGR() cycle
>>>  fn <- tempfile()
>>>  writeOGR(SPDF, fn, layer='test', driver='ESRI Shapefile')
>>>  SPDF2 <- readOGR(dsn=fn,layer='test')
>>
>>  Second Polygon object is now a hole
>>>  sapply(SPDF2 using polygons[[1]]@Polygons,slot,"hole")
>>  [1] FALSE  TRUE
>>
>>  I see from the sp documentation that "Polygon objects belonging to a
>>  Polygons object should either not overlap one-other, or should be
>>  fully included" but I am not sure how this relates to bordering
>>  Polygon objects. I would welcome any advice as to whether what I am
>>  asking of writeOGR is reasonable?
>
> The problem is with the 'ESRI Shapefile' representation and driver:
>
> library(rgdal)
> r1 <- rbind(c(1,1),c(1,2),c(2,2),c(2,1),c(1,1))
> r2 <- r1; r2[,1] <- r2[,1]+1
> Ps1 = Polygons(list(Polygon(r1),Polygon(r2)),ID=1)
> SPDF = SpatialPolygonsDataFrame( SpatialPolygons(list(Ps1)),
> data.frame(Example=c("Minimal")))
> sapply(slot(slot(SPDF, "polygons")[[1]], "Polygons"), slot, "hole")
> sapply(slot(slot(SPDF, "polygons")[[1]], "Polygons"), slot, "ringDir")
>
> # which constructs a MULTIPOLYGON object:
>
> rgeos::writeWKT(SPDF)
> library(sf)
> st_as_text(st_geometry(st_as_sf(SPDF)))
>
> #  The 'ESRI Shapefile' driver is not Simple-Feature compliant (it pre-dates 
> #  it), so the failure occurs by seeing the second exterior ring
> #  as an interior ring
>
> fn <- tempfile()
> writeOGR(SPDF, fn, layer='test', driver='ESRI Shapefile')
> SPDF2 <- readOGR(dsn=fn, layer='test')
> sapply(slot(slot(SPDF2, "polygons")[[1]], "Polygons"), slot, "hole")
> sapply(slot(slot(SPDF2, "polygons")[[1]], "Polygons"), slot, "ringDir")
> rgeos::writeWKT(SPDF2)
> st_as_text(st_geometry(st_as_sf(SPDF2)))
>
> # This happens with sf too, using the same GDAL driver:
>
> sf2 <- st_read(dsn=fn, layer='test')
> st_geometry(sf2)
> library(sf)
> st_as_text(st_geometry(st_as_sf(sf2)))
> rgeos::writeWKT(as(sf2, "Spatial"))
>
> # Adding the comment fix doesn't help:
>
> comment(slot(SPDF, "polygons")[[1]])
> SPDF_c <- rgeos::createSPComment(SPDF)
> comment(slot(SPDF_c, "polygons")[[1]])
> writeOGR(SPDF_c, fn, layer='test_c', driver='ESRI Shapefile',
>   verbose=TRUE)
>
> #  reports
> #  Object initially classed as: wkbPolygon
> #  SFS comments in Polygons objects
> #  Object reclassed as: wkbMultiPolygon
>
> SPDF2_c <- readOGR(dsn=fn, layer='test_c')
> sapply(slot(slot(SPDF2_c, "polygons")[[1]], "Polygons"), slot, "hole")
> sapply(slot(slot(SPDF2_c, "polygons")[[1]], "Polygons"), slot, "ringDir")
> rgeos::writeWKT(SPDF2_c)
> st_as_text(st_geometry(st_as_sf(SPDF2_c)))
>
>
>
> # If the input object is written out with the GeoPackage driver:
>
> fn1 <- tempfile(fileext=".gpkg")
> writeOGR(SPDF, fn1, layer="test", driver='GPKG')
> sf2a <- st_read(dsn=fn1,layer='test')
> st_coordinates(st_geometry(sf2a))
> SPDF2a <- readOGR(dsn=fn1)
> sapply(slot(slot(SPDF2a, "polygons")[[1]], "Polygons"), slot, "hole")
> sapply(slot(slot(SPDF2a, "polygons")[[1]], "Polygons"), slot, "ringDir")
> rgeos::writeWKT(SPDF2a)
> st_as_text(st_geometry(st_as_sf(SPDF2a)))
>
> # the issue is resolved. If we separate the exterior rings:
>
> r2a <- r1; r2a[,1] <- r2a[,1]+1.00001
> Ps1a = Polygons(list(Polygon(r1),Polygon(r2a)),ID=1)
> SPDFa = SpatialPolygonsDataFrame( SpatialPolygons(list(Ps1a)),
>  data.frame(Example=c("Minimal")))
> fna <- tempfile()
> writeOGR(SPDFa, fna, layer='test', driver='ESRI Shapefile')
> SPDF2_a <- readOGR(dsn=fna, layer='test')
> sapply(slot(slot(SPDF2_a, "polygons")[[1]], "Polygons"), slot, "hole")
> sapply(slot(slot(SPDF2_a, "polygons")[[1]], "Polygons"), slot, "ringDir")
> rgeos::writeWKT(SPDF2_a)
> st_as_text(st_geometry(st_as_sf(SPDF2_a)))
>
> # we are OK as the two exterior rings do not touch.
>
> # does using sf make a difference?
>
> fn_s <- tempfile(fileext=".shp")
> st_write(st_as_sf(SPDF), dsn=fn_s)
> sf_in <- st_read(fn_s)
> st_as_text(st_geometry(st_as_sf(sf_in)))
>
> # No
>
> fn_s <- tempfile(fileext=".shp")
> st_write(st_as_sf(SPDF_c), dsn=fn_s)
> sf_in_c <- st_read(fn_s)
> st_as_text(st_geometry(st_as_sf(sf_in_c)))
>
> # nor with the pretend-SF-compliant comment set either.
>
> So the weakness is in the "ESRI Shapefile" write driver, or possibly in the 
> OGRGeometryFactory::organizePolygons() function in GDAL used in OGR_write() 
> (a C++ function) called by writeOGR(). If sf::st_write() also calls 
> OGRGeometryFactory::organizePolygons(), we'd maybe consider that it has a 
> weakness for the "ESRI Shapefile" driver, but which does not affect 
> SF-compliant drivers.

Without the comment set, OGRGeometryFactory::organizePolygons() is used; 
with it set, OGRGeometryFactory::organizePolygons() is not used, because 
the object is declared as two exterior rings. In both cases, we have the 
output object written out and read back in incorrectly with the ESRI 
shapefile driver, but SF-compliant drivers round-trip (in the test 
GeoJSON), correctly.

It is likely that the changes made in 2015 to accommodate GeoJSON led to 
this possible regression for the ESRI Shapefile driver. I'm adding this 
geometry to tests/tripup.R and data files; without code changes the hole 
slot is wrong and the ring direction is changes to match, so the 
coordinates change too.

Reading using the deprecated maptools::readShapeSpatial() also gets a hole 
in the second external ring. However, writing with deprecated 
maptools::writeSpatialShape() yields a shapefile that when read with 
maptools::readShapeSpatial() gives the correct two exterior ring, no hole 
object. When read with sf::st_read() and rgdal::readOGR(), the object is 
also correct. So the problem definitely lies in rgdal::writeOGR(), and 
sf::st_write() - roundtripping with sf::st_write() and sf::st_read() 
degrades from MULTIPOLYGON to POLYGON with the ESRI Shapefile driver.

Roger

>
> This is probably related to a similar but inverse problem with the 
> SF-compliant GeoJSON driver in 2015:
>
> https://stat.ethz.ch/pipermail/r-sig-geo/2015-October/023609.html
>
> continued the next month in:
>
> https://stat.ethz.ch/pipermail/r-sig-geo/2015-November/023656.html
>
> The details are in this SVN diff
>
> https://r-forge.r-project.org/scm/viewvc.php/pkg/src/OGR_write.cpp?root=rgdal&r1=555&r2=571
>
> up to the end og the list thread, and this one from then until now:
>
> https://r-forge.r-project.org/scm/viewvc.php/pkg/src/OGR_write.cpp?root=rgdal&r1=571&r2=733
>
> Summary: could you change drivers, or is it really necessary to fix an EOL 
> problem? What is your use case?
>
>>
>>  Thanks in advance for your time,
>
> Thanks for a complete example,
>
> Roger
>
>>  Phil
>>
>>>  sessionInfo()
>>  R version 3.5.1 (2018-07-02)
>>  Platform: x86_64-w64-mingw32/x64 (64-bit)
>>  Running under: Windows >= 8 x64 (build 9200)
>>
>>  Matrix products: default
>>
>>  locale:
>>  [1] LC_COLLATE=English_United Kingdom.1252  LC_CTYPE=English_United
>>  Kingdom.1252    LC_MONETARY=English_United Kingdom.1252 LC_NUMERIC=C
>>                          LC_TIME=English_United Kingdom.1252
>>
>>  attached base packages:
>>  [1] stats     graphics  grDevices utils     datasets  methods   base
>>
>>  other attached packages:
>>  [1] rgdal_1.4-4 sp_1.3-1
>>
>>  loaded via a namespace (and not attached):
>>  [1] compiler_3.5.1  tools_3.5.1     yaml_2.2.0      grid_3.5.1
>>  lattice_0.20-35
>>
>>  _______________________________________________
>>  R-sig-Geo mailing list
>>  R-sig-Geo using r-project.org
>>  https://stat.ethz.ch/mailman/listinfo/r-sig-geo
>> 
>
>

-- 
Roger Bivand
Department of Economics, Norwegian School of Economics,
Helleveien 30, N-5045 Bergen, Norway.
voice: +47 55 95 93 55; e-mail: Roger.Bivand using nhh.no
https://orcid.org/0000-0003-2392-6140
https://scholar.google.no/citations?user=AWeghB0AAAAJ&hl=en



More information about the R-sig-Geo mailing list