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@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@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.biv...@nhh.no
https://orcid.org/0000-0003-2392-6140
https://scholar.google.no/citations?user=AWeghB0AAAAJ&hl=en

_______________________________________________
R-sig-Geo mailing list
R-sig-Geo@r-project.org
https://stat.ethz.ch/mailman/listinfo/r-sig-geo

Reply via email to