Changes between Version 1 and Version 2 of UsersWikiSplitPolygonWithPoints


Ignore:
Timestamp:
Apr 27, 2013, 2:04:47 PM (11 years ago)
Author:
nicolasribotosgeo
Comment:

Legend:

Unmodified
Added
Removed
Modified
  • UsersWikiSplitPolygonWithPoints

    v1 v2  
    1 -- Splitting a table of linestring by a table of points onto or close to the lines --
     1[[TOC]]
     2
     3= Splitting a table of linestring by a table of points onto or close to the lines  =
     4
     5== What we want ==
     6
     7|| Given this layer of Linestrings: || and this layer of points: 
     8|| [[Image(SLBP_dept_ori.png)]] ||  [[Image(SLBP_points.png)]]
     9
     10We want to split the linestrings by the points
     11
     12== The data ==
     13
     14French administrative subdivisions, called "départements", will be used. Data can be downloaded here: [http://professionnels.ign.fr/DISPLAY/000/528/175/5281750/GEOFLADept_FR_Corse_AV_L93.zip]
     15The LIMITE_DEPARTEMENT.SHP shapefile is used. It contains departements limits as MultiLinestrings
     16
     17Data projection is Lambert-93, EPSG:2154
     18
     19== Loading the data ==
     20
     21{{{
     22#!sh
     23shp2pgsql -IiDS -g geom -s 2154 LIMITE_DEPARTEMENT.SHP departement | psql
     24}}}
     25
     26The table of points will be generated by randomly choosing points on the linestrings:
     27
     28{{{
     29#!sql
     30create sequence points_seq;
     31create table points as with rand as (
     32        select random() as locus from generate_series (1, 10)
     33), rand2 as (
     34        select nextval('points_seq') as gid, l.gid as lgid, r.locus, st_line_interpolate_point(l.geom, locus) as geom
     35        from rand as r, lines l
     36) select * from rand2
     37order by random() limit 1500; -- only 1500 pts out of 3300 generated with random locus on the linestrings
     38
     39}}}
     40
     41== Principle of Splitting ==
     42
     43Linear 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.
     44To identify each point location for a Linestring, we use the rank() windowing function to generate a ascending id for each successive point location.
     45Then, a self join of the table will be used to select 2 locations
     46Based 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
     561. 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.
     57
     58{{{
     59#!sql
     60create table poly as (
     61    select gid, code_dept, (st_dump(geom)).*
     62    from departement
     63);
     64}}}
     65
     662. extract rings out of polygons
     67
     68{{{
     69#!sql
     70create table rings as (
     71    select st_exteriorRing((st_dumpRings(geom)).geom) as g
     72    from poly
     73);
     74}}}
     75
     763. 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).
     77Here, no points further than 10km:
     78
     79{{{
     80#!sql
     81create table simplerings as (
     82    select st_simplifyPreserveTopology(st_linemerge(st_union(g)), 10000) as g
     83    from rings
     84);
     85}}}
     86
     874. extract lines as individual objects, in order to rebuild polygons from these simplified lines
     88
     89{{{
     90#!sql
     91create table simplelines as (
     92    select (st_dump(g)).geom as g
     93    from simplerings
     94);
     95}}}
     96
     975.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
     100create table simplepolys as (
     101    select (st_dump(st_polygonize(distinct g))).geom as g
     102    from simplelines
     103);
     104}}}
     105
     1066. Add an id column to help us identify objects and a spatial index
     107
     108{{{
     109#!sql
     110alter table simplepolys  add column gid serial primary key;
     111create index simplepolys_geom_gist on simplepolys  using gist(g);
     112}}}
     113
     1147. 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
     118create 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
     125It does not work: code_dept=92 is a curved polygon and fails with st_contains() test:
     126
     127 [[Image(SPT_bad_attributes.png)]]
     128
     1298. Second method is based on percentage of overlaping area comparison. Empirical ratio used here.
     130{{{
     131#!sql
     132
     133create 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
     1469. rebuild departements by grouping them by code_dept (other attributes could be re-associated here):
     147
     148{{{
     149#!sql
     150create 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
     159Layer now looks like this:
     160
     161[[Image(SPT_simple_dept.png)]]
     162
     163Small island where collapsed during process:
     164
     165[[Image(SPT_islands_removed.png)]]
     166
     167== One big query ==
     168
     169{{{
     170#!sql
     171with 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,
     185poly d
     186where st_intersects(d.geom, baz.geom)
     187and st_area(st_intersection(d.g, baz.geom))/st_area(baz.g) > 0.5;
     188}}}
     189
     190== Function wrapper ==
     191
     192The 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
     208create or replace function simplifyLayerPreserveTopology (schemaname text, tablename text, idcol text, geom_col text, tolerance float)
     209returns 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{{{
     319select * from simplifyLayerPreserveTopology('', 'departement', 'gid', 'geom', 10000) as (gid int, geom geometry);
     320}}}
     321
     322== Example with countries ==
     323
     324Simplification tolerance: 0.3 degrees :)
     325
     326{{{
     327create 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
     335Handle enclosed polygons cases.