<?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 postgis)</title><link>https://www.grulic.org.ar/~mdione/glob/</link><description></description><atom:link href="https://www.grulic.org.ar/~mdione/glob/categories/postgis.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 &lt;a href="mailto:mdione@grulic.org.ar"&gt;Marcos Dione&lt;/a&gt; </copyright><lastBuildDate>Sat, 04 Apr 2026 12:37:05 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Block sizes from OSM data</title><link>https://www.grulic.org.ar/~mdione/glob/posts/block-sizes-from-osm-data/</link><dc:creator>Marcos Dione</dc:creator><description>&lt;p&gt;My city has big blocks. One close to mine is around 6km of circumference. I wanted to make a map that show the sizes of
the blocks. What I have is a rendering DB for my area. Let's see what I can do.&lt;/p&gt;
&lt;p&gt;An obvious solution would be to use any routing database, which would convert squiggly segments into edges of a graph. I
feebly tried that, but no routing system I know is packaged for Debian, and one of them asked me to compile it. I could
compile it, but I was lazy that way.&lt;/p&gt;
&lt;p&gt;So the plan is this: take the whole street network&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="https://www.grulic.org.ar/~mdione/glob/posts/block-sizes-from-osm-data/#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;  and recursively remove the dead end streets. The recursion part 
is because there are areas where there are whole branches of dead end streets, so if in one pass a street A might look 
not dead because it connects to another B, but B is removed, the next pass should remove A too. So, for each street, 
take each end, and see how many other segments it's connected to; if 0, it's a dead end and we
should remove it. Keep going until no new streets were removed.&lt;/p&gt;
&lt;p&gt;One problem is how the data is represented. In a rendering database, streets do not exist, just segments with a 
consistent tagging, so streets can be split if, for instance, max speed changes, or there's a bridge. But one thing 
they're not split on is where another street joins them (which would be reflected in a routing db, lazy me! :)&lt;/p&gt;
&lt;p&gt;This does not matter when searching for connected streets to a point, but a segment might have parts that are a dead end,
and parts that are not, because they're connected to other streets(segments) that are connected too. So now, for each end,
take the point, and if it's not connected, removed the point and try again. The full algo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Find all the segments&lt;/li&gt;
&lt;li&gt;While no segment has been removed&lt;ul&gt;
&lt;li&gt;For each segment&lt;ul&gt;
&lt;li&gt;For each end&lt;ul&gt;
&lt;li&gt;While the end is not connected&lt;ul&gt;
&lt;li&gt;Remove the end from the segment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If not enough nodes left, remove the segment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Looks like an infinite problem! The city I'm interested in has 16k segments, and this looks worse than O(N²)! But we 
&lt;em&gt;can&lt;/em&gt; do some shortcuts. One of the expensive operations is "the end is connected" or "find all the other segments that
include this point". Luckily, a rendering DB has a geographic index, and we can do tricks like looking for segments only 
around the area of the segment you're looking at, so reducing the amount of comparisons by a lot: less that 10 instead of
16k! And of course, you should not consider segments you already removed.&lt;/p&gt;
&lt;p&gt;To be honest, I thought this would take way more effort and code. Yes, I changed the algo thrice, but all in all it took 
me like 6h to get it as it is. Now, it is not perfect. It does not detect some artifacts (mostly cycles connected to a 
single segment), but is good enough for me for now. Not bad for &amp;lt;200 lines of Python and SQL :)&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#! /usr/bin/env python3&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;psycopg2&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;shapely&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;from_wkb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set_srid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LineString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Fetching all segments, takes a while.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;psycopg2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'europe'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;
&lt;span class="s2"&gt;        WITH marseille AS (&lt;/span&gt;
&lt;span class="s2"&gt;            SELECT way&lt;/span&gt;
&lt;span class="s2"&gt;            FROM planet_osm_polygon&lt;/span&gt;
&lt;span class="s2"&gt;            WHERE osm_id = -76469&lt;/span&gt;
&lt;span class="s2"&gt;        )&lt;/span&gt;
&lt;span class="s2"&gt;        SELECT line.osm_id, line.way&lt;/span&gt;
&lt;span class="s2"&gt;        FROM&lt;/span&gt;
&lt;span class="s2"&gt;            planet_osm_line AS line,&lt;/span&gt;
&lt;span class="s2"&gt;            marseille&lt;/span&gt;
&lt;span class="s2"&gt;        WHERE&lt;/span&gt;
&lt;span class="s2"&gt;            line.way &amp;amp;&amp;amp; marseille.way AND&lt;/span&gt;
&lt;span class="s2"&gt;            line.highway IN (&lt;/span&gt;
&lt;span class="s2"&gt;                'primary',&lt;/span&gt;
&lt;span class="s2"&gt;                'primary_link',&lt;/span&gt;
&lt;span class="s2"&gt;                'secondary',&lt;/span&gt;
&lt;span class="s2"&gt;                'secondary_link',&lt;/span&gt;
&lt;span class="s2"&gt;                'tertiary',&lt;/span&gt;
&lt;span class="s2"&gt;                'tertiary_link',&lt;/span&gt;
&lt;span class="s2"&gt;                'residential',&lt;/span&gt;
&lt;span class="s2"&gt;                'unclassified',&lt;/span&gt;
&lt;span class="s2"&gt;                'living_street',&lt;/span&gt;
&lt;span class="s2"&gt;                'road'&lt;/span&gt;
&lt;span class="s2"&gt;            ) AND&lt;/span&gt;
&lt;span class="s2"&gt;            (line.access IS NULL OR line.access = 'yes')&lt;/span&gt;
&lt;span class="s2"&gt;            &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;" LIMIT &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;
&lt;span class="s2"&gt;    """&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;segments_removed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;666&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# fake osm_id so line.osm_id NOT IN %s AND works (empty sets are syntax errors)&lt;/span&gt;

    &lt;span class="c1"&gt;# first pass: collect all segments&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;osm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;from_wkb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;

    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; segments."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# second pass: eliminate segments for which we can find an unconnected end&lt;/span&gt;
    &lt;span class="n"&gt;pass_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"PASS &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pass_number&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;2d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;total_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;total_changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;to_remove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;osm_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
            &lt;span class="n"&gt;removed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
            &lt;span class="n"&gt;connections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

                    &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;
&lt;span class="s2"&gt;                        WITH segment AS (&lt;/span&gt;
&lt;span class="s2"&gt;                            SELECT way&lt;/span&gt;
&lt;span class="s2"&gt;                            FROM planet_osm_line&lt;/span&gt;
&lt;span class="s2"&gt;                            WHERE osm_id = &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;
&lt;span class="s2"&gt;                        )&lt;/span&gt;
&lt;span class="s2"&gt;                        SELECT&lt;/span&gt;
&lt;span class="s2"&gt;                            line.osm_id,&lt;/span&gt;
&lt;span class="s2"&gt;                            line.name&lt;/span&gt;
&lt;span class="s2"&gt;                        FROM planet_osm_line AS line, segment&lt;/span&gt;
&lt;span class="s2"&gt;                        WHERE&lt;/span&gt;
&lt;span class="s2"&gt;                            line.way &amp;amp;&amp;amp; ST_Buffer(ST_Envelope(segment.way), 10) AND&lt;/span&gt;
&lt;span class="s2"&gt;                            line.osm_id != &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; AND&lt;/span&gt;
&lt;span class="s2"&gt;                            line.osm_id NOT IN &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; AND&lt;/span&gt;
&lt;span class="s2"&gt;                            line.highway IN (&lt;/span&gt;
&lt;span class="s2"&gt;                                'trunk',&lt;/span&gt;
&lt;span class="s2"&gt;                                'trunk_link',&lt;/span&gt;
&lt;span class="s2"&gt;                                'primary',&lt;/span&gt;
&lt;span class="s2"&gt;                                'primary_link',&lt;/span&gt;
&lt;span class="s2"&gt;                                'secondary',&lt;/span&gt;
&lt;span class="s2"&gt;                                'secondary_link',&lt;/span&gt;
&lt;span class="s2"&gt;                                'tertiary',&lt;/span&gt;
&lt;span class="s2"&gt;                                'tertiary_link',&lt;/span&gt;
&lt;span class="s2"&gt;                                'residential',&lt;/span&gt;
&lt;span class="s2"&gt;                                'unclassified',&lt;/span&gt;
&lt;span class="s2"&gt;                                'living_street',&lt;/span&gt;
&lt;span class="s2"&gt;                                'road'&lt;/span&gt;
&lt;span class="s2"&gt;                            ) AND&lt;/span&gt;
&lt;span class="s2"&gt;                            (access IS NULL OR access = 'yes') AND&lt;/span&gt;
&lt;span class="s2"&gt;                            ST_Intersects(line.way, ST_GeomFromWKB(&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;, 3857));&lt;/span&gt;
&lt;span class="s2"&gt;                    """&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&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="n"&gt;osm_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments_removed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wkb&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

                    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="c1"&gt;# removing one point removes the segment&lt;/span&gt;
                            &lt;span class="n"&gt;removed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
                            &lt;span class="n"&gt;to_remove&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&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="k"&gt;break&lt;/span&gt;

                        &lt;span class="c1"&gt;# remove the point&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LineString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
                        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LineString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

                        &lt;span class="n"&gt;changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;connections&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;break&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;
                &lt;span class="n"&gt;total_changed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

            &lt;span class="n"&gt;total_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;total_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;removed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'|'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;segments_removed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_remove&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;to_remove&lt;/span&gt; &lt;span class="p"&gt;]))&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;osm_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;to_remove&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total_changed&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; changed, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_remove&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; removed, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; left."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# if only some segments were changed, the topology does not change, so we save one pass&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_remove&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;

        &lt;span class="n"&gt;pass_number&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Saving segments..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""&lt;/span&gt;
