Saturday 16 April 2016

postgis - Using ST_Difference to remove overlapping features?


I am trying to use ST_Difference to create a set of polygons (processing.trimmedparcelsnew) that do not contain any of the area covered by another set of polygons (test.single_geometry_1) using PostGis 2.1 (and Postgres SQL 9.3). Here is my query:



CREATE TABLE processing.trimmedparcelsnew AS
SELECT
orig.id, ST_Difference(orig.geom, cont.geom) AS difference
FROM
test.single_geometry_1 cont,
test.multi_geometry_1 orig;

But the resulting polygons have not been trimmed, instead they seem to have been split where they intersect with the other layer. I have tried just running the select without putting the result in a table and everything else I can think of, but I can't seem to get this function to work.


I have attached a picture of the result


enter image description here





After comments, I have tried adding a WHERE clause. I want the parcels that have no intersections, and the intersecting areas of the other parcels removed (the layer test.single_geometry represents contamination I want removed from my parcels). I tried an intersect but of course I actually want the non intersections so I am now trying a disjoint. I have also tried adding the orig to my table but the documentation for ST_Difference (http://postgis.net/docs/ST_Difference.html) does say it returns the exact geometry I need (a geometry that represents that part of geometry A that does not intersect with geometry B), so I am confused as to why I would want the original polygon in my table instead. Anyway, here is my modified code:


CREATE TABLE processing.trimmedparcelsnew AS
SELECT
orig.id, ST_Difference(orig.geom, cont.geom) AS difference, orig.geom AS geom
FROM
test.single_geometry_1 cont,
test.multi_geometry_1 orig
WHERE ST_Disjoint(orig.geom, cont.geom);




Following from dbaston's answer I have now tried:


CREATE TABLE processing.parcels_trimmed AS
SELECT id, COALESCE(ST_Difference(geom, (SELECT ST_Union(b.geom)
FROM test.single_geometry_1 b
WHERE ST_Intersects(a.geom, b.geom)
AND a.id != b.id)), a.geom)
FROM test.multi_geometry_1 a;

The result of this is just a copy of test.multi_geometry_1. Though now the splitting is no longer occurring.



I tried the earlier version, but again just get a copy of test.multi_geometry_1:


CREATE TABLE processing.parcels_trimmed_no_coalesce AS
SELECT id, COALESCE(ST_Difference(geom, (SELECT ST_Union(b.geom)
FROM test.single_geometry_1 b
WHERE ST_Intersects(a.geom, b.geom)
AND a.id != b.id)), a.geom)
FROM test.multi_geometry_1 a;

I am starting to wonder if there is something else I am doing wrong? The proceeding statement is:


DROP TABLE IF EXISTS processing.parcels_trimmed_no_coalesce;


And I am running the queries from the PostgreSQL SQL query window and Openjump.


The statement I use to see the table is:


SELECT * FROM processing.parcels_trimmed_no_coalesce;



In the interest of simplification I have now reduced this query down to just:


SELECT id, COALESCE(ST_Difference(geom, (SELECT ST_Union(b.geom) 
FROM test.geometriestocutagainst b
WHERE ST_Intersects(a.geom, b.geom)

AND a.id != b.id)), a.geom)
FROM test.geometriestocut a;

This still results in just the original polygons (test.geometriestocut) when the desired result is the original trimmed against test.geometriestocutagainst.



Answer



A self-join allows you to operate on the relationship between pairs of two features. But I don't think you're interested in pairs: for each feature, you want to operate on the relationship between that feature and all other features in your dataset. You can accomplish this with a subquery expression:


CREATE TABLE parcels_trimmed AS
SELECT id, ST_Difference(geom, (SELECT ST_Union(b.geom)
FROM parcels b
WHERE ST_Intersects(a.geom, b.geom)

AND a.id != b.id))
FROM parcels a;

You might see something odd in the results, though. Parcels that don't have any overlaps are being dropped entirely! That's because the ST_Union aggregate on an empty recordset is going to be NULL, and ST_Difference(geom, NULL) is NULL. To smooth this over, you need to wrap your ST_Difference call in a COALESCE:


CREATE TABLE parcels_trimmed AS
SELECT id, COALESCE(ST_Difference(geom, (SELECT ST_Union(b.geom)
FROM parcels b
WHERE ST_Intersects(a.geom, b.geom)
AND a.id != b.id)), a.geom)
FROM parcels a;


This means that if the result of ST_Difference is NULL, the coalesced expression will evaluate to the original geometry.


The above query will remove overlapping areas from your domain entirely. If you instead want to pick a winner, you could do a.id < b.id, or some other criterion, instead of a.id != b.id.


No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...