Changes between Version 2 and Version 3 of UsersWikiSplitPolygonWithPoints


Ignore:
Timestamp:
04/27/13 14:13:49 (12 years ago)
Author:
nicolasribotosgeo
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • UsersWikiSplitPolygonWithPoints

    v2 v3  
    4242
    4343Linear Referencing function st_line_interpolate_point() and st_line_substring() will be used to first locate all points along their respective Linestrings, then cut linestrings based on these locations.
     44In the first CTE, an union is performed to generate the 0 and 1 line fractions, that are necessary to correctly split linestrings.
    4445To identify each point location for a Linestring, we use the rank() windowing function to generate a ascending id for each successive point location.
    45 Then, a self join of the table will be used to select 2 locations
    46 Based on the technique described here [http://gis.stackexchange.com/questions/178/simplifying-adjacent-polygons]:
    47  * we extract linestrings out of polygons,
    48  * then union and simplify them
    49  * st_polygonize() is used to rebuild surfaces from linestrings.
    50  * Finally, attributes from the initial layer are associated with simplified polygons.
    51 
    52 == Steps ==
    53 
    54 ''Steps are divided into individual queries for the sake of clarity. One big query summarize them at the end of this page''
    55 
    56 1. First extract the input multipolygons into polygons, keeping their departement code. This will allow us to associate attributes to each part of multipolygons at the end of the process.
     46Then, a self join of the table will be used to select the 2 locations to give to st_line_substring
    5747
    5848{{{
    5949#!sql
    60 create table poly as (
    61     select gid, code_dept, (st_dump(geom)).*
    62     from departement
    63 );
    64 }}}
    65 
    66 2. extract rings out of polygons
    67 
    68 {{{
    69 #!sql
    70 create table rings as (
    71     select st_exteriorRing((st_dumpRings(geom)).geom) as g
    72     from poly
    73 );
     50 
     51with locus as (
     52        select l.gid, 0 as l
     53        from lines l
     54        UNION ALL
     55        select l.gid, 1 as l
     56        from lines l
     57        UNION ALL
     58        select l.gid, st_line_locate_point(l.geom, p.geom) as  l
     59        from lines l, points p
     60        where st_dwithin(l.geom, p.geom, 0.01)
     61       
     62        order by gid, l
     63), loc_with_idx as (
     64        select gid, l, rank() over (partition by gid order by l) as idx
     65        from locus
     66)
     67select l.gid, st_line_substring(l.geom, loc1.l, loc2.l) as geom
     68from loc_with_idx loc1 join loc_with_idx loc2 using (gid) join lines l using (gid)
     69where loc2.idx = loc1.idx+1;
    7470}}}
    75 
    76 3. Simplify the rings. At this step, we choose the simplification ratio we want (some trials can be made by calling st_simplifyPreserveTopology on departement table).
    77 Here, no points further than 10km:
    78 
    79 {{{
    80 #!sql
    81 create table simplerings as (
    82     select st_simplifyPreserveTopology(st_linemerge(st_union(g)), 10000) as g
    83     from rings
    84 );
    85 }}}
    86 
    87 4. extract lines as individual objects, in order to rebuild polygons from these simplified lines
    88 
    89 {{{
    90 #!sql
    91 create table simplelines as (
    92     select (st_dump(g)).geom as g
    93     from simplerings
    94 );
    95 }}}
    96 
    97 5.rebuild the polygons, first by polygonizing the lines, with a distinct clause to eliminate overlaping segments that may prevent polygon to be created, then dump the collection of polygons into individual parts, in order to rebuild our layer.
    98 {{{
    99 #!sql
    100 create table simplepolys as (
    101     select (st_dump(st_polygonize(distinct g))).geom as g
    102     from simplelines
    103 );
    104 }}}
    105 
    106 6. Add an id column to help us identify objects and a spatial index
    107 
    108 {{{
    109 #!sql
    110 alter table simplepolys  add column gid serial primary key;
    111 create index simplepolys_geom_gist on simplepolys  using gist(g);
    112 }}}
    113 
    114 7. attribute association between input layer and simplified polygons: First method to retrieve attribute is based on containment of a point of the surface of simplified polygons.
    115 
    116 {{{
    117 #!sql
    118 create table simpledep as (
    119     select code_dept, g
    120     from departement d, simplepolys s
    121     where st_contains(d.geom, st_pointOnSurface(s.g))
    122 );
    123 }}}
    124 
    125 It does not work: code_dept=92 is a curved polygon and fails with st_contains() test:
    126 
    127  [[Image(SPT_bad_attributes.png)]]
    128 
    129 8. Second method is based on percentage of overlaping area comparison. Empirical ratio used here.
    130 {{{
    131 #!sql
    132 
    133 create table simpledep as (
    134         select d.code_dept, s.g as geom
    135         from departement d, simplepolys s
    136         where st_intersects(d.geom, s.g)
    137         and st_area(st_intersection(s.g, d.geom))/st_area(s.g) > 0.5
    138 );
    139 }}}
    140 
    141  It gives better results in our testcase:
    142 
    143  [[Image(SPT_good_attributes.png)]]
    144 
    145 
    146 9. rebuild departements by grouping them by code_dept (other attributes could be re-associated here):
    147 
    148 {{{
    149 #!sql
    150 create table simple_departement as (
    151         select code_dept, st_collect(geom) as geom
    152         from simpledep
    153         group by code_dept
    154 );
    155 }}}
    156 
    157 == Result ==
    158 
    159 Layer now looks like this:
    160 
    161 [[Image(SPT_simple_dept.png)]]
    162 
    163 Small island where collapsed during process:
    164 
    165 [[Image(SPT_islands_removed.png)]]
    166 
    167 == One big query ==
    168 
    169 {{{
    170 #!sql
    171 with poly as (
    172         select gid, code_dept, (st_dump(geom)).*
    173         from departement
    174 ) select d.code_dept, baz.geom
    175  from (
    176         select (st_dump(st_polygonize(distinct geom))).geom as geom
    177         from (
    178                 select (st_dump(st_simplifyPreserveTopology(st_linemerge(st_union(geom)), 10000))).geom as geom
    179                 from (
    180                         select st_exteriorRing((st_dumpRings(geom)).geom) as geom
    181                         from poly
    182                 ) as foo
    183         ) as bar
    184 ) as baz,
    185 poly d
    186 where st_intersects(d.geom, baz.geom)
    187 and st_area(st_intersection(d.g, baz.geom))/st_area(baz.g) > 0.5;
    188 }}}
    189 
    190 == Function wrapper ==
    191 
    192 The following code could be wrapped into a function like this. (It uses unlogged and temp tables, but not sure if it really helps to get speed):
    193 
    194 {{{
    195 -- Simplify the given table of multipolygon with the given tolerance.
    196 -- This function preserves the connection between polygons and try to avoid generating gaps between objects.
    197 -- To identify objects after simplification, area comparison is performed, instead of PIP test, that may fail
    198 -- with odd-shaped polygons. Area comparison may also failed on some cases
    199 
    200 -- Example: (table 'departement' is in the public schema)
    201 -- select * from simplifyLayerPreserveTopology('', 'departement', 'gid', 'geom', 10000) as (gid int, geom geometry);
    202 --
    203 -- @param schename: text, the schema name of the table to simplify. set to null or empty string to use search_path-defined schemas
    204 -- @param tablename: text, the name of the table to simplify
    205 -- @param idcol: text, the name of a unique table identifier column. This is the gid returned by the function
    206 -- @param tolerance: float, the simplify tolerance, in object's unit
    207 -- @return a setof (gid, geom) where gid is the identifier of the multipolygon, geom is the simplified geometry
    208 create or replace function simplifyLayerPreserveTopology (schemaname text, tablename text, idcol text, geom_col text, tolerance float)
    209 returns setof record as $$
    210     DECLARE
    211         schname alias for $1;
    212         tabname alias for $2;
    213         tid alias for $3;
    214         geo alias for $4;
    215         tol alias for $5;
    216         numpoints int:=0;
    217         time text:='';
    218         fullname text := '';
    219 
    220     BEGIN
    221         IF schname IS NULL OR length(schname) = 0 THEN
    222             fullname := quote_ident(tabname);
    223         ELSE
    224             fullname := quote_ident(schname)||'.'||quote_ident(tabname);
    225         END IF;
    226        
    227         raise notice 'fullname: %', fullname;
    228 
    229         EXECUTE 'select sum(st_npoints('||quote_ident(geo)||')), to_char(clock_timestamp(), ''MI:ss:MS'') from '
    230             ||fullname into numpoints, time;
    231         raise notice 'Num points in %: %. Time: %', tabname, numpoints, time;
    232        
    233         EXECUTE 'create unlogged table public.poly as ('
    234                 ||'select '||quote_ident(tid)||', (st_dump('||quote_ident(geo)||')).* from '||fullname||')';
    235 
    236         -- extract rings out of polygons
    237         create unlogged table rings as
    238         select st_exteriorRing((st_dumpRings(geom)).geom) as g from public.poly;
    239        
    240         select to_char(clock_timestamp(), 'MI:ss:MS') into time;
    241         raise notice 'rings created: %', time;
    242        
    243         drop table poly;
    244 
    245         -- Simplify the rings. Here, no points further than 10km:
    246         create unlogged table gunion as select st_union(g) as g from rings;
    247        
    248         select to_char(clock_timestamp(), 'MI:ss:MS') into time;
    249         raise notice 'union done: %', time;
    250        
    251         drop table rings;
    252        
    253         create unlogged table mergedrings as select st_linemerge(g) as g from gunion;
    254        
    255         select to_char(clock_timestamp(), 'MI:ss:MS') into time;
    256         raise notice 'linemerge done: %', time;
    257        
    258         drop table gunion;
    259        
    260         create unlogged table simplerings as select st_simplifyPreserveTopology(g, tol) as g from mergedrings;
    261        
    262        
    263         select to_char(clock_timestamp(), 'MI:ss:MS') into time;
    264         raise notice 'rings simplified: %', time;
    265        
    266         drop table mergedrings;
    267 
    268         -- extract lines as individual objects, in order to rebuild polygons from these
    269         -- simplified lines
    270         create unlogged table simplelines as select (st_dump(g)).geom as g from simplerings;
    271        
    272         drop table simplerings;
    273 
    274         -- Rebuild the polygons, first by polygonizing the lines, with a
    275         -- distinct clause to eliminate overlaping segments that may prevent polygon to be created,
    276         -- then dump the collection of polygons into individual parts, in order to rebuild our layer.
    277         drop table if exists simplepolys;
    278         create  table simplepolys as
    279         select (st_dump(st_polygonize(distinct g))).geom as g
    280         from simplelines;
    281        
    282         select count(*) from simplepolys into numpoints;
    283         select to_char(clock_timestamp(), 'MI:ss:MS') into time;
    284         raise notice 'rings polygonized. num rings: %. time: %', numpoints, time;
    285        
    286         drop table simplelines;
    287 
    288         -- some spatial indexes
    289         create index simplepolys_geom_gist on simplepolys  using gist(g);
    290 
    291         raise notice 'spatial index created...';
    292 
    293         -- works better: comparing percentage of overlaping area gives better results.
    294         -- as input set is multipolygon, we first explode multipolygons into their polygons, to
    295         -- be able to find islands and set them the right departement code.
    296         RETURN QUERY EXECUTE 'select '||quote_ident(tid)||', st_collect('||quote_ident(geo)||') as geom '
    297             ||'from ('
    298             --||'    select distinct on (d.'||quote_ident(tid)||') d.'||quote_ident(tid)||', s.g as geom '
    299             ||'    select d.'||quote_ident(tid)||', s.g as geom '
    300             ||'   from '||fullname||' d, simplepolys s '
    301             --||'    where (st_intersects(d.'||quote_ident(geo)||', s.g) or st_contains(d.'||quote_ident(geo)||', s.g))'
    302             ||'    where st_intersects(d.'||quote_ident(geo)||', s.g) '
    303             ||'    and st_area(st_intersection(s.g, d.'||quote_ident(geo)||'))/st_area(s.g) > 0.5 '
    304             ||'    ) as foo '
    305             ||'group by '||quote_ident(tid);
    306            
    307         --drop table simplepolys;
    308        
    309         RETURN;
    310    
    311     END;
    312 $$ language plpgsql strict;
    313 }}}
    314 
    315 === Example usage ===
    316 ''(table 'departement' is in public schema)''
    317 
    318 {{{
    319 select * from simplifyLayerPreserveTopology('', 'departement', 'gid', 'geom', 10000) as (gid int, geom geometry);
    320 }}}
    321 
    322 == Example with countries ==
    323 
    324 Simplification tolerance: 0.3 degrees :)
    325 
    326 {{{
    327 create table simple_countries as (
    328     select * from simplifyLayerPreserveTopology('', 'countries', 'gid', 'geom', 0.3) as (gid int, geom geometry);
    329 }}}
    330 
    331 || [[Image(world_before.png)]] || [[Image(world_after.png)]] ||
    332 
    333 == !ToDo ==
    334 
    335 Handle enclosed polygons cases.