I found a solution, thanks to @robin loche's comment! I post my solution here in case others have similar questions.
The solution uses ST_ApproximateMedialAxis - it is part of the postgis_sfcgal extension of Postgis and creates aproximate medial axes within shapes, so i can be used to calculate the distance to shorelines.
Preparation
Make sure, you have the extension installed:
CREATE EXTENSION postgis_sfcgal;
I have three tables in my archaeological db:
topology.water_body
containing the water body/river system
topology.recrivers
reconstructed river travel paths (simple linepaths)
topology.water_lines
containing the river system's borders as linestrings.
The last table can be created from your river system shape using a SELECT...INTO query:
SELECT (dump_set).path[1] as id, (dump_set).geom as geom
into topology.water_lines
FROM (SELECT ST_Dump(ST_Boundary(geom)::geometry(MULTILINESTRING, 4326)) as dump_set
from topology.water_body) as dump_results;
As you can see, I use EPSG:4326, you might want to adjust this if you use something else. So, for my example, I use the data from my question above. The line shown has the id 3.
The Solution
I had problems using ST_ApproximateMedialAxis, because my colleagues from archaeology created an insanely complex river system with touching rings... The solution was to extract only a part of the river system and check this smaller part. So, I get the bounding box of my path, expand it a bit and clip that:
SELECT clipped.geom as axis
INTO topology.clipped_water_body
from (SELECT (ST_Dump(ST_ClipByBox2D(geom, (SELECT ST_Expand(Box2D(geom), 0.006) AS arr
FROM topology.recrivers
WHERE id = 3)))).geom AS geom
FROM topology.water_body) as clipped
WHERE ST_contains(clipped.geom, (SELECT geom FROM topology.recrivers WHERE id = 3)) = true;
Remember, the id of my shape is 3, thus the query. I also excluded all geometries that do not contain my path (in my example, this would be the case close to the northeast corner). This will create a clipped subpart of my insane river shape - and there will be only one shape left (no touching rings, hopefully):
A second query will create the approximated ideal medians in my single geometry:
SELECT ST_ApproximateMedialAxis(axis) as axis
INTO topology.ideal_paths
FROM topology.clipped_water_body
Of course, I can skip the creation of the clipped_water_body table, but I wanted to make clear what I am doing. This will create a web of lines, as seen in this closeup:
Now, here it becomes interesting. The medial axis is designed to be in the middle of each river arm, so the closest shore point of any point along the axes leads to half the river width! I just need a sample. I can do this by taking the points (using ST_DumpPoints) of the path I want to take through the river (or rather that of the vessel travelling the path). I can create the closest points to the median axes:
SELECT ST_ClosestPoint((ST_DumpPoints(geom)).geom, (SELECT axis FROM topology.ideal_paths)) as geom
INTO topology.closest_points
FROM topology.recrivers
where id = 3;
This will yield a number of sample points on the median axes:
We are almost done now! We now have a couple of samples we can check. The rest is a bit of SQL magic:
SELECT 2 * MIN(ST_Distance(cp.geom::geography, st_closestpoint(wl.geom, cp.geom)::geography)) as min_width
FROM topology.closest_points as cp,
topology.water_lines as wl
Here we compare the distance of each of our water lines (i.e. the shorelines) to each of our sample points. We then take the minimum of all distances and multiply by 2 - this yields be the minimum river width from our sample points - or at least a good approximation.
Summary
Using ST_ApproximateMedialAxis and your path, you can create sample points to measure the distance to the closest shore line. Naturally, this is an approximation only.
Possible Enhancements
You could enhance this method by creating more points on your original path (using ST_LineInterpolatePoints). This will yield better results and might catch some weird edge cases.
This will be done by changing the closest points-calculation into the following two statements:
-- create enough sample points for a good approximation
SELECT (ST_Dump(ST_LineInterpolatePoints(geom, 0.02))).geom
INTO topology.sample_points
FROM topology.recrivers
where id = 3;
-- closest points in the ideal axis
SELECT ST_ClosestPoint(geom, (SELECT axis FROM topology.ideal_paths)) as geom
INTO topology.closest_points
FROM topology.sample_points;
This created 50 samples, as shown here:
Feedback welcome!