<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>DuckDB &amp;mdash; Sean Barnett</title>
    <link>https://seanbarnett.id.au/tag:DuckDB</link>
    <description>Coffee, basketball, programming</description>
    <pubDate>Thu, 06 Aug 2026 05:31:13 +0000</pubDate>
    <image>
      <url>https://i.snap.as/FOPXss01.png</url>
      <title>DuckDB &amp;mdash; Sean Barnett</title>
      <link>https://seanbarnett.id.au/tag:DuckDB</link>
    </image>
    <item>
      <title>Ingesting the Reference Geoscape Datasets</title>
      <link>https://seanbarnett.id.au/ingesting-geoscape-datasets?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[This post forms part of the ongoing #TagJob project.&#xA;&#xA;In the previous post I introduced two Geoscape datasets that have been made available on the Australian Government&#39;s data.gov.au website: National Roads and Administrative Boundaries. The datasets are distributed in two different formats, neither of which is optimal for my intended spatial processing model. A first task is then to transform the data to a common format, and one that has the right performance characteristics for the project.!--more--&#xA;&#xA;My spatial processing model will cache required meta-data and geometry in RAM, trading significantly higher memory requirements in exchange for significantly faster data access. Loading data into memory requires a high-performance storage engine, and for that I have selected DuckDB.&#xA;&#xA;DuckDB is highly performant in terms of storage and execution, and is further recommended for this application by a trait that might often be seen as a limitation: it&#39;s an embedded database. So, while it can&#39;t do the client-server dance, DuckDB will deliver data to my application without an intermediate network and the overheads that brings. Better still, DuckDB&#39;s spatial extension - and particularly GDAL integration - make it reasonably trivial to ingest both National Roads in GDB format and Administrative Boundaries in SHP format. For example:&#xA;create table mapfeaturestatepolygon as&#xA;select from STRead(&#39;ACTSTATEPOLYGONshp.dbf&#39;);&#xA;However, the code I&#39;ve written does get a just little more complicated. Firstly, the datasets are distributed in a hierarchical directory structure, sometimes with separate files (or actually sets of files) for each state or territory. So I&#39;m fishing through the directory hierarchy for those files, and then joining their contents into single tables.&#xA;&#xA;And secondly, I have elected to &#34;normalise out&#34; coded values and recurring text values (e.g. road names), replacing them with integer foreign keys. My rationale is thus:&#xA;&#xA;this is how I&#39;ll store the data in memory once loaded (to save space), and so it avoids doing any such conversion during the load&#xA;notwithstanding DuckDBs storage smarts, I&#39;m still hoping for space efficiency on disk&#xA;&#xA;One subtlety in this is that I want coded values in the database to be consistent with my enumerations within the code base (i.e. database and code agree that &#34;NATIONAL OR STATE HIGHWAY&#34; has code &#34;1&#34;), but without the overhead of comparing strings on ingestion. This allows me to ingest data rapidly, while still having business rules that consider coded values. My solution here is to use pre-assigned codes for known enumeration values, but to allocate new codes for any extra values that sneak into the database:&#xA;create table mapcoderoadhierarchy as&#xA;   select &#xA;      cast(if (codeenum   = 0, codeenum, max(codeenum) over () + rank() over (partition by codeenum order by codevalue)) as int16) as codekey, &#xA;      codevalue, rowcount as int&#xA;   from (&#xA;      select &#xA;         case when hierarchy is null then 0 &#xA;         when hierarchy=&#39;NATIONAL OR STATE HIGHWAY&#39; then 1 &#xA;         when hierarchy=&#39;ARTERIAL ROAD&#39; then 2&#xA;         when hierarchy=&#39;SUB-ARTERIAL ROAD&#39; then 3&#xA;         when hierarchy=&#39;COLLECTOR ROAD&#39; then 4&#xA;         when hierarchy=&#39;LOCAL ROAD&#39; then 5&#xA;         when hierarchy=&#39;ACCESS ROAD&#39; then 6&#xA;         when hierarchy=&#39;VEHICLE TRACK&#39; then 7&#xA;         when hierarchy=&#39;BUSWAY&#39; then 8&#xA;         when hierarchy=&#39;FERRY&#39; then 9&#xA;         when hierarchy=&#39;FOOTPATH&#39; then 10&#xA;         when hierarchy=&#39;CYCLEPATH&#39; then 11&#xA;         else -1 &#xA;      end as codeenum, hierarchy as codevalue, count() as rowcount&#xA;      from tempmapfeatureroad group by hierarchy&#xA;   );&#xA;&#xA;I am initially focusing on the following datasets / layers, but may add more down the track:&#xA;&#xA;National Roads (4,340,757 rows)&#xA;Administrative Boundaries&#xA;  State Polygon (12,844 rows)&#xA;  Local Government Area Polygon (2,210 rows)&#xA;  Locality Polygon (15,782 rows)&#xA;&#xA;The code for this article is in the TagJobSpatial repository here.&#xA;&#xA;On my MacBook Pro M1 Max processor the load takes approximately 1 minute, and the resultant DuckDB database is about 2.5 gigabytes.&#xA;&#xA;Tags: #TagJob #Geospatial #DuckDB]]&gt;</description>
      <content:encoded><![CDATA[<p><em>This post forms part of the ongoing <a href="https://seanbarnett.id.au/tag:TagJob" class="hashtag"><span>#</span><span class="p-category">TagJob</span></a> project.</em></p>

<p>In <a href="https://seanbarnett.id.au/geoscape-datasets-data-gov-au-https-data-gov-au">the previous post</a> I introduced two Geoscape datasets that have been made available on the Australian Government&#39;s <a href="https://data.gov.au">data.gov.au</a> website: National Roads and Administrative Boundaries. The datasets are distributed in two different formats, neither of which is optimal for my intended spatial processing model. A first task is then to transform the data to a common format, and one that has the right performance characteristics for the project.</p>

<p>My spatial processing model will cache required meta-data and geometry in RAM, trading significantly higher memory requirements in exchange for significantly faster data access. Loading data into memory requires a high-performance storage engine, and for that I have selected DuckDB.</p>

<p>DuckDB is highly performant in terms of storage and execution, and is further recommended for this application by a trait that might often be seen as a limitation: it&#39;s an embedded database. So, while it can&#39;t do the client-server dance, DuckDB will deliver data to my application without an intermediate network and the overheads that brings. Better still, DuckDB&#39;s spatial extension – and particularly GDAL integration – make it reasonably trivial to ingest both National Roads in GDB format and Administrative Boundaries in SHP format. For example:</p>

<pre><code>create table map_feature_state_polygon as
select from ST_Read(&#39;ACT_STATE_POLYGON_shp.dbf&#39;);
</code></pre>

<p>However, the code I&#39;ve written does get a just little more complicated. Firstly, the datasets are distributed in a hierarchical directory structure, sometimes with separate files (or actually sets of files) for each state or territory. So I&#39;m fishing through the directory hierarchy for those files, and then joining their contents into single tables.</p>

<p>And secondly, I have elected to “normalise out” coded values and recurring text values (e.g. road names), replacing them with integer foreign keys. My rationale is thus:</p>
<ul><li>this is how I&#39;ll store the data in memory once loaded (to save space), and so it avoids doing any such conversion during the load</li>
<li>notwithstanding DuckDBs storage smarts, I&#39;m still hoping for space efficiency on disk</li></ul>

<p>One subtlety in this is that I want coded values in the database to be consistent with my enumerations within the code base (i.e. database and code agree that “NATIONAL OR STATE HIGHWAY” has code “1”), but without the overhead of comparing strings on ingestion. This allows me to ingest data rapidly, while still having business rules that consider coded values. My solution here is to use pre-assigned codes for known enumeration values, but to allocate new codes for any extra values that sneak into the database:</p>

<pre><code>create table map_code_road_hierarchy as
   select 
      cast(if (code_enum &gt;= 0, code_enum, max(code_enum) over () + rank() over (partition by code_enum order by code_value)) as int16) as code_key, 
      code_value, row_count as int
   from (
      select 
         case when hierarchy is null then 0 
         when hierarchy=&#39;NATIONAL OR STATE HIGHWAY&#39; then 1 
         when hierarchy=&#39;ARTERIAL ROAD&#39; then 2
         when hierarchy=&#39;SUB-ARTERIAL ROAD&#39; then 3
         when hierarchy=&#39;COLLECTOR ROAD&#39; then 4
         when hierarchy=&#39;LOCAL ROAD&#39; then 5
         when hierarchy=&#39;ACCESS ROAD&#39; then 6
         when hierarchy=&#39;VEHICLE TRACK&#39; then 7
         when hierarchy=&#39;BUSWAY&#39; then 8
         when hierarchy=&#39;FERRY&#39; then 9
         when hierarchy=&#39;FOOTPATH&#39; then 10
         when hierarchy=&#39;CYCLEPATH&#39; then 11
         else -1 
      end as code_enum, hierarchy as code_value, count() as row_count
      from temp_map_feature_road group by hierarchy
   );
</code></pre>

<p>I am initially focusing on the following datasets / layers, but may add more down the track:</p>
<ul><li>National Roads (4,340,757 rows)</li>
<li>Administrative Boundaries
<ul><li>State Polygon (12,844 rows)</li>
<li>Local Government Area Polygon (2,210 rows)</li>
<li>Locality Polygon (15,782 rows)</li></ul></li></ul>

<p>The code for this article is in the TagJobSpatial repository <a href="https://bitbucket.org/tagsoftware/tagjobspatial/raw/e38c489b7f21061cec5a31ffadd60bafd470d6b2/etc/geoscape_load.sql">here</a>.</p>

<p>On my MacBook Pro M1 Max processor the load takes approximately 1 minute, and the resultant DuckDB database is about 2.5 gigabytes.</p>

<p>Tags: <a href="https://seanbarnett.id.au/tag:TagJob" class="hashtag"><span>#</span><span class="p-category">TagJob</span></a> <a href="https://seanbarnett.id.au/tag:Geospatial" class="hashtag"><span>#</span><span class="p-category">Geospatial</span></a> <a href="https://seanbarnett.id.au/tag:DuckDB" class="hashtag"><span>#</span><span class="p-category">DuckDB</span></a></p>
]]></content:encoded>
      <guid>https://seanbarnett.id.au/ingesting-geoscape-datasets</guid>
      <pubDate>Sun, 07 Jun 2026 12:17:29 +0000</pubDate>
    </item>
  </channel>
</rss>