&lt;span class="s2"&gt;            INSERT INTO streets (way)&lt;/span&gt;
&lt;span class="s2"&gt;            VALUES (ST_GeomFromWKB(&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;, 3857))&lt;/span&gt;
&lt;span class="s2"&gt;        """&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wkb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Cutting..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="n"&gt;buffered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;marseille&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marseille&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'|'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blocks.geojson'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w+'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# GeoJSONstream, one GeoJSON object per line&lt;/span&gt;
    &lt;span class="c1"&gt;# GDAL/QGIS won't accept &lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;polygon&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;marseille&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geoms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;polygon&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Runtime takes a while because someone correctly micromapped a 350m dead end road due to diffrences in road side parking, 
14 segments/passes in total; otherwise 8 would have suffised. I could have added a heuristic where, when a pass
removed just a few segments, the next pass would only search among the segments close to those. Technically I could do 
this from the first pass.&lt;/p&gt;
&lt;p&gt;One thing that surprised me whas this: QGIS can read GeoJSON files, but if they're going to be a colleciton of things, 
better be a GeoJSONseq, that is a file with a GeoJSON object per line. But since these files do not include info about 
the EPSG, you have to set it by hand on QGIS. now, this would be fine if QGIS would ask about it when loading the
layer, but instead two things happen: it confuses the EPSG, but would still correctly zoom to the layer assuming that 
the layer has the same projection as the project. Thanks to &lt;code&gt;uglyhack#qgis@libera.chat&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Several caveats:&lt;/p&gt;
&lt;p&gt;I didn't include motorways or tunks in the block computation, because they create a complication in the definition
of block, specially with bridges over or tunnels under other roads, of which there are a lot here. I know
at least 3 bridges that still break the graph; there are few enough that I can ignore them.&lt;/p&gt;
&lt;p&gt;The city has big blocks of industrial areas. Service roads are not included, and in any case most are dead ends.&lt;/p&gt;
&lt;p&gt;The biggest areas are actually not urban, including a big chunk of a National Park in the two big red areas to the 
South&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="https://www.grulic.org.ar/~mdione/glob/posts/block-sizes-from-osm-data/#fn:3"&gt;2&lt;/a&gt;&lt;/sup&gt; and the ports and the sea nearby&lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="https://www.grulic.org.ar/~mdione/glob/posts/block-sizes-from-osm-data/#fn:4"&gt;3&lt;/a&gt;&lt;/sup&gt;. Extracting a real urban area is possible, but harder. The road network
should give you an idea of what it looks like.&lt;/p&gt;
&lt;p&gt;On top I also drew three things, roads (grey, white, blue), rivers (thick blue) and train tracks (black), to give a 
better idea of the complexity of the city. This time it includes motorways, trunks, service and private roads. The 
latter also give an idea of how much is in private hands (white). The thin blue lines are the streets thad define the 
blocks, and the grey are public but dead ends.&lt;/p&gt;
&lt;p&gt;Edit: while looking for something else I found this function:&lt;/p&gt;
&lt;p&gt;https://postgis.net/docs/ST_Polygonize.html&lt;/p&gt;
&lt;p&gt;Maybe one day I'll use this one instead :)&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://www.grulic.org.ar/~mdione/glob/images/Marseille_blocks.png"&gt;&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I'm focused on a traffic issue: the city is not navigable by car, leading to lots of traffic in the few streets 
  available, leading to lots of noise from impatient drivers. &lt;a class="footnote-backref" href="https://www.grulic.org.ar/~mdione/glob/posts/block-sizes-from-osm-data/#fnref:1" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;The map is rotated 108° so I could zoom in as much as possible, so South is rougly to the left. &lt;a class="footnote-backref" href="https://www.grulic.org.ar/~mdione/glob/posts/block-sizes-from-osm-data/#fnref:3" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;See where all the ferry lines go to; that's the Vieux Port (Old Port), and the new, industrial one is in the same 
  area to the North/right. &lt;a class="footnote-backref" href="https://www.grulic.org.ar/~mdione/glob/posts/block-sizes-from-osm-data/#fnref:4" title="Jump back to footnote 3 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>geojson</category><category>gis</category><category>openstreetmap</category><category>postgis</category><category>postgres</category><category>python</category><category>qgis</category><guid>https://www.grulic.org.ar/~mdione/glob/posts/block-sizes-from-osm-data/</guid><pubDate>Sat, 14 Mar 2026 11:16:41 GMT</pubDate></item><item><title>Rendering isolated amenities vs dense regions</title><link>https://www.grulic.org.ar/~mdione/glob/posts/rendering-isolated-amenities-vs-dense-regions/</link><dc:creator>Marcos Dione</dc:creator><description>&lt;p&gt;For a while I have been maintaining a slight fork of &lt;code&gt;osm-carto&lt;/code&gt;. I changed a
few things here and there so I can use the map for several types of activities,
including driving. For that I set important highways (motorway to secondary) so
they stand out more using dark colors, but I also wanted to be able to spot
things that can be critical, like pharmacies, fuel stations and hospitals. Not
far from where I live there is a very mountainous regions
with a few small towns where not always there are these amenities.&lt;/p&gt;
&lt;p&gt;The problem arises when you have to make a balance between a few icons on a sparse
region and too many icons on a very dense one. Initially I started by adding smaller
icons (10px instead of the usual 14px) and my first tests looked OK even on the
most dense part of this region. But I had forgotten to check against really dense
parts, like the biggest cities in Europe.&lt;/p&gt;
&lt;p&gt;Long story short: The map becomes not only useless (too many icons) but also really
ugly.&lt;/p&gt;
&lt;p&gt;This is not the first time I try to do this, and back in January I got a glimpse
of a possibility: I read in the &lt;a href="https://weeklyosm.eu/"&gt;weekly news&lt;/a&gt; about
&lt;a href="https://wikimap.wiki/"&gt;wikimap&lt;/a&gt;, which shows dots in a map for every geo-tagged
wikipedia article. What got my attention is that the developer was using aggregated
icons, so instead of showing a lot of them packed tightly, he's shown a big icon
with a count of aggregated locations.&lt;/p&gt;
&lt;p&gt;So I contacted him and asked him how he did it. He linked me to
&lt;a href="https://gitlab.com/nocylah/wikimap/-/blob/master/wikiscan/wikiscan/sql.py"&gt;the code&lt;/a&gt;,
but warned me that the post processing was really slow. Because of that I never
really tried it.&lt;/p&gt;
&lt;p&gt;But today, after looking at packed clusters of icons in cities like İstanbul, I
decided to give it a go. The good part is that because I only need this for just
a couple of amenities that can be concentrated enough to make the map ugly:
pharmacies and fuel stations. Another advantage is that, unlike wikimap, I only
need this for a single zoom level; the idea is to map those amenities in sparsely
populated zones, assuming that in dense zones they should be common enough. So
here's my first attempt:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;CREATE MATERIALIZED VIEW IF NOT EXISTS density AS
    SELECT
        a.osm_id,
        (SELECT
            count(b)
        FROM
            planet_osm_point AS b
        WHERE
            ST_DWithin(a.way, b.way, 10000)
        AND a.amenity = b.amenity
        AND a.osm_id != b.osm_id) AS density
FROM
    planet_osm_point AS a
WHERE
    a.amenity = 'pharmacy';
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We take a target (&lt;code&gt;a.way&lt;/code&gt;) and we find all the similar objects within a radius
(10k EPSG:3857 meters, which is &lt;em&gt;not&lt;/em&gt; actual meters except at the Equator) and
not counting itself. Then we use a cutout value to decide to render the node or
not:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;[feature = 'amenity_pharmacy'][zoom &amp;gt;= 11][density &amp;lt; 15],
[feature = 'amenity_pharmacy'][zoom &amp;gt;= 14] {
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Looking at the resulting data I found an interesting tidbit: Madrid has
probably the higher pharmacy density in the world. Many nodes there have
&lt;a href="https://dionecanali.hd.free.fr/~mdione/anotherg/sshots/?imageLightboxIndex=Screenshot_20210929_173155.png"&gt;more than 1500 neighboring peers in a 10km radius&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;This was not good enough. Initially I though the reason was that, instead of
density, I should look at minimum distance, replacing &lt;code&gt;count(b)&lt;/code&gt; with
&lt;code&gt;min(ST_Distance(b.way, a.way)&lt;/code&gt; and calling the table &lt;code&gt;isolation&lt;/code&gt;, and define
a different cutout value.
The problem with &lt;em&gt;that&lt;/em&gt; one is that if you have a two or more pharmacies close to
one another but otherwise isolated, I want to draw those. Instead, I reduced the
radius to 3km and played with the cutout value until I was satisfied.&lt;/p&gt;
&lt;p&gt;A disadvantage of this approach is that now you have two values to play with,
the radius and the cutout value, while the other one has only one. Not only that,
every time you want to test a new radius value, you have to run an expensive query.
For Europe, in my puny rig (10yo CPU, 8GiB of RAM, a consumer SSD on SATA) it can
take around 30m, which is not specially expensive once you got the value you're
happy with. Of course, I run my tests in smaller extracts.&lt;/p&gt;
&lt;p&gt;Also, I don't run an up-to-date database, I simply reimport from time to time.
If I did, the data in the materialized view (it's like a table, but remembers
how it was calculated, and can be refreshed) would slowly become out-of-date,
but one could live with that and run the refreshes, say, once a week.&lt;/p&gt;
&lt;p&gt;Lastly, this mostly works for me because I have just a few 'popular' tags that
I render so early (ZL11); for a generic map like OSM's default it would probably
be way more expensive. Wikimap's developer told me that "it takes about 10 hours
for the set of geotagged articles from the English Wikipedia."&lt;/p&gt;
&lt;p&gt;Finally, some screen shots:&lt;/p&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://www.grulic.org.ar/~mdione/glob/images/isolated_pharmacies-1.png"&gt;&lt;/p&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://www.grulic.org.ar/~mdione/glob/images/isolated_pharmacies-2.png"&gt;&lt;/p&gt;
&lt;p&gt;And, if you followed the link about Madrid,
&lt;a href="https://dionecanali.hd.free.fr/~mdione/anotherg/sshots/?imageLightboxIndex=Screenshot_20210929_173230.png"&gt;this is how it looks after&lt;/a&gt;.&lt;/p&gt;</description><category>openstreetmap</category><category>postgis</category><guid>https://www.grulic.org.ar/~mdione/glob/posts/rendering-isolated-amenities-vs-dense-regions/</guid><pubDate>Wed, 29 Sep 2021 16:05:30 GMT</pubDate></item></channel></rss>