[R-sig-Geo] thinning a SpatialPolygonsDataFrame

Michael Friendly friendly at yorku.ca
Thu Nov 19 14:55:10 CET 2009


Thanks very much for doing the heavy lifting here, Roger.

For use in the Guerry package, I've turned this into a function (rather 
than saving other versions of gfrance).

If you include something like this in sp, I'll withdraw it from Guerry.

thinnedSpatialPoly <- function(SP, tolerance, minarea) {
  if (!require(shapefiles)) stop("shapefiles package is required")
  stopifnot(inherits(SP, "SpatialPolygons"))

    # TODO: determine and set defaults for tolerance, minarea 
    # TODO: suppress warnings: "In Polygon(crds_s) : Non-finite label 
point detected and replaced"
  pls <- slot(SP, "polygons")
  pls_dp <- vector(mode="list", length=length(pls))
  for (i in 1:length(pls)) {
    Pls <- slot(pls[[i]], "Polygons")
    Pls_dp <- vector(mode="list", length=length(Pls))
    for (j in 1:length(Pls)) {
      crds <- slot(Pls[[j]], "coords")
      crds_s <- dp(list(x=crds[,1], y=crds[,2]), tolerance=tolerance)
      crds_s <- do.call("cbind", crds_s)
      if(!identical(crds_s[1,], crds_s[nrow(crds_s),]))
        crds_s <- rbind(crds_s, crds_s[1,])
      Pls_dp[[j]] <- Polygon(crds_s)
    }
    Keep <- logical(length(Pls_dp))
    for (j in 1:length(Pls_dp)) {
      Keep[j] <- TRUE
      if (slot(Pls_dp[[j]], "area") < minarea) Keep[j] <- FALSE
    }
    Pls_dp <- Pls_dp[Keep]
    pls_dp[[i]] <- Polygons(Pls_dp, ID=slot(pls[[i]], "ID"))
  }
    SP_dp <- SpatialPolygons(pls_dp, proj4string=slot(SP, "proj4string"))
  if(inherits(SP, "SpatialPolygonsDataFrame")) {
    data <- slot(SP, "data")
    SP_dp <- SpatialPolygonsDataFrame(SP_dp, data=data)
  }
  SP_dp
}

best,
-Michael


