I had to Union geometries with a high degree of overlap, and the ST_Union function was far too slow (union on 18,000 polygons took over two hours.)<br><br>So I wrote a pl/pgsql implementation of CascadedUnion, which will tide me over till PostGIS gets its own CascadedUnion.
<br><br>The first function is a convenience function that just discovers the initial extent of the table and calls the second function.<br>The second function is recursive and splits the extent until there are fewer than a certain number of points, calling ST_Union on such subsets.
<br><br>So for example:<br><blockquote style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;" class="gmail_quote">SELECT CascadedUnion(&#39;public&#39;, &#39;mytable&#39;, &#39;the_geom&#39;, 1000);
<br></blockquote>will split the bounding box recursively until there are fewer than 1000 geometries in each box, and call ST_Union on each of these subsets.<br><br>For my (highly overlapping) data set, this does in 18 seconds what a plain ST_Union took more than two hours to do. For non-overlapping datasets it will work out slower.
<br><br>Heres the code:<br><br><div>&nbsp; <br></div><blockquote style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;" class="gmail_quote">CREATE OR REPLACE FUNCTION CascadedUnion(character varying, character varying, character varying, integer)&nbsp; 
<br></blockquote><blockquote style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;" class="gmail_quote">RETURNS geometry AS<br>$BODY$<br>DECLARE<br>&nbsp;&nbsp;&nbsp; curs1 refcursor;<br>&nbsp;&nbsp;&nbsp; bounds BOX2D;
<br>BEGIN<br>&nbsp;&nbsp;&nbsp; OPEN curs1 FOR EXECUTE &#39;SELECT extent(&#39; || $3 || &#39;) FROM &#39; || $1 || &#39;.&#39; || $2;<br>&nbsp;&nbsp;&nbsp; FETCH curs1 INTO bounds;<br>&nbsp;&nbsp;&nbsp; RETURN CascadedUnion($1, $2, $3, $4, bounds);<br>END;<br>$BODY$
<br>LANGUAGE &#39;plpgsql&#39; VOLATILE;<br><br>CREATE OR REPLACE FUNCTION CascadedUnion(character varying, character varying, character varying, integer, box3d)<br>RETURNS geometry AS<br>$BODY$<br>DECLARE<br>&nbsp;&nbsp;&nbsp; end_geom GEOMETRY;
<br>&nbsp;&nbsp;&nbsp; bounds BOX3D;<br>&nbsp;&nbsp;&nbsp; bounds1 BOX3D;<br>&nbsp;&nbsp;&nbsp; bounds2 BOX3D;<br>&nbsp;&nbsp;&nbsp; numfeatures integer;<br>&nbsp;&nbsp;&nbsp; curs1 refcursor;<br>&nbsp;&nbsp;&nbsp; curs2 refcursor;<br>&nbsp;&nbsp;&nbsp; max_x real;<br>&nbsp;&nbsp;&nbsp; max_y real;<br>&nbsp;&nbsp;&nbsp; min_x real;<br>&nbsp;&nbsp;&nbsp; min_y real;<br>
&nbsp;&nbsp;&nbsp; result1 geometry;<br>&nbsp;&nbsp;&nbsp; result2 geometry;<br>&nbsp;&nbsp;&nbsp; emptygeom geometry;<br>BEGIN<br>&nbsp;&nbsp;&nbsp; max_x := xmax($5);<br>&nbsp;&nbsp;&nbsp; min_x := xmin($5);<br>&nbsp;&nbsp;&nbsp; max_y := ymax($5);<br>&nbsp;&nbsp;&nbsp; min_y := ymin($5);<br><br>&nbsp;&nbsp;&nbsp; select into bounds $5;<br>
&nbsp;&nbsp;&nbsp; OPEN curs1 FOR EXECUTE &#39;SELECT count(*) FROM &#39; || $1 || &#39;.&#39; || $2 || &#39; WHERE &#39; || $3 || &#39; &amp;&amp; setsrid(box2d(&#39;&#39;BOX(&#39; || min_x || &#39; &#39; || min_y || &#39;,&#39; || max_x || &#39; &#39; || max_y || &#39;)&#39;&#39;), srid(&#39; || $3 || &#39;)) AND intersects(centroid(&#39; || $3 || &#39;), setsrid(box2d(&#39;&#39;BOX(&#39; || min_x || &#39; &#39; || min_y || &#39;,&#39; || max_x || &#39; &#39; || max_y || &#39;)&#39;&#39;), srid(&#39; || $3 || &#39;)))&#39;;
<br>&nbsp;&nbsp;&nbsp; FETCH curs1 INTO numfeatures;<br><br>&nbsp;&nbsp;&nbsp; IF numfeatures &lt; $4 THEN <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OPEN curs2 FOR EXECUTE &#39;select st_union(&#39; || $3 || &#39;) from &#39; || $1 || &#39;.&#39; || $2 || &#39; where &#39; || $3 || &#39; &amp;&amp; setsrid(box2d(&#39;&#39;BOX(&#39; || min_x || &#39; &#39; || min_y || &#39;,&#39; || max_x || &#39; &#39; || max_y || &#39;)&#39;&#39;), srid(&#39; || $3 || &#39;)) AND intersects(centroid(&#39; || $3 || &#39;), setsrid(box2d(&#39;&#39;BOX(&#39; || min_x || &#39; &#39; || min_y || &#39;,&#39; || max_x || &#39; &#39; || max_y || &#39;)&#39;&#39;), srid(&#39; || $3 || &#39;)))&#39;;
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FETCH curs2 INTO end_geom;<br>&nbsp;&nbsp;&nbsp; ELSE<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --split in longest dimension<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IF max_x - min_x &gt; max_y - min_y THEN<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bounds1 := makebox3d(makepoint(min_x, min_y), makepoint((max_x+min_x)/2, max_y));
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bounds2 := makebox3d(makepoint((max_x + min_x)/2, min_y), makepoint(max_x, max_y));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ELSE<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bounds1 := makebox3d(makepoint(min_x, min_y), makepoint(max_x, (max_y+min_y)/2));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bounds2 := makebox3d(makepoint(min_x, (max_y+min_y)/2), makepoint(max_x, max_y));
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; END IF;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --recurse<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SELECT INTO result1 CascadedUnion($1, $2, $3, $4, bounds1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SELECT INTO result2 CascadedUnion($1, $2, $3, $4, bounds2);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SELECT INTO emptygeom geomfromtext(&#39;GEOMETRYCOLLECTION(EMPTY)&#39;, find_srid($1, $2, $3));
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SELECT INTO end_geom st_union(coalesce(result1, emptygeom), coalesce(result2, emptygeom));<br>&nbsp;&nbsp;&nbsp; END IF;<br>&nbsp;&nbsp;&nbsp; RETURN end_geom;<br><br>END;<br>$BODY$<br>LANGUAGE &#39;plpgsql&#39; VOLATILE;<br></blockquote><br>
<br>Regards<br>Craig<br><br>One Track Mind Ltd.<br>PO Box 1604, Shortland St, Auckland, New Zealand<br>Phone +64-9-966 0433 Fax +64-9-969 0045<br>Web <a href="http://www.onetrackmind.co.nz">http://www.onetrackmind.co.nz</a>
<br>