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. |