Roger Bivand wrote:
> On Thu, 19 Nov 2009, Pinaud David wrote:
>
>> Hi Michael,
>> Roger is fully right, this function does not preserve the topology, 
>> so be aware of that, some problems can occur...
>> If you want to use shapefiles::dp() just for raw plotting and visual 
>> simplification, you can try (on the fly):
>>
>> library(rgdal)
>> library(shapefiles)
>>
>> fr <- readOGR("polygonFRA_WGS84.shp", "polygonFRA_WGS84") # a 
>> shapefile of France with complex topology (holes, islands...) in 
>> WGS84 coordinates
>> pp <- slot(fr, "polygons") # take the polygons
>> cf <- coordinates(slot(pp[[39]], "Polygons")[[1]])  # extract the 
>> coordinates of the main polygon ("continental France")
>> pf <- list(x=cf[,1], y=cf[,2]) # list of coordinates, as dp() needs a 
>> list and not a matrix or dataframe...
>> cf1 <- dp(pf, 0.1) # simplification, with a bandwith of 0.1 decimal 
>> degree
>> plot(fr)  # to see the result
>> points(cf1, col="red", t="l")
>
> This seems to work OK given that slivers are not important:
> library(sp)
> library(Guerry)
> # installed from R-Forge
> data(gfrance)
> object.size(gfrance)
> pls <- slot(gfrance, "polygons")
> pls_dp <- vector(mode="list", length=length(pls))
> require(shapefiles)
> tol <- 2500
> minarea <- 500000
> for (i in 1:length(pls)) {
>   Pls <- slot(pls[[i]], "Polygons")
>   Pls_dp <- vector(mode="list", length=length(Pls))
>   for (j in 1:length(Pls)) {
>     crds <- slot(Pls[[j]], "coords")
>     crds_s <- dp(list(x=crds[,1], y=crds[,2]), tolerance=tol)
>     crds_s <- do.call("cbind", crds_s)
>     if(!identical(crds_s[1,], crds_s[nrow(crds_s),]))
>       crds_s <- rbind(crds_s, crds_s[1,])
>     Pls_dp[[j]] <- Polygon(crds_s)
>   }
>   Keep <- logical(length(Pls_dp))
>   for (j in 1:length(Pls_dp)) {
>     Keep[j] <- TRUE
>     if (slot(Pls_dp[[j]], "area") < minarea) Keep[j] <- FALSE
>   }
>   Pls_dp <- Pls_dp[Keep]
>   pls_dp[[i]] <- Polygons(Pls_dp, ID=slot(pls[[i]], "ID"))
> }
> gfrance_dp <- SpatialPolygonsDataFrame(SpatialPolygons(pls_dp), 
> data=slot(gfrance, "data"))
> object.size(gfrance_dp)
>
> If this was a function, the input would be an object inheriting from 
> SpatialPolygons, the tolerance, and a minimum polygon area. The 
> removal of small Polygon objects needs protecting from removing all 
> belonging to a given Polygons object. The CRS also needs copying 
> across. The objects are rebuilt to correct areas, centroids, plot 
> orders, and the bounding box, all of which may change. For figures, 
> the companion thread on R-help is relevant, PDF output for choropleth 
> maps is often a good deal larger in file size than that of the 
> equivalent PNG device.
>
> I'll ask the shapefiles authors whether I can copy dp() to maptools 
> and include suitable methods - this will help in benchmarking the 
> future GEOS version.
>
> Hope this helps,
>
> Roger
>
>>
>> HTH
>> David
>>
>> Michael Friendly a écrit :
>>> I think, for my application, I'd be happy with the D-P polyline 
>>> simplification algorithm, because that is what is used
>>> in SAS (worked well), and I don't think there are unusual topologies 
>>> in my map of France that would be so severely
>>> out of whack as to lead to *significant* visual artifacts.  In fact, 
>>> you might well expect some artifacts from any visual
>>> thinning, but it's a matter of the tradeoff in the way the thinned 
>>> map is used in a visualization.  Mark Monmonier's
>>> US Visibility Map might be an extremely thinned, but highly useful 
>>> example.
>>>
>>> For R spatial analysis, I think this is worth pursuing and 
>>> integrating into sp methods.  In SAS, proc greduce works simply
>>> by adding another variable, density, to the (x,y) coordinates of the 
>>> spatial polygons, density %in% 1:5, where density==5
>>> is the full map.  It is then a simple matter to subset the polygon 
>>> outlines by saying
>>>
>>> data smallmap;
>>>    set mymap;
>>>    where density<4;
>>>
>>> or
>>>
>>> proc gmap map=mymap(where=(density<4));
>>> ...
>>>
>>> Meanwhile, I can't see easily how I could use shapefiles::dp() to 
>>> thin my Guerry::gfrance maps, because the documentation is,
>>> shall we say, somewhat thin.
>>> -Michael
>>>
>>>
>>>
>>> Roger Bivand wrote:
>>>> On Wed, 18 Nov 2009, Pinaud David wrote:
>>>>
>>>>> Hi Michael,
>>>>> maybe you should try the function dp() in the package shapefiles 
>>>>> that is an implementation of the Douglas-Peucker polyLine 
>>>>> simplification algorithm.
>>>>
>>>> Note that its help page does warn that it is not 
>>>> topology-preserving, that is that lines are generalised, but that 
>>>> coincident lines (boundaries of neighbouring polygons) may be 
>>>> generalised differently. GEOS offers a topology-preserving line 
>>>> generalisation facility, which ought to take longer but do better 
>>>> than dp(), because it will not lead to visual artefacts 
>>>> (overlapping polygons, interpolygon slivers, etc.).
>>>>
>>>> Roger
>>>>
>>>>> HTH
>>>>> David
>>>>>
>>>>> Michael Friendly a écrit :
>>>>>> The Guerry package contains two maps of france (gfrance, 
>>>>>> gfrance85) which are quite detailed and large in size (~900K).
>>>>>> In writing a vignette for the package, there are quite a few 
>>>>>> figures that use the map multiple times in a layout, and
>>>>>> consequently result in huge file sizes for the .PDF files 
>>>>>> created.  For these purposes, the map need not be nearly
>>>>>> so detailed.
>>>>>>
>>>>>> I'm wondering if there is a facility to "thin" the map by drawing 
>>>>>> it at a lower density of lines in the polygon regions.
>>>>>> When I was working with SAS, there was a GREDUCE procedure that 
>>>>>> did this nicely.
>>>>>>
>>>>>> thanks,
>>>>>> -Michael
>>>>>>
>>>>>
>>>>>
>>>>
>>>
>>>
>>
>>
>


-- 
Michael Friendly     Email: friendly AT yorku DOT ca 
Professor, Psychology Dept.
York University      Voice: 416 736-5115 x66249 Fax: 416 736-5814
4700 Keele Street    http://www.math.yorku.ca/SCS/friendly.html
Toronto, ONT  M3J 1P3 CANADA



More information about the R-sig-Geo mailing list