<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hiroaki Yutani</title>
    <description>The latest articles on DEV Community by Hiroaki Yutani (@yutannihilation).</description>
    <link>https://dev.to/yutannihilation</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3536034%2F7008fdc3-3b36-41f8-af43-06e1baa4af9e.jpeg</url>
      <title>DEV Community: Hiroaki Yutani</title>
      <link>https://dev.to/yutannihilation</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yutannihilation"/>
    <language>en</language>
    <item>
      <title>How it feels to write a GPKG library in 2026 (in Rust!)</title>
      <dc:creator>Hiroaki Yutani</dc:creator>
      <pubDate>Sat, 24 Jan 2026 04:53:22 +0000</pubDate>
      <link>https://dev.to/yutannihilation/how-it-feels-to-write-a-gpkg-library-in-2026-in-rust-52mg</link>
      <guid>https://dev.to/yutannihilation/how-it-feels-to-write-a-gpkg-library-in-2026-in-rust-52mg</guid>
      <description>&lt;p&gt;There are a lot of existing libraries to deal with GeoPackage (GPKG). GDAL is the undisputed champion of the geospatial world. If your target is web browsers, there is &lt;a href="https://github.com/ngageoint/geopackage-js" rel="noopener noreferrer"&gt;GeoPackage JS&lt;/a&gt;. Rust? We have &lt;a href="https://crates.io/crates/gpkg" rel="noopener noreferrer"&gt;gpkg-rs&lt;/a&gt;! But, unfortunately, sometimes you still have to write your own one for various reasons. &lt;/p&gt;

&lt;p&gt;In my case, the main reason was WebAssembly support. I'm building a browser-side converter for Japanese geospatial data, which converts Shapefiles to GeoParquet and GeoJSON. To add GPKG to that list, I needed some Rust crate for this, but, gpkg-rs doesn't support Wasm.&lt;/p&gt;

&lt;p&gt;repository: &lt;a href="https://github.com/yutannihilation/ksj2gp" rel="noopener noreferrer"&gt;https://github.com/yutannihilation/ksj2gp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxib2kewjmi01e0s36vh0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxib2kewjmi01e0s36vh0.png" alt="ksj2gp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To be fair, it's not gpkg-rs's fault. Only last year did &lt;a href="https://github.com/rusqlite/rusqlite" rel="noopener noreferrer"&gt;rusqlite&lt;/a&gt;, the underlying crate for handling SQLite, gain Wasm support, while gpkg-rs was implemented several years ago.&lt;/p&gt;

