/posts  ·   /talks  ·   /boat  ·   /#30DayMapChallenge 2020 | 2022  ·  /About

2022 / Day 18: Blue

Today I’ll be looking at calculating shared paths of polygons with deriving left/right side information for them. This is useful for example for generating lines from admin area polygons where every border line is present only once. Or for example simplifying polygons without losing topological integrity.

I wrote about this same subject some time ago but using a different approach to do it. Time has passed and i’ve developed (hopefully) my skills and understanding, so I’m ready to try out st_sharedpaths.

My original approach had more aggregation in it so so hopefully this way it will be more performant. As a test dataset I’ll generate a set of random points with st_generatepoints as in many previous times during this years #30DayMapChallenge, and cluster them using st_clusterkmeans, followed by doing a st_voronoipolygons to create a space evenly filled in with polygons (see CTE called voros).

Voronoi polygons we’ll be calculating shared paths for. These arecalculated from random point clusters therefore they will not look exactly thesame with every query

As noted before - my original approach to solving this was to aggregate all polygon boundaries and then st_linemerge them. Which can become a rather tedious task if there’s a lot of data. A better way around is with st_sharedpaths which works with a pair of geometries and returns a GeometryCollection of two elements:

In order to rule out duplicates I’ll condense the pairing geometries into and array ordered by the value of their respective identifiers (cl in this case) which allows me to do a

select
    distinct on (<ordered_identifier_array>)
    <ordered_identifier_array>, <ordered_geoms_array>
from
    ...

Another way for getting unique combinations here would be to use

select
    *
from (
    select
        row_number() over(partition by <ordered_identifier_array>) as rn
        <ordered_identifier_array>, <ordered_geoms_array>
    from
        ...
) x
where
    x.rn = 1

as I used for 2022 / Day 6: Network. The result of this is encapsulated in CTE pairs.

For attaching L/R side info to the linestring I previously used an approach of doing a minuscule L/R st_offsetcurve followed by st_lineinterpolatepoint to the midway of the offset linestring and then st_within with the midway point to discover what’s on either side of the line. Now with st_sharedpaths it’s a bit simpler because:

So unless the polygon interiors share space / overlap (meaning their shared paths of boundaries are moving in the same direction) we can safely assume that the first of the geometry pair is always to the right, and the second one to the left for clockwise (st_ispolygoncw) and the other way around for counter-clockwise (st_ispolygonccw) polygons. Which is also the reason why st_forcepolygoncw is used after the voronoi cell unioning in CTE voros. Although it doesn’t do anything in this case - our polygons will be clockwise by default.

output image

with
    minmax as (
        select 1 as oid,
            st_point(
                40500.000000,5993000.000000,3301
            ) as ll,
            st_point(
                1064500.000000,7017000.000000,3301
            ) as ur
    ),
    bounds as (
        select
            st_envelope(
                st_collect(
                    array[ll, ur]
                )
            ) as geom
        from minmax
    ),
    clusters as (
        select
            pt.path[1] as oid, pt.geom,
            st_clusterkmeans(
                pt.geom,
                30,
                10000000
            ) over () cl
        from
            bounds
                join lateral
                    st_generatepoints(
                        bounds.geom,
                        1000
                    ) pts on true
                join lateral
                    st_dump(
                        pts
                    ) pt on true
    ),
    voros as (
        select cl, ints as geom
        from
            bounds, (
                select
                    c.cl,
                    st_forcepolygoncw(st_union(d.geom)) as geom
                from (
                    select
                        st_voronoipolygons(
                            st_collect(geom)
                        ) as geom
                    from
                        clusters
                ) v
                    join lateral
                        st_dump(v.geom) d on true
                    join lateral (
                        select c.cl
                        from clusters c
                        where st_within(c.geom, d.geom)
                        limit 1
                    ) c on true
                group by c.cl
            ) b
                join lateral
                    st_intersection(
                        b.geom,
                        bounds.geom
                    ) ints on true
    ),
    pairs as (
        select
             distinct on (cls)
             cls, geoms
        from (
            select
                a.cl,
                case
                    when a.cl < b.cl then
                        array[a.cl, b.cl]
                    else
                        array[b.cl, a.cl]
                end as cls,
                case
                    when a.cl < b.cl then
                        array[a.geom, b.geom]
                    else
                        array[b.geom, a.geom]
                end as as geoms
            from
                voros a,
                voros b
            where
                st_intersects(a.geom, b.geom) and
                a.cl != b.cl
        ) d
    ),
    shared_paths as (
        select
            row_number() over()::int as oid, pairs.cls,
            st_linemerge(st_collect(d.geom)) as geom
        from pairs
            join lateral
                st_sharedpaths(
                    st_boundary(geoms[1]),
                    st_boundary(geoms[2])
                ) sp on true
            join lateral
                st_dump(sp) d on true
        group by pairs.cls
    )
select
    sp.oid,
    sp.cls[1] as right_cl, sp.cls[2] as left_cl,
    sp.geom
from
    share_paths sp
;