<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>.:: Marcos Dione/StyXman's glob ::. (Posts about fiona)</title><link>https://www.grulic.org.ar/~mdione/glob/</link><description></description><atom:link href="https://www.grulic.org.ar/~mdione/glob/categories/fiona.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2025 &lt;a href="mailto:mdione@grulic.org.ar"&gt;Marcos Dione&lt;/a&gt; </copyright><lastBuildDate>Thu, 29 May 2025 15:41:11 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Deriving centerlines from riverbanks without</title><link>https://www.grulic.org.ar/~mdione/glob/posts/deriving-centerlines-from-riverbanks-without/</link><dc:creator>Marcos Dione</dc:creator><description>&lt;p&gt;For a long time now I've been thinking on a problem: OSM data sometimes contains
riverbanks that have no centerline. This means that someone mapped (part of) the
coasts of a river (or stream!), but didn't care about adding a line that would
mark its centerline.&lt;/p&gt;
&lt;p&gt;But this should be computationally solvable, right? Well, it's not that easy.
See, for given any riverbank polygon in OSM's database, you have 4 types of
segments: those representing the right and left riverbanks (two types) and the
flow-in and flow-out segments, which link the banks upstream and downstream. With
a little bit of luck there will be only one flow-in and one flow-out segment, but
there are no guarantees here.&lt;/p&gt;
&lt;p&gt;One method could try and identify these segments,
then draw a line starting in the middle of the flow-in segment, calculating the
middle by traversing both banks at the same time, and finally connect to the
middle for the flow-out segment. Identifying the segments by itself is hard,
but it is also possible that the result is not optimal, leading to a jagged line.
I didn't try anything on those lines, but I could try some examples by hand...&lt;/p&gt;
&lt;p&gt;Enter topology, the section of maths that deals with this kind of problems. The
&lt;a href="https://en.wikipedia.org/wiki/Topological_skeleton"&gt;skeleton&lt;/a&gt; of a polygon is a
group of lines that are equidistant to the borders of the polygon. One of the
properties this set of lines provides is direction, which can be exploited to
find the banks and try to apply the previous algorithm. But a skeleton has a lot
of 'branches' that might confuse the algo.
Going a little further,
there's the &lt;a href="https://en.wikipedia.org/wiki/Medial_axis"&gt;medial axis&lt;/a&gt;, which in
most cases can be considered a simplified skeleton, without most of the skeleton
branches.&lt;/p&gt;
&lt;p&gt;Enter free software :) &lt;a href="http://doc.cgal.org/latest/Manual/packages.html"&gt;CGAL&lt;/a&gt;
is a library that can compute a lot of topological properties. PostGIS is clever
enough to leverage those algorithms and present, among others, the functions
&lt;code&gt;ST_StraightSkeleton()&lt;/code&gt; and &lt;code&gt;ST_ApproximateMedialAxis()&lt;/code&gt;. With these two and the
original polygon I plan to derive the centerline. But first an image that will
help explaining it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://www.grulic.org.ar/~mdione/glob/images/expanded_medial_1.png"&gt;&lt;/p&gt;
&lt;p&gt;The green 'rectangle' is the original riverbank polygon. The thin black
line is the skeleton for it; the medium red line is the medial. Notice how the
medial and the center of the skeleton coincide. Then we have the 4 branches
forming a V shape with its vertex at each end of the medial and its other two
ends coincide with the ends of the flow in and flow out segments!&lt;/p&gt;
&lt;p&gt;So the algorithm is simple: start with the medial; from its ends, find the
branches in the skeleton that form that V; using the other two ends of those Vs,
calculate the point right between them, and extend the medial to those points.
This only calculates a centerline. The next step would be to give it a direction.
For that I will need to see if there are any nearby lines that could be part of
the river (that's what the centerline is for, to possibly extend existing
rivers/centerlines), and use its direction to give it to the new centerline.&lt;/p&gt;
&lt;p&gt;For the moment the algorithm only solves this simple case. A slightly more
complex case is not that trivial, as skeletons and medials are returned as a
&lt;code&gt;MultiLineString&lt;/code&gt; with a line for each &lt;em&gt;segment&lt;/em&gt;, so I will have to rebuild them
into &lt;code&gt;LineString&lt;/code&gt;s before processing.&lt;/p&gt;
&lt;p&gt;I put all the code
&lt;a href="https://github.com/StyXman/osm-centerlines/blob/master/centerlines.py"&gt;online&lt;/a&gt;, of course :)
Besides a preloaded PostgreSQL+PostGIS database with OSM data, you'll need
&lt;code&gt;python3-sqlalchemy&lt;/code&gt;, &lt;code&gt;geoalchemy&lt;/code&gt;, &lt;code&gt;python3-fiona&lt;/code&gt; and &lt;code&gt;python3-shapely&lt;/code&gt;. The
first two allows me to fetch the data from the db. Ah! by the way, you will need
a couple of views:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VIEW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;planet_osm_riverbank_skel&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;osm_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;way&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ST_StraightSkeleton&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;way&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;skel&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;planet_osm_polygon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;waterway&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'riverbank'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;VIEW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;planet_osm_riverbank_medial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;osm_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;way&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ST_ApproximateMedialAxis&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;way&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;medial&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;planet_osm_polygon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;waterway&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'riverbank'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Shapely allows me to manipulate the polygonal data, and fiona is used to save the
results to a shapefile. This is the first time I ever use all of them (except
SQLAlchemy), and it's nice that it's so easy to do all this in Python.&lt;/p&gt;</description><category>fiona</category><category>gis</category><category>openstreetmap</category><category>python</category><category>shapely</category><category>sqlalchemy</category><guid>https://www.grulic.org.ar/~mdione/glob/posts/deriving-centerlines-from-riverbanks-without/</guid><pubDate>Tue, 26 Jul 2016 16:55:14 GMT</pubDate></item></channel></rss>