&lt;p&gt;If I only wanted Wasm support, the easiest route would have been to fork gpkg-rs and update its rusqlite dependency. However, I had two additional ambitions that convinced me to build a new crate from the ground up: rusqlite-gpkg.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support 3D geometries by using geo-traits instead of geo-types&lt;/li&gt;
&lt;li&gt;Write spatial index&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check out the repository here:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/yutannihilation" rel="noopener noreferrer"&gt;
        yutannihilation
      &lt;/a&gt; / &lt;a href="https://github.com/yutannihilation/rusqlite-gpkg" rel="noopener noreferrer"&gt;
        rusqlite-gpkg
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      GeoPackage reader/writer built on top of rusqlite.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;rusqlite-gpkg&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/yutannihilation/rusqlite-gpkg/actions/workflows/test.yml" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0614a96ab211c95d63f95490b2afb3067b25f25bcce55e2014be1b372c7c2e7a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f797574616e6e6968696c6174696f6e2f727573716c6974652d67706b672f746573742e796d6c3f7374796c653d666c61742d737175617265266c6f676f3d676974687562" alt=""&gt;&lt;/a&gt;
&lt;a href="https://crates.io/crates/rusqlite-gpkg" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/70cd22435851244f77443aaacf4f2a764dce9cd4b560a59f7207053e5df4b11f/68747470733a2f2f696d672e736869656c64732e696f2f6372617465732f762f727573716c6974652d67706b672e7376673f7374796c653d666c61742d737175617265266c6f676f3d72757374" alt=""&gt;&lt;/a&gt;
&lt;a href="https://docs.rs/rusqlite-gpkg/latest/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2c6f9a3fe1df03e97ba4ffd0fdf4ed24e73f18fc364eb5daee5dc47ba77dbce4/68747470733a2f2f696d672e736869656c64732e696f2f646f637372732f727573716c6974652d67706b672e7376673f7374796c653d666c61742d737175617265266c6f676f3d646f6373646f747273" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GeoPackage reader/writer built on top of &lt;a href="https://crates.io/crates/rusqlite" rel="nofollow noopener noreferrer"&gt;rusqlite&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Web Demo&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A simple GitHub Pages demo is available with a button to generate
and download a &lt;code&gt;.gpkg&lt;/code&gt; file in the browser using a Web Worker + OPFS.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://yutannihilation.github.io/rusqlite-gpkg/" rel="nofollow noopener noreferrer"&gt;https://yutannihilation.github.io/rusqlite-gpkg/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;See &lt;code&gt;web/README.md&lt;/code&gt; for implementation details and design notes.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;rusqlite-gpkg&lt;/code&gt; provides a small API around the main GeoPackage concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Gpkg&lt;/code&gt; represents the whole data of GeoPackage data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GpkgLayer&lt;/code&gt; represents a single layer in the data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GpkgFeature&lt;/code&gt; represents a single feature in the layer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Value&lt;/code&gt; represents a single property value related to the feature.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apache Arrow support is available behind the &lt;code&gt;arrow&lt;/code&gt; feature flag.
You can find some example codes in the bottom of this README.&lt;/p&gt;
&lt;p&gt;The library focuses on simple, explicit flows. You control how layers are created
and which property columns are present.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Browser usage (to_bytes / from_bytes)&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Web environments often cannot access files directly (OPFS can be used by
&lt;code&gt;rusqlite&lt;/code&gt;…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yutannihilation/rusqlite-gpkg" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;Enough with the backstory. Let’s get into the heart of the matter: What does it actually feel like to write a GeoPackage library in 2026?&lt;/p&gt;

&lt;p&gt;(Disclaimer: this post discusses GPKG as a vector data format. I have not yet explored how it works as a raster data format.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Age of LLMs
&lt;/h2&gt;

&lt;p&gt;No "writing a library in 2026" post can skip mentioning AI. I actually used Codex a lot, especially for adding tests and documentation. If I were an LLM expert, I would have inserted lengthy advice here. But, it is fortunate that I'm so poor at LLM that we can skip this boring part and save some time. :)&lt;/p&gt;

&lt;h2&gt;
  
  
  GPKG Is SQLite
&lt;/h2&gt;

&lt;p&gt;Did you know GPKG is built on top of SQLite? More specifically, did you know a &lt;code&gt;.gpkg&lt;/code&gt; file is just a plain SQLite database? There is no magic. If you read &lt;a href="https://www.geopackage.org/spec140/index.html" rel="noopener noreferrer"&gt;the specification&lt;/a&gt;, you can find GPKG is designed to be "portable" in the sense that it only requires SQLite to read the data.&lt;/p&gt;

&lt;p&gt;Unlike other geospatial formats like GeoParquet, GPKS's specification doesn't define a binary file format (except for the geometry column). Instead, it defines SQLs to define the tables and triggers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvkwuu9h1e4zpv064k32.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvkwuu9h1e4zpv064k32.png" alt="SQLs described in the GPKG's specification"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are pros and cons.On the positive side, we don't need to implement a parser for the binary! We can just rely on some existing SQLite client/library. If you have implemented a parser for some binary format, you should realize how much this makes the code easier.&lt;/p&gt;

&lt;p&gt;However, this also means we need an SQLite library as a dependency. At least, I cannot imagine writing a full SQLite reader (and a writer) from scratch. This makes build harder especially in case of Rust; &lt;a href="https://github.com/tursodatabase/turso" rel="noopener noreferrer"&gt;Turso&lt;/a&gt; is a promising pure-Rust implementation of SQLite, but it's not ready at the moment. As explained above, rusqlite is ready to use. But, as it depends on libsqlite and needs to bundle it in many cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writer
&lt;/h2&gt;

&lt;p&gt;Reading a GPKG file can be done without knowing geospatial. I mean, the data needs to be interpreted by some other libraries that knows geospatial, but the reader can just extract and pass the WKB binary and/or some metadata like SRID.&lt;/p&gt;

&lt;p&gt;Writing data into a GPKG file is, however, a bit complicated. The writer cannot be ignorant about geospatial, at least at two points.&lt;/p&gt;

&lt;h3&gt;
  
  
  SRID and WKT
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;gpkg_spatial_ref_sys&lt;/code&gt; table contains the information about the CRSs used by the layers. The specification says these columns are mandatory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;srs_id&lt;/code&gt;: Unique identifier for each Spatial Reference System within a GeoPackage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;definition&lt;/code&gt;: Well-known Text Representation of the Spatial Reference System&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, at least, the writer needs to know the SRID and the WKT representation of the CRS of the data the writer is trying to write. This is not easy. In practice, this actually requires PROJ dependency if the writer want to support arbitrary SRIDs.&lt;/p&gt;

&lt;p&gt;For comparison, take an example of GeoArrow's specification; GeoArrow allows various types of CRS. This is easy for writers. Of course, on the other hand, this imposes the responsibility of interpreting various CRS representation to the reader's side. But, the reader still be able to pass the CRS representation without interpreting. I think this is better strategy.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://geoarrow.org/extension-types.html#extension-metadata" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;geoarrow.org&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;ST_&lt;/code&gt; Functions
&lt;/h3&gt;

&lt;p&gt;A GPKG file includes spatial indices built on SQLite’s R-tree extension. The table definitions contain triggers that update the corresponding index. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTOINCREMENT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="n"&gt;POINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"name"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"elevation"&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"active"&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"category"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"note"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom_insert"&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;ST_IsEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom"&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ST_MinX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ST_MaxX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;ST_MinY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ST_MaxY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom_update6"&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;NOTNULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;ST_IsEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;NOTNULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;ST_IsEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom"&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;minx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ST_MinX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;maxx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ST_MaxX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;miny&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ST_MinY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;maxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ST_MaxY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom_update7"&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;NOTNULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;ST_IsEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;ISNULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;ST_IsEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom"&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ST_MinX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ST_MaxX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;ST_MinY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ST_MaxY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom_update2"&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;OF&lt;/span&gt; &lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;ISNULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;ST_IsEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom_update5"&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;NOTNULL&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;ST_IsEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom"&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ST_MinX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ST_MaxX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;ST_MinY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ST_MaxY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom_update4"&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;ISNULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;ST_IsEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom_delete"&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"geom"&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"rtree_points_geom"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"trigger_insert_feature_count_points"&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;gpkg_ogr_contents&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;feature_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feature_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;WHERE&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'points'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="nv"&gt;"trigger_delete_feature_count_points"&lt;/span&gt; &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"points"&lt;/span&gt; &lt;span class="k"&gt;BEGIN&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;gpkg_ogr_contents&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;feature_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;feature_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;WHERE&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'points'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you scroll through the SQL definitions, you will notice several &lt;code&gt;ST_&lt;/code&gt; functions (e.g. &lt;code&gt;ST_IsEmpty&lt;/code&gt;). You might assume these functions are automatically provided by SQLite.&lt;/p&gt;

&lt;p&gt;Surprisingly, they are not. Just because they are used in the table definition does not mean they exist. We must provide the following functions ourselves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ST_IsEmpty&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ST_MinX&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ST_MinY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ST_MaxX&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ST_MaxY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means the writer needs to parse WKB and iterate over coordinates. It is not extremely difficult, but it is an extra responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing an SQLite Database Actually Writes Multiple Files
&lt;/h2&gt;

&lt;p&gt;If you have worked with a GPKG file, you may have noticed additional files such as &lt;code&gt;.gpkg-wal&lt;/code&gt; and &lt;code&gt;.gpkg-shm&lt;/code&gt; created when you open a &lt;code&gt;.gpkg&lt;/code&gt; file. This adds complexity.&lt;/p&gt;

&lt;p&gt;This even affects the API design of the SQLite library. The rusqlite's &lt;a href="https://docs.rs/rusqlite/latest/rusqlite/struct.Connection.html#method.open" rel="noopener noreferrer"&gt;&lt;code&gt;Connection::open()&lt;/code&gt;&lt;/a&gt; takes a &lt;code&gt;Path&lt;/code&gt;, and there's no version that accepts &lt;code&gt;impl std::io::Read&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;AsRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, reading and writing an SQLite database requires a filesystem, not just a file handle. This is problem in web browsers, where a normal filesystem does not exist. In theory, &lt;a href="https://github.com/Spxg/sqlite-wasm-rs" rel="noopener noreferrer"&gt;sqlite-wasm-rs&lt;/a&gt;, the crate behind rusqlite's Wasm support, should support OPFS, but enabling it was not straightforward (&lt;a href="https://docs.rs/sqlite-wasm-vfs/0.1.1/sqlite_wasm_vfs/sahpool/fn.install.html" rel="noopener noreferrer"&gt;&lt;code&gt;sqlite_wasm_vfs::sahpool::install()&lt;/code&gt;&lt;/a&gt; is async while other APIs are sync).&lt;/p&gt;

&lt;p&gt;Compared to other cloud-native formats that work as a single file, this feels like a disadvantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, How Does It Feel?
&lt;/h2&gt;

&lt;p&gt;Honestly, not very comfortable.&lt;/p&gt;

&lt;p&gt;The need for geospatial logic in the writer and the requirement for a filesystem both make GPKG harder to use in modern, browser-based environments. Compared to other cloud-native formats available in 2026, it feels less flexible.&lt;/p&gt;

&lt;p&gt;That said, GPKG still has clear strengths, especially for desktop use. And as SQLite is becoming cool again, I believe the ecosystem will improve (especially Turso)!&lt;/p&gt;

</description>
      <category>geospatial</category>
      <category>rust</category>
    </item>
    <item>
      <title>FOSS4G Auckland 2025</title>
      <dc:creator>Hiroaki Yutani</dc:creator>
      <pubDate>Sat, 29 Nov 2025 10:53:36 +0000</pubDate>
      <link>https://dev.to/mierune/foss4g-auckland-2025-4pck</link>
      <guid>https://dev.to/mierune/foss4g-auckland-2025-4pck</guid>
      <description>&lt;p&gt;Kia ora! Last week, we were in Auckland, New Zealand, for &lt;a href="https://2025.foss4g.org/" rel="noopener noreferrer"&gt;FOSS4G 2025&lt;/a&gt;. We had a great time and gave four talks about various topics from GTFS to GeoArrow. Thanks so much to the organizers for putting on such a fantastic conference, and to everyone who came to our presentations! &lt;/p&gt;

&lt;p&gt;For those who missed the talks, no worries——we share the slides in this post below. You can click the image to jump to the slides.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p3kp3uoud25cw6uaco2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6p3kp3uoud25cw6uaco2.jpg" alt="Yutani is talking about GeoArrow" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you know, the next global FOSS4G conference, FOSS4G 2026, will be held in Hiroshima, Japan (30 Aug - 5 Sep, 2026). We are looking forward to welcoming you, as part of the broader Japanese geospatial community!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadry53xehm6bly1bftjl.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadry53xehm6bly1bftjl.jpg" alt="Sheeps" width="800" height="1065"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Make It Easier to View GTFS : Building a GTFS Timetable Viewer with SvelteKit (by Xinmiao Qu)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.google.com/presentation/d/1tfH-7iYTFv27xKMA3-v8khtjFEIsFRqQmOODBf-Zils/edit?slide=id.p#slide=id.p" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6gx6hs0ajvt26xvpvze.png" alt="slides" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Longest Path Analysis in QGIS with NetworkX and Python (by Xinmiao Qu)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://drive.google.com/open?id=17XnySJizhuPrp3QNRNvRgoUz6olBha7a&amp;amp;usp=drive_copy" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1qyosjrmlerc40unme51.png" alt="slides" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive Simulation for Visualizing Bus Locations Using GTFS Data (by Kei Yamazaki)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.google.com/presentation/d/18Voi1V_rx5nHR8uZ0u8MKYKUc1Sc3W_DM9dQUzmcmho/edit?usp=sharing" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rgiz8x483kv7ww5900q.png" alt="slides" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GeoArrow on Web; Can We Live Without GeoJSON? (by Hiroaki Yutani)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://yutannihilation.github.io/foss4g-2025-slides/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpfrr9f5zfsqyztv73na.png" alt="slides" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>foss4g</category>
    </item>
  </channel>
</rss>
