<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: spatialite</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/spatialite.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2023-09-23T04:32:14+00:00</updated><author><name>Simon Willison</name></author><entry><title>TG: Polygon indexing</title><link href="https://simonwillison.net/2023/Sep/23/tg-polygon-indexing/#atom-tag" rel="alternate"/><published>2023-09-23T04:32:14+00:00</published><updated>2023-09-23T04:32:14+00:00</updated><id>https://simonwillison.net/2023/Sep/23/tg-polygon-indexing/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/tidwall/tg/blob/v0.1.0/docs/POLYGON_INDEXING.md"&gt;TG: Polygon indexing&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
TG is a brand new geospatial library by Josh Baker, author of the Tile38 in-memory spatial server (kind of a geospatial Redis). TG is written in pure C and delivered as a single C file, reminiscent of the SQLite amalgamation.&lt;/p&gt;

&lt;p&gt;TG looks really interesting. It implements almost the exact subset of geospatial functionality that I find most useful: point-in-polygon, intersect, WKT, WKB, and GeoJSON—all with no additional dependencies.&lt;/p&gt;

&lt;p&gt;The most interesting thing about it is the way it handles indexing. In this documentation Josh describes two approaches he uses to speeding up point-in-polygon and intersection using a novel approach that goes beyond the usual RTree implementation.&lt;/p&gt;

&lt;p&gt;I think this could make the basis of a really useful SQLite extension—a lighter-weight alternative to SpatiaLite.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=37619100"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/c"&gt;c&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geojson"&gt;geojson&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tg"&gt;tg&lt;/a&gt;&lt;/p&gt;



</summary><category term="c"/><category term="geospatial"/><category term="spatialite"/><category term="sqlite"/><category term="geojson"/><category term="tg"/></entry><entry><title>Weeknotes: AI hacking and a SpatiaLite tutorial</title><link href="https://simonwillison.net/2023/Jan/15/weeknotes/#atom-tag" rel="alternate"/><published>2023-01-15T19:45:39+00:00</published><updated>2023-01-15T19:45:39+00:00</updated><id>https://simonwillison.net/2023/Jan/15/weeknotes/#atom-tag</id><summary type="html">
    &lt;p&gt;Short weeknotes this time because the key things I worked on have already been covered here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://simonwillison.net/2023/Jan/13/semantic-search-answers/"&gt;How to implement Q&amp;amp;A against your documentation with GPT3, embeddings and Datasette&lt;/a&gt; described my experiment using custom SQL functions in Datasette to provide a Q&amp;amp;A interface against an existing corpus of documents. This resulted in a flurry of software releases:
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://datasette.io/plugins/datasette-openai"&gt;datasette-openai&lt;/a&gt; - custom SQL functions for querying the OpenAI APIs from Datasette&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datasette.io/plugins/datasette-faiss"&gt;datasette-faiss&lt;/a&gt; - a plugin that builds a FAISS index for fast vector similarity search&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datasette.io/plugins/datasette-cookies-for-magic-parameters"&gt;datasette-cookies-for-magic-parameters&lt;/a&gt; - a supporting plugin that helps store secrets such as API keys in cookies so they won't be transmitted in query strings&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datasette.io/tools/openai-to-sqlite"&gt;openai-to-sqlite&lt;/a&gt; - a command-line tool for storing OpenAI API responses in a SQLite database - useful for calculating embeddings&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://simonwillison.net/2023/Jan/9/datasette-064/"&gt;Datasette 0.64, with a warning about SpatiaLite&lt;/a&gt; - I released Datasette 0.64 with a new &lt;code&gt;--setting default_allow_sql off&lt;/code&gt; setting, due to the discovery that some of the SQL functions added by the SpatiaLite module can be used to crash the Datasette instance.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datasette.io/tutorials/spatialite"&gt;Building a location to time zone API with SpatiaLite&lt;/a&gt; is the latest in the official series of tutorials for Datasette. It demonstrates how SpatiaLite can be used to safely build and deploy a geospatial API.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Releases this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-openai"&gt;datasette-openai&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-openai/releases/tag/0.2"&gt;0.2&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-openai/releases"&gt;4 releases total&lt;/a&gt;) - 2023-01-14
&lt;br /&gt;SQL functions for calling OpenAI APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/openai-to-sqlite"&gt;openai-to-sqlite&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/openai-to-sqlite/releases/tag/0.2"&gt;0.2&lt;/a&gt; - (&lt;a href="https://github.com/simonw/openai-to-sqlite/releases"&gt;2 releases total&lt;/a&gt;) - 2023-01-13
&lt;br /&gt;Save OpenAI API results to a SQLite database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-cookies-for-magic-parameters"&gt;datasette-cookies-for-magic-parameters&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-cookies-for-magic-parameters/releases/tag/0.1.2"&gt;0.1.2&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-cookies-for-magic-parameters/releases"&gt;3 releases total&lt;/a&gt;) - 2023-01-12
&lt;br /&gt;UI for setting cookies to populate magic parameters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/git-history"&gt;git-history&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/git-history/releases/tag/0.7a0"&gt;0.7a0&lt;/a&gt; - (&lt;a href="https://github.com/simonw/git-history/releases"&gt;10 releases total&lt;/a&gt;) - 2023-01-11
&lt;br /&gt;Tools for analyzing Git history using SQLite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette"&gt;datasette&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette/releases/tag/0.64.1"&gt;0.64.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette/releases"&gt;124 releases total&lt;/a&gt;) - 2023-01-11
&lt;br /&gt;An open source multi-tool for exploring and publishing data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-faiss"&gt;datasette-faiss&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-faiss/releases/tag/0.1a0"&gt;0.1a0&lt;/a&gt; - 2023-01-11
&lt;br /&gt;Maintain a FAISS index for specified Datasette tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/json-to-files"&gt;json-to-files&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/json-to-files/releases/tag/0.1"&gt;0.1&lt;/a&gt; - 2023-01-10
&lt;br /&gt;Create separate files on disk based on a JSON object&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-auth-passwords"&gt;datasette-auth-passwords&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-auth-passwords/releases/tag/1.1"&gt;1.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-auth-passwords/releases"&gt;12 releases total&lt;/a&gt;) - 2023-01-09
&lt;br /&gt;Datasette plugin for authentication using passwords&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-publish-fly"&gt;datasette-publish-fly&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-publish-fly/releases/tag/1.3"&gt;1.3&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-publish-fly/releases"&gt;9 releases total&lt;/a&gt;) - 2023-01-09
&lt;br /&gt;Datasette plugin for publishing data using Fly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/shapefile-to-sqlite"&gt;shapefile-to-sqlite&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/shapefile-to-sqlite/releases/tag/0.4.2"&gt;0.4.2&lt;/a&gt; - (&lt;a href="https://github.com/simonw/shapefile-to-sqlite/releases"&gt;6 releases total&lt;/a&gt;) - 2023-01-08
&lt;br /&gt;Load shapefiles into a SQLite (optionally SpatiaLite) database&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/clickhouse/github-explorer"&gt;Querying the GitHub archive with the ClickHouse playground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/cooking/breakfast-tacos"&gt;Breakfast tacos with eggs, vegetarian sausages and avocado sauce&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/sqlite/geopoly"&gt;Geopoly in SQLite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/sqlite/sqlite-extensions-python-macos"&gt;Loading SQLite extensions in Python on macOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/shot-scraper/scraping-flourish"&gt;Scraping the Sky News Westminster Accounts, a Flourish application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/python/pipx-alpha"&gt;Upgrading a pipx application to an alpha version&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gpt-3"&gt;gpt-3&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openai"&gt;openai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/vector-search"&gt;vector-search&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="spatialite"/><category term="ai"/><category term="datasette"/><category term="weeknotes"/><category term="gpt-3"/><category term="openai"/><category term="generative-ai"/><category term="vector-search"/><category term="llms"/></entry><entry><title>Datasette 0.64, with a warning about SpatiaLite</title><link href="https://simonwillison.net/2023/Jan/9/datasette-064/#atom-tag" rel="alternate"/><published>2023-01-09T21:22:51+00:00</published><updated>2023-01-09T21:22:51+00:00</updated><id>https://simonwillison.net/2023/Jan/9/datasette-064/#atom-tag</id><summary type="html">
    &lt;p&gt;I release Datasette 0.64 this morning. This release is mainly a response to the realization that it's not safe to run Datasette with the SpatiaLite extension loaded if that Datasette instance is configured to enable arbitrary SQL queries from untrusted users.&lt;/p&gt;
&lt;p&gt;Here are &lt;a href="https://docs.datasette.io/en/stable/changelog.html#v0-64"&gt;the release notes&lt;/a&gt; quoted in full:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Datasette now &lt;strong&gt;strongly recommends against allowing arbitrary SQL queries if you are using SpatiaLite&lt;/strong&gt;. SpatiaLite includes SQL functions that could cause the Datasette server to crash. See &lt;a href="https://docs.datasette.io/en/stable/spatialite.html#spatialite"&gt;SpatiaLite&lt;/a&gt; for more details.&lt;/li&gt;
&lt;li&gt;New &lt;a href="https://docs.datasette.io/en/stable/settings.html#setting-default-allow-sql"&gt;default_allow_sql&lt;/a&gt; setting, providing an easier way to disable all arbitrary SQL execution by end users: &lt;code&gt;datasette --setting default_allow_sql off&lt;/code&gt;. See also &lt;a href="https://docs.datasette.io/en/stable/authentication.html#authentication-permissions-execute-sql"&gt;Controlling the ability to execute arbitrary SQL&lt;/a&gt;. (&lt;a href="https://github.com/simonw/datasette/issues/1409"&gt;#1409&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://datasette.io/tutorials/spatialite"&gt;Building a location to time zone API with SpatiaLite&lt;/a&gt; is a new Datasette tutorial showing how to safely use SpatiaLite to create a location to time zone API.&lt;/li&gt;
&lt;li&gt;New documentation about &lt;a href="https://docs.datasette.io/en/stable/installation.html#installation-extensions"&gt;how to debug problems loading SQLite extensions&lt;/a&gt;. The error message shown when an extension cannot be loaded has also been improved. (&lt;a href="https://github.com/simonw/datasette/issues/1979"&gt;#1979&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Fixed an accessibility issue: the &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; elements in the table filter form now show an outline when they are currently focused. (&lt;a href="https://github.com/simonw/datasette/issues/1771"&gt;#1771&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;The problem with SpatiaLite&lt;/h4&gt;
&lt;p&gt;Datasette allows arbitrary SQL execution &lt;a href="https://docs.datasette.io/en/stable/sql_queries.html"&gt;as a core feature&lt;/a&gt;. It takes a bunch of steps to provide this safely: database connections are opened in read-only mode, it imposes a strict time limit on SQL queries and Datasette is designed to be run in containers for a further layer of protection.&lt;/p&gt;
&lt;p&gt;SQLite itself is an excellent platform for this feature: it has a set of default functionality that supports this well, protected by a &lt;a href="https://www.sqlite.org/testing.html"&gt;legendarily thorough test suite&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.gaia-gis.it/fossil/libspatialite/index"&gt;SpatiaLite&lt;/a&gt; is a long running third-party extension for SQLite that adds a &lt;a href="https://www.gaia-gis.it/gaia-sins/spatialite-sql-5.0.1.html"&gt;bewildering array&lt;/a&gt; of additional functionality to SQLite - much of it around GIS, but with a whole host of extras as well. It includes debugging routines, XML parsers and even it's own implementation of stored procedures!&lt;/p&gt;
&lt;p&gt;Unfortunately, not all of this functionality is safe to expose to untrusted queries - even for databases that have been opened in read-only mode.&lt;/p&gt;
&lt;p&gt;After identifying functions which could crash the Datasette instance, I decided that Datasette should make a strong recommendation not to expose SpatiaLite in an unprotected manner.&lt;/p&gt;
&lt;p&gt;In addition to the new documentation, I also added a feature I've been planning for a while: a &lt;a href="https://docs.datasette.io/en/stable/settings.html#setting-default-allow-sql"&gt;simple setting&lt;/a&gt; for disabling arbitrary SQL queries entirely:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette --setting default_allow_sql off
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prior to 0.64 you could achieve the same thing by adding the following line to your &lt;a href="https://docs.datasette.io/en/stable/metadata.html"&gt;metadata.json&lt;/a&gt; file:&lt;/p&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
    &lt;span class="pl-ent"&gt;"allow_sql"&lt;/span&gt;: &lt;span class="pl-c1"&gt;false&lt;/span&gt;
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or in &lt;code&gt;metadata.yml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-yaml"&gt;&lt;pre&gt;&lt;span class="pl-ent"&gt;allow_sql&lt;/span&gt;: &lt;span class="pl-c1"&gt;false&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The new setting achieves the same thing, but is more obvious and can be easily applied even for Datasette instances that don't use metadata.&lt;/p&gt;
&lt;h4&gt;A new SpatiaLite tutorial&lt;/h4&gt;
&lt;p&gt;The documentation now recommends running SpatiaLite instances with pre-approved SQL implemented using Datasette's canned queries feature.&lt;/p&gt;
&lt;p&gt;To help clarify how this works, I decided to publish a new entry in the official series of &lt;a href="https://datasette.io/tutorials"&gt;Datasette tutorials&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://datasette.io/tutorials/spatialite"&gt;Building a location to time zone API with SpatiaLite&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is an updated version of &lt;a href="https://simonwillison.net/2017/Dec/12/location-time-zone-api/"&gt;a tutorial&lt;/a&gt; I first wrote back in 2017.&lt;/p&gt;
&lt;p&gt;The new tutorial now includes material on Chris Amico's &lt;a href="https://datasette.io/plugins/datasette-geojson-map"&gt;datasette-geojson-map&lt;/a&gt; plugin, SpatiaLite point-in-polygon queries, polygon intersection queries, spatial indexes and how to use the &lt;code&gt;simplify()&lt;/code&gt; function to reduce huge polygons down to a size that is more practical to display on a map.&lt;/p&gt;
&lt;p&gt;I'm really happy with this new tutorial. Not only does it show a safe way to run SpatiaLite, but it also illustrates a powerful pattern for using Datasette to create and deploy custom APIs.&lt;/p&gt;
&lt;p&gt;The resulting API can be accessed here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://timezones.datasette.io/timezones"&gt;https://timezones.datasette.io/timezones&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It's hosted on &lt;a href="https://fly.io/"&gt;Fly&lt;/a&gt;, using their $1.94/month instance size with 256MB of RAM - easily powerful enough to host this class of application.&lt;/p&gt;
&lt;p&gt;I also updated the &lt;a href="https://datasette.io/plugins/datasette-publish-fly"&gt;datasette-publish-fly&lt;/a&gt; plugin to make it easier to deploy instances with SQL execution disabled, see the &lt;a href="https://github.com/simonw/datasette-publish-fly/releases/tag/1.3"&gt;1.3 release notes&lt;/a&gt;.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/annotated-release-notes"&gt;annotated-release-notes&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="security"/><category term="spatialite"/><category term="datasette"/><category term="annotated-release-notes"/></entry><entry><title>Spatialite Speed Test</title><link href="https://simonwillison.net/2021/Apr/4/spatialite-speed-test/#atom-tag" rel="alternate"/><published>2021-04-04T16:28:42+00:00</published><updated>2021-04-04T16:28:42+00:00</updated><id>https://simonwillison.net/2021/Apr/4/spatialite-speed-test/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://northredoubt.com/n/2012/01/20/spatialite-speed-test/"&gt;Spatialite Speed Test&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Part of an excellent series of posts about SpatiaLite from 2012—here John C. Zastrow reports on running polygon intersection queries against a 1.9GB database file in 40 seconds without an index and 0.186 seconds using the SpatialIndex virtual table mechanism.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;&lt;/p&gt;



</summary><category term="geospatial"/><category term="spatialite"/><category term="sqlite"/></entry><entry><title>Drawing shapes on a map to query a SpatiaLite database (and other weeknotes)</title><link href="https://simonwillison.net/2021/Jan/24/drawing-shapes-spatialite/#atom-tag" rel="alternate"/><published>2021-01-24T06:50:29+00:00</published><updated>2021-01-24T06:50:29+00:00</updated><id>https://simonwillison.net/2021/Jan/24/drawing-shapes-spatialite/#atom-tag</id><summary type="html">
    &lt;p&gt;This week I built a Datasette plugin that lets you query a database by drawing shapes on a map!&lt;/p&gt;
&lt;h4&gt;datasette-leaflet-freedraw&lt;/h4&gt;
&lt;p&gt;SpatiaLite is a SQLite extension that adds &lt;a href="http://www.gaia-gis.it/gaia-sins/spatialite-sql-4.3.0.html"&gt;a plethora&lt;/a&gt; of geospatial querying features. Datasette &lt;a href="https://docs.datasette.io/en/stable/spatialite.html"&gt;works well with SpatiaLite&lt;/a&gt; already, but every now and then I dip in to a geospatial project to see if there are any neat ways I can extend Datasette's spatial querying support.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://datasette.io/plugins/datasette-leaflet-freedraw"&gt;datasette-leaflet-freedraw&lt;/a&gt; is my new plugin which brings the excellent &lt;a href="https://github.com/Wildhoney/Leaflet.FreeDraw"&gt;FreeDraw&lt;/a&gt; Leaflet module to Datasette.&lt;/p&gt;
&lt;p&gt;It's a pure-JavaScript plugin which looks for Datasette SQL parameters called &lt;code&gt;:freedraw&lt;/code&gt; and enhances them with an interactive map. You can then draw on the map to populate the form field with a GeoJSON MultiPolygon representing the shape that you drew. Submit that to the server to excute it as part of a query.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A screenshot showing the plugin with a shape drawn around San Francisco" src="https://static.simonwillison.net/static/2021/datasette-leaflet-freedraw.png" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;This is best illustrated with a demo. Here's a SQL query that let you draw a shape to find California protected areas (parks, nature reserves and suchlike) that intersect with the polygon that you draw:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;select&lt;/span&gt;
  AsGeoJSON(geometry), &lt;span class="pl-k"&gt;*&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt;
  CPAD_2020a_SuperUnits
&lt;span class="pl-k"&gt;where&lt;/span&gt;
  Intersects(GeomFromGeoJSON(:freedraw), geometry) &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;1&lt;/span&gt;
  &lt;span class="pl-k"&gt;and&lt;/span&gt; &lt;span class="pl-c1"&gt;CPAD_2020a_SuperUnits&lt;/span&gt;.&lt;span class="pl-c1"&gt;rowid&lt;/span&gt; &lt;span class="pl-k"&gt;in&lt;/span&gt; (
    &lt;span class="pl-k"&gt;select&lt;/span&gt;
      rowid
    &lt;span class="pl-k"&gt;from&lt;/span&gt;
      SpatialIndex
    &lt;span class="pl-k"&gt;where&lt;/span&gt;
      f_table_name &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;CPAD_2020a_SuperUnits&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
      &lt;span class="pl-k"&gt;and&lt;/span&gt; search_frame &lt;span class="pl-k"&gt;=&lt;/span&gt; GeomFromGeoJSON(:freedraw)
  )&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This uses the &lt;a href="https://www.calands.org/cpad/"&gt;California Protected Areas Database&lt;/a&gt; by GreenInfo Network, &lt;a href="https://simonwillison.net/2020/Aug/28/weeknotes-cpad/"&gt;discussed here previously&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://calands.datasettes.com/calands/draw_to_find?freedraw=%7B%22type%22%3A%22MultiPolygon%22%2C%22coordinates%22%3A%5B%5B%5B%5B-122.40142822265626%2C37.810868914073005%5D%2C%5B-122.38838195800783%2C37.807071480609274%5D%2C%5B-122.37121582031251%2C37.79784832917947%5D%2C%5B-122.35885620117189%2C37.794592824285104%5D%2C%5B-122.35679626464845%2C37.77722770873696%5D%2C%5B-122.35061645507814%2C37.76040136229719%5D%2C%5B-122.34786987304688%2C37.74574303801687%5D%2C%5B-122.34786987304688%2C37.73379707124429%5D%2C%5B-122.35061645507814%2C37.72456477660484%5D%2C%5B-122.35542297363283%2C37.72021976910832%5D%2C%5B-122.3870086669922%2C37.703923721150886%5D%2C%5B-122.40966796875001%2C37.69957750311587%5D%2C%5B-122.43576049804689%2C37.697404298539745%5D%2C%5B-122.47421264648439%2C37.69849090879089%5D%2C%5B-122.50099182128908%2C37.70229391925025%5D%2C%5B-122.50991821289064%2C37.7071832174446%5D%2C%5B-122.51678466796876%2C37.714244967649265%5D%2C%5B-122.51953125000001%2C37.72510788462094%5D%2C%5B-122.52639770507814%2C37.743571187449064%5D%2C%5B-122.52571105957033%2C37.77071473849611%5D%2C%5B-122.51953125000001%2C37.78211205989562%5D%2C%5B-122.5078582763672%2C37.795678008523424%5D%2C%5B-122.49549865722658%2C37.80490143094975%5D%2C%5B-122.47901916503908%2C37.811953859192705%5D%2C%5B-122.46322631835939%2C37.814123701604466%5D%2C%5B-122.40142822265626%2C37.810868914073005%5D%5D%5D%5D%7D"&gt;Try the SQL query here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(That &lt;code&gt;from SpatialIndex&lt;/code&gt; sub-select at the end is a pattern for taking advantage of SpatiaLite spatial indexes.)&lt;/p&gt;
&lt;p&gt;The plugin itself is pretty simple: it scans for &lt;code&gt;freedraw&lt;/code&gt; input fields and enhances them with a map interface that writes GeoJSON back to the form field.&lt;/p&gt;
&lt;p&gt;You can then use SpatiaLite functions such as &lt;code&gt;GeomFromGeoJSON()&lt;/code&gt; to transform that user input into something that can be used in a query.&lt;/p&gt;
&lt;p&gt;Another fun query: let's add &lt;code&gt;WHERE PARK_NAME like '%mini%'&lt;/code&gt; to the query to filter for just the "mini parks" in San Francisco:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;select&lt;/span&gt;
  AsGeoJSON(geometry), &lt;span class="pl-k"&gt;*&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt;
  CPAD_2020a_SuperUnits
&lt;span class="pl-k"&gt;where&lt;/span&gt;
  PARK_NAME &lt;span class="pl-k"&gt;like&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;%mini%&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-k"&gt;and&lt;/span&gt;
  Intersects(GeomFromGeoJSON(:freedraw), geometry) &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;1&lt;/span&gt;
  &lt;span class="pl-k"&gt;and&lt;/span&gt; &lt;span class="pl-c1"&gt;CPAD_2020a_SuperUnits&lt;/span&gt;.&lt;span class="pl-c1"&gt;rowid&lt;/span&gt; &lt;span class="pl-k"&gt;in&lt;/span&gt; (
    &lt;span class="pl-k"&gt;select&lt;/span&gt;
      rowid
    &lt;span class="pl-k"&gt;from&lt;/span&gt;
      SpatialIndex
    &lt;span class="pl-k"&gt;where&lt;/span&gt;
      f_table_name &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;CPAD_2020a_SuperUnits&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
      &lt;span class="pl-k"&gt;and&lt;/span&gt; search_frame &lt;span class="pl-k"&gt;=&lt;/span&gt; GeomFromGeoJSON(:freedraw)
  )&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here are the mini parks &lt;a href="https://calands.datasettes.com/calands/draw_to_find_mini_parks?freedraw=%7B%22type%22%3A%22MultiPolygon%22%2C%22coordinates%22%3A%5B%5B%5B%5B-122.42134094238283%2C37.82009043941308%5D%2C%5B-122.39662170410158%2C37.814123701604466%5D%2C%5B-122.3925018310547%2C37.811953859192705%5D%2C%5B-122.36572265625001%2C37.78645343442073%5D%2C%5B-122.35473632812501%2C37.77017196507861%5D%2C%5B-122.34512329101564%2C37.74954362315467%5D%2C%5B-122.34512329101564%2C37.72836644908416%5D%2C%5B-122.34855651855469%2C37.72021976910832%5D%2C%5B-122.36640930175783%2C37.71044257039148%5D%2C%5B-122.3815155029297%2C37.70772645289051%5D%2C%5B-122.42134094238283%2C37.709899354855125%5D%2C%5B-122.45429992675781%2C37.70609673460725%5D%2C%5B-122.49824523925783%2C37.708269684354526%5D%2C%5B-122.51953125000001%2C37.71315858834301%5D%2C%5B-122.52296447753908%2C37.71533133102705%5D%2C%5B-122.53395080566408%2C37.73108180994824%5D%2C%5B-122.53463745117189%2C37.751172385606196%5D%2C%5B-122.53120422363283%2C37.763115548102924%5D%2C%5B-122.51472473144533%2C37.78808138412046%5D%2C%5B-122.49412536621095%2C37.80761398306056%5D%2C%5B-122.47489929199219%2C37.81792077237497%5D%2C%5B-122.45910644531251%2C37.81737834565083%5D%2C%5B-122.44331359863283%2C37.82171764783966%5D%2C%5B-122.42134094238283%2C37.82009043941308%5D%5D%5D%5D%7D"&gt;for San Francisco&lt;/a&gt; and &lt;a href="https://calands.datasettes.com/calands/draw_to_find_mini_parks?freedraw=%7B%22type%22%3A%22MultiPolygon%22%2C%22coordinates%22%3A%5B%5B%5B%5B-122.20916748046876%2C37.85045908105496%5D%2C%5B-122.19509124755861%2C37.84612146910074%5D%2C%5B-122.19028472900392%2C37.83663205340172%5D%2C%5B-122.18994140625001%2C37.83120898199491%5D%2C%5B-122.18444824218751%2C37.819548028632376%5D%2C%5B-122.18273162841798%2C37.804358908571395%5D%2C%5B-122.17826843261719%2C37.78536811469731%5D%2C%5B-122.17998504638673%2C37.77397129533325%5D%2C%5B-122.18410491943361%2C37.769629187677%5D%2C%5B-122.19200134277345%2C37.76474401178006%5D%2C%5B-122.2053909301758%2C37.76447260365713%5D%2C%5B-122.21740722656251%2C37.76582963431185%5D%2C%5B-122.22839355468751%2C37.7690864062913%5D%2C%5B-122.23972320556642%2C37.77641361883315%5D%2C%5B-122.24899291992189%2C37.78075532805877%5D%2C%5B-122.26650238037111%2C37.78998061344339%5D%2C%5B-122.27851867675783%2C37.791337175930714%5D%2C%5B-122.28572845458986%2C37.791337175930714%5D%2C%5B-122.29122161865236%2C37.791879793952084%5D%2C%5B-122.30083465576173%2C37.794050226188425%5D%2C%5B-122.3056411743164%2C37.79649188624147%5D%2C%5B-122.31250762939455%2C37.801103690609615%5D%2C%5B-122.31903076171876%2C37.81303878836991%5D%2C%5B-122.31491088867189%2C37.82253123860035%5D%2C%5B-122.31044769287111%2C37.827412594823755%5D%2C%5B-122.30598449707031%2C37.82985315185641%5D%2C%5B-122.28126525878908%2C37.83446287267591%5D%2C%5B-122.2713088989258%2C37.84042796619096%5D%2C%5B-122.25036621093751%2C37.842868092687425%5D%2C%5B-122.22839355468751%2C37.85073017332982%5D%2C%5B-122.22564697265626%2C37.85100126460795%5D%2C%5B-122.20916748046876%2C37.85045908105496%5D%5D%5D%5D%7D"&gt;for Oakland&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This demo uses the user's shapes as part of a &lt;code&gt;SELECT&lt;/code&gt; query, but it's also possible to use Datasette's &lt;a href="https://docs.datasette.io/en/stable/sql_queries.html#writable-canned-queries"&gt;Writable canned queries&lt;/a&gt; to create forms that will save the shapes to a database using an &lt;code&gt;INSERT&lt;/code&gt; or &lt;code&gt;UPDATE&lt;/code&gt; query.&lt;/p&gt;
&lt;p&gt;If you want to try this plugin out note that it currently requires the &lt;a href="https://github.com/simonw/datasette/releases/tag/0.54a0"&gt;Datasette 0.54a alpha&lt;/a&gt;. This is because it takes advantage of the new &lt;code&gt;&amp;lt;script type="module"&amp;gt;&lt;/code&gt; support I &lt;a href="https://simonwillison.net/2021/Jan/17/weeknotes-still-pretty-distracted/#script-type-module"&gt;added last week&lt;/a&gt;. &lt;strong&gt;Update 25th January 2021:&lt;/strong&gt; Datasette 0.54 has now &lt;a href="https://simonwillison.net/2021/Jan/25/datasette/"&gt;been released&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Other releases this week&lt;/h4&gt;
&lt;p&gt;I recorded my talk for &lt;a href="https://research-fosdem.github.io/"&gt;FOSDEM&lt;/a&gt; - and in doing so I broke my unproductive streak and pushed out releases for a whole bunch of Datasette projects - partly through fixing bugs I spotted while trying to record the talk!&lt;/p&gt;
&lt;p&gt;Here's the full list, with some annotations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-auth-github"&gt;datasette-auth-github&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-auth-github/releases/tag/0.13"&gt;0.13&lt;/a&gt; - 2021-01-24
&lt;br /&gt;Datasette plugin that authenticates users against GitHub. This is a big release: I finally finished upgrading the plugin to work against Datasette's new-ish &lt;a href="https://docs.datasette.io/en/stable/authentication.html"&gt;authentication and permissions&lt;/a&gt; mechanism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-leaflet-freedraw"&gt;datasette-leaflet-freedraw&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-leaflet-freedraw/releases/tag/0.1.4"&gt;0.1.4&lt;/a&gt; -  2021-01-24
&lt;br /&gt;Draw polygons on maps in Datasette. See above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-cluster-map"&gt;datasette-cluster-map&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-cluster-map/releases/tag/0.16"&gt;0.16&lt;/a&gt; - 2021-01-23
&lt;br /&gt;Datasette plugin that shows a map for any data with latitude/longitude columns. Fixed some bugs related to the display of marker popups that included foreign key references to other tables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/asgi-csrf"&gt;asgi-csrf&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/asgi-csrf/releases/tag/0.8"&gt;0.8&lt;/a&gt; - 2021-01-23
&lt;br /&gt;ASGI middleware for protecting against CSRF attacks. I fixed a broken test caused by my use of an undocumented and unstable API in &lt;code&gt;httpx&lt;/code&gt; - &lt;a href="https://github.com/simonw/asgi-csrf/issues/18"&gt;details here&lt;/a&gt;. I also added a new &lt;code&gt;always_set_cookie=True&lt;/code&gt; option for cases where you always want to ensure a CSRF cookie has been set, see &lt;a href="https://github.com/simonw/asgi-csrf/issues/16"&gt;#16&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-leaflet-geojson"&gt;datasette-leaflet-geojson&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-leaflet-geojson/releases/tag/0.7"&gt;0.7&lt;/a&gt; - 2021-01-20
&lt;br /&gt;Datasette plugin that replaces any GeoJSON column values with a Leaflet map. I fixed a long-standing point of concern: this plugin renders a map for every row on a page containing GeoJSON, which can potentially mean hundreds of maps for queries that return may results. The plugin now renders just the first ten maps and provides a grey clickable "Click to show map" widget for anything past the first ten.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette"&gt;datasette&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette/releases/tag/0.54a0"&gt;0.54a0&lt;/a&gt; - 2021-01-19
&lt;br /&gt;Lots of stuff in this one, see &lt;a href="https://github.com/simonw/datasette/releases/tag/0.54a0"&gt;the release notes&lt;/a&gt;. I pushed out an alpha so I could start using the JavaScript modules stuff in &lt;code&gt;datasette-leaflet-freedraw&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/sqlite-transform"&gt;sqlite-transform&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/sqlite-transform/releases/tag/0.3.1"&gt;0.3.1&lt;/a&gt; - 2021-01-18
&lt;br /&gt;Tool for running transformations on columns in a SQLite database. I improved the &lt;code&gt;--help&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/dogsheep/swarm-to-sqlite"&gt;swarm-to-sqlite&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/dogsheep/swarm-to-sqlite/releases/tag/0.3.3"&gt;0.3.3&lt;/a&gt; - 2021-01-18
&lt;br /&gt;Create a SQLite database containing your checkin history from Foursquare Swarm. I fixed a bug caused by new fields showing up in the Swarm JSON output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/sqlite-utils/releases/tag/3.3"&gt;3.3&lt;/a&gt; - 2021-01-18
&lt;br /&gt;Python CLI utility and library for manipulating SQLite databases. I added the &lt;code&gt;alter=True&lt;/code&gt; argument to the &lt;code&gt;.m2m()&lt;/code&gt; method, which I needed to fix the bug in &lt;code&gt;swarm-to-sqlite&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/til/til/github_bulk-repo-github-graphql.md"&gt;Bulk fetching repository details with the GitHub GraphQL API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/til/til/sqlite_fixing-column-encoding-with-ftfy-and-sqlite-transform.md"&gt;Fixing broken text encodings with sqlite-transform and ftfy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/til/til/github_syntax-highlighting-python-console.md"&gt;Syntax highlighting Python console examples with GFM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/til/til/jupyter_javascript-in-a-jupyter-notebook.md"&gt;Embedding JavaScript in a Jupyter notebook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/leaflet"&gt;leaflet&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="geospatial"/><category term="projects"/><category term="spatialite"/><category term="sqlite"/><category term="datasette"/><category term="weeknotes"/><category term="leaflet"/></entry><entry><title>California Protected Areas Database in Datasette</title><link href="https://simonwillison.net/2020/Aug/21/california-protected-areas-database-datasette/#atom-tag" rel="alternate"/><published>2020-08-21T23:15:58+00:00</published><updated>2020-08-21T23:15:58+00:00</updated><id>https://simonwillison.net/2020/Aug/21/california-protected-areas-database-datasette/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://calands.datasettes.com/"&gt;California Protected Areas Database in Datasette&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I built this yesterday: it’s a Datasette interface on top of the CPAD 2020 GIS database of protected areas in California maintained by GreenInfo Network. This was a useful excuse to build a GitHub Actions flow that builds a SpatiaLite database using my shapefile-to-sqlite tool, and I fixed a few bugs in my datasette-leaflet-geojson plugin as well.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://github.com/simonw/calands-datasette"&gt;calands-datasette on GitHub&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/shapefiles"&gt;shapefiles&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/california"&gt;california&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-actions"&gt;github-actions&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/leaflet"&gt;leaflet&lt;/a&gt;&lt;/p&gt;



</summary><category term="geospatial"/><category term="projects"/><category term="shapefiles"/><category term="spatialite"/><category term="california"/><category term="datasette"/><category term="github-actions"/><category term="leaflet"/></entry><entry><title>Things I learned about shapefiles building shapefile-to-sqlite</title><link href="https://simonwillison.net/2020/Feb/19/shapefile-to-sqlite/#atom-tag" rel="alternate"/><published>2020-02-19T05:25:58+00:00</published><updated>2020-02-19T05:25:58+00:00</updated><id>https://simonwillison.net/2020/Feb/19/shapefile-to-sqlite/#atom-tag</id><summary type="html">
    &lt;p&gt;The latest in my series of &lt;a href="https://datasette.readthedocs.io/en/latest/ecosystem.html#tools-for-creating-sqlite-databases"&gt;x-to-sqlite tools&lt;/a&gt; is &lt;a href="https://github.com/simonw/shapefile-to-sqlite"&gt;shapefile-to-sqlite&lt;/a&gt;. I learned a whole bunch of things about the ESRI shapefile format while building it.&lt;/p&gt;
&lt;p&gt;Governments really love ESRI shapefiles. There is a huge amount of interesting geospatial data made available in the format - &lt;a href="https://catalog.data.gov/dataset?res_format=SHP"&gt;4,614 on Data.gov&lt;/a&gt;!&lt;/p&gt;
&lt;h3 id="shapefile-to-sqlite"&gt;shapefile-to-sqlite&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;shapefile-to-sqlite&lt;/code&gt; loads the data from these files into a SQLite database, turning geometry properties into database columns and the geometry itself into a blob of GeoJSON. Let&amp;#39;s try it out on a shapefile containing the &lt;a href="https://catalog.data.gov/dataset/national-parks"&gt;boundaries of US national parks&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ wget http:&lt;span class="hljs-comment"&gt;//nrdata.nps.gov/programs/lands/nps_boundary.zip&lt;/span&gt;
...
Saving to: ‘nps_boundary.zip’
nps_boundary.zip                           &lt;span class="hljs-number"&gt;100&lt;/span&gt;%[=====================================================================================&amp;gt;]  &lt;span class="hljs-number"&gt;12.61&lt;/span&gt;M   &lt;span class="hljs-number"&gt;705&lt;/span&gt;KB/s    &lt;span class="hljs-keyword"&gt;in&lt;/span&gt; &lt;span class="hljs-number"&gt;22&lt;/span&gt;s     
&lt;span class="hljs-number"&gt;2020&lt;/span&gt;&lt;span class="hljs-number"&gt;-02&lt;/span&gt;&lt;span class="hljs-number"&gt;-18&lt;/span&gt; &lt;span class="hljs-number"&gt;19&lt;/span&gt;:&lt;span class="hljs-number"&gt;59&lt;/span&gt;:&lt;span class="hljs-number"&gt;22&lt;/span&gt; (&lt;span class="hljs-number"&gt;597&lt;/span&gt; KB/s) - ‘nps_boundary.zip’ saved [&lt;span class="hljs-number"&gt;13227561&lt;/span&gt;/&lt;span class="hljs-number"&gt;13227561&lt;/span&gt;]

$ unzip nps_boundary.zip 
Archive:  nps_boundary.zip
inflating: temp/Current_Shapes/Data_Store/&lt;span class="hljs-number"&gt;06&lt;/span&gt;&lt;span class="hljs-number"&gt;-06&lt;/span&gt;&lt;span class="hljs-number"&gt;-12&lt;/span&gt;_Posting/nps_boundary.xml  
inflating: temp/Current_Shapes/Data_Store/&lt;span class="hljs-number"&gt;06&lt;/span&gt;&lt;span class="hljs-number"&gt;-06&lt;/span&gt;&lt;span class="hljs-number"&gt;-12&lt;/span&gt;_Posting/nps_boundary.dbf  
inflating: temp/Current_Shapes/Data_Store/&lt;span class="hljs-number"&gt;06&lt;/span&gt;&lt;span class="hljs-number"&gt;-06&lt;/span&gt;&lt;span class="hljs-number"&gt;-12&lt;/span&gt;_Posting/nps_boundary.prj  
inflating: temp/Current_Shapes/Data_Store/&lt;span class="hljs-number"&gt;06&lt;/span&gt;&lt;span class="hljs-number"&gt;-06&lt;/span&gt;&lt;span class="hljs-number"&gt;-12&lt;/span&gt;_Posting/nps_boundary.shp  
inflating: temp/Current_Shapes/Data_Store/&lt;span class="hljs-number"&gt;06&lt;/span&gt;&lt;span class="hljs-number"&gt;-06&lt;/span&gt;&lt;span class="hljs-number"&gt;-12&lt;/span&gt;_Posting/nps_boundary.shx

$ shapefile-to-sqlite nps.db temp/Current_Shapes/Data_Store/&lt;span class="hljs-number"&gt;06&lt;/span&gt;&lt;span class="hljs-number"&gt;-06&lt;/span&gt;&lt;span class="hljs-number"&gt;-12&lt;/span&gt;_Posting/nps_boundary.shp
temp/Current_Shapes/Data_Store/&lt;span class="hljs-number"&gt;06&lt;/span&gt;&lt;span class="hljs-number"&gt;-06&lt;/span&gt;&lt;span class="hljs-number"&gt;-12&lt;/span&gt;_Posting/nps_boundary.shp
[####################################]  &lt;span class="hljs-number"&gt;100&lt;/span&gt;%

$ datasette nps.db
Serve! files=(&lt;span class="hljs-string"&gt;'nps.db'&lt;/span&gt;,) (immutables=()) on port &lt;span class="hljs-number"&gt;8003&lt;/span&gt;
INFO:     Started server process [&lt;span class="hljs-number"&gt;33534&lt;/span&gt;]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http:&lt;span class="hljs-comment"&gt;//127.0.0.1:8001 (Press CTRL+C to quit)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I recommend installing the &lt;a href="https://github.com/simonw/datasette-leaflet-geojson"&gt;datasette-leaflet-geojson&lt;/a&gt; plugin, which will turn any column containing GeoJSON into a Leaflet map.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2020/nps-boundaries.jpg" alt="Screenshot of National Parks in Datasette" style="max-width: 100%" /&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;#39;ve installed SpatiaLite (&lt;a href="https://datasette.readthedocs.io/en/latest/spatialite.html#installation"&gt;installation instructions here&lt;/a&gt;) you can use the &lt;code&gt;--spatialite&lt;/code&gt; option to instead store the geometry in a SpatiaLite column, unlocking &lt;a href="http://www.gaia-gis.it/gaia-sins/spatialite-sql-latest.html"&gt;a bewildering array&lt;/a&gt; of SQL geometry functions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ shapefile-to-sqlite nps.db temp/Current_Shapes/Data_Store/&lt;span class="hljs-number"&gt;06&lt;/span&gt;&lt;span class="hljs-number"&gt;-06&lt;/span&gt;&lt;span class="hljs-number"&gt;-12&lt;/span&gt;_Posting/nps_boundary.shp --spatialite --table=nps-spatialite
temp/Current_Shapes/Data_Store/&lt;span class="hljs-number"&gt;06&lt;/span&gt;&lt;span class="hljs-number"&gt;-06&lt;/span&gt;&lt;span class="hljs-number"&gt;-12&lt;/span&gt;_Posting/nps_boundary.shp
[##################################--]   &lt;span class="hljs-number"&gt;94&lt;/span&gt;%  &lt;span class="hljs-number"&gt;00&lt;/span&gt;:&lt;span class="hljs-number"&gt;00&lt;/span&gt;:&lt;span class="hljs-number"&gt;00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I deployed a copy of the resulting database using Cloud Run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ datasette publish cloudrun nps.db \
    -&lt;span class="ruby"&gt;-service national-parks \
&lt;/span&gt;    -&lt;span class="ruby"&gt;-title &lt;span class="hljs-string"&gt;"National Parks"&lt;/span&gt; \
&lt;/span&gt;    -&lt;span class="ruby"&gt;-source_url=&lt;span class="hljs-string"&gt;"https://catalog.data.gov/dataset/national-parks"&lt;/span&gt; \
&lt;/span&gt;    -&lt;span class="ruby"&gt;-source=&lt;span class="hljs-string"&gt;"data.gov"&lt;/span&gt; \
&lt;/span&gt;    -&lt;span class="ruby"&gt;-spatialite \
&lt;/span&gt;    -&lt;span class="ruby"&gt;-install=datasette-leaflet-geojson \
&lt;/span&gt;    -&lt;span class="ruby"&gt;-install=datasette-render-binary \
&lt;/span&gt;    -&lt;span class="ruby"&gt;-extra-options=&lt;span class="hljs-string"&gt;"--config max_returned_rows:5"&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I used &lt;code&gt;max_returned_rows:5&lt;/code&gt; there because these geometrries are pretty big - without it a page with 100 rows on it can return over 90MB of HTML!&lt;/p&gt;
&lt;p&gt;You can browse the GeoJSON version of the table &lt;a href="https://national-parks-j7hipcg4aq-uc.a.run.app/nps/nps_boundary"&gt;here&lt;/a&gt; and the SpatiaLite version &lt;a href="https://national-parks-j7hipcg4aq-uc.a.run.app/nps/nps-spatialite"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The SpatiaLite version defaults to rendering each geometry as an ugly binary blob. You can convert them to GeoJSON for compatibility with &lt;code&gt;datasette-leaflet-geojson&lt;/code&gt; using the SpatiaLite &lt;code&gt;AsGeoJSON()&lt;/code&gt; function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class="hljs-keyword"&gt;select&lt;/span&gt; &lt;span class="hljs-keyword"&gt;id&lt;/span&gt;, UNIT_NAME, AsGeoJSON(geometry)
&lt;span class="hljs-keyword"&gt;from&lt;/span&gt; [nps-spatialite]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&amp;#39;s &lt;a href="https://national-parks-j7hipcg4aq-uc.a.run.app/nps?sql=select+id%2C+UNIT_NAME%2C+AsGeoJSON%28geometry%29+from+%5Bnps-spatialite%5D"&gt;the result&lt;/a&gt; of that query running against the demo.&lt;/p&gt;
&lt;h3 id="understanding-shapefiles"&gt;Understanding shapefiles&lt;/h3&gt;
&lt;p&gt;The most confusing thing about shapefiles is that they aren&amp;#39;t a single file. A shapefile comes as a minimum of three files: &lt;code&gt;foo.shp&lt;/code&gt; containing geometries, &lt;code&gt;foo.shx&lt;/code&gt; containing an index into those geometries (really more of an implementation detail) and &lt;code&gt;foo.dbf&lt;/code&gt; contains key/value properties for each geometry.&lt;/p&gt;
&lt;p&gt;They often come bundled with other files too. &lt;code&gt;foo.prj&lt;/code&gt; is a WKT projection for the data for example. Wikipedia lists &lt;a href="https://en.wikipedia.org/wiki/Shapefile#Overview"&gt;a whole bunch&lt;/a&gt; of other possibilities.&lt;/p&gt;
&lt;p&gt;As a result, shapefiles are usually distributed as a zip file. Some shapefile libraries can even read directly from a zip.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://tools.ietf.org/html/rfc7946"&gt;GeoJSON format&lt;/a&gt; was designed as a modern alternative to shapefiles, so understanding GeoJSON really helps in understanding shapefiles. In particular the GeoJSON geometry types: Point, LineString, MultiLineString, Polygon and MultiPolygon match how shapefile geometries work.&lt;/p&gt;
&lt;p&gt;An important detail in shapefiles is that data in the &lt;code&gt;.shp&lt;/code&gt; and &lt;code&gt;.dbf&lt;/code&gt; files is matched by array index - so the first geometry can be considered as having ID=0, the second ID=1 and so on.&lt;/p&gt;
&lt;p&gt;You can read the properties from the &lt;code&gt;.dbf&lt;/code&gt; file using the &lt;a href="https://dbfread.readthedocs.io/en/latest/"&gt;dbfread&lt;/a&gt; Python module like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ipython
&lt;span class="hljs-keyword"&gt;In&lt;/span&gt; [&lt;span class="hljs-number"&gt;1&lt;/span&gt;]: import dbfread
&lt;span class="hljs-keyword"&gt;In&lt;/span&gt; [&lt;span class="hljs-number"&gt;2&lt;/span&gt;]: db = dbfread.DBF(&lt;span class="hljs-string"&gt;"temp/Current_Shapes/Data_Store/06-06-12_Posting/nps_boundary.dbf"&lt;/span&gt;)
&lt;span class="hljs-keyword"&gt;In&lt;/span&gt; [&lt;span class="hljs-number"&gt;3&lt;/span&gt;]: next(iter(db))
&lt;span class="hljs-keyword"&gt;Out&lt;/span&gt;[&lt;span class="hljs-number"&gt;3&lt;/span&gt;]: 
OrderedDict([(&lt;span class="hljs-string"&gt;'UNIT_TYPE'&lt;/span&gt;, &lt;span class="hljs-string"&gt;'Park'&lt;/span&gt;),
            (&lt;span class="hljs-string"&gt;'STATE'&lt;/span&gt;, &lt;span class="hljs-string"&gt;''&lt;/span&gt;),
            (&lt;span class="hljs-string"&gt;'REGION'&lt;/span&gt;, &lt;span class="hljs-string"&gt;'NC'&lt;/span&gt;),
            (&lt;span class="hljs-string"&gt;'UNIT_CODE'&lt;/span&gt;, &lt;span class="hljs-string"&gt;'NACC'&lt;/span&gt;),
            (&lt;span class="hljs-string"&gt;'UNIT_NAME'&lt;/span&gt;, &lt;span class="hljs-string"&gt;'West Potomac Park'&lt;/span&gt;),
            (&lt;span class="hljs-string"&gt;'DATE_EDIT'&lt;/span&gt;, &lt;span class="hljs-keyword"&gt;None&lt;/span&gt;),
            (&lt;span class="hljs-string"&gt;'GIS_NOTES'&lt;/span&gt;, &lt;span class="hljs-string"&gt;''&lt;/span&gt;),
            (&lt;span class="hljs-string"&gt;'CREATED_BY'&lt;/span&gt;, &lt;span class="hljs-string"&gt;'Legacy'&lt;/span&gt;),
            (&lt;span class="hljs-string"&gt;'METADATA'&lt;/span&gt;, &lt;span class="hljs-string"&gt;''&lt;/span&gt;),
            (&lt;span class="hljs-string"&gt;'PARKNAME'&lt;/span&gt;, &lt;span class="hljs-string"&gt;''&lt;/span&gt;)])
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="reading-shapefiles-in-python"&gt;Reading shapefiles in Python&lt;/h3&gt;
&lt;p&gt;I&amp;#39;m a big fan of the &lt;a href="https://shapely.readthedocs.io/"&gt;Shapely&lt;/a&gt; Python library, so I was delighted to see that Sean Gillies, creator of Shapely, also created a library for reading and writing shapefiles: &lt;a href="https://fiona.readthedocs.io/"&gt;Fiona&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://macwright.org/2012/10/31/gis-with-python-shapely-fiona.html"&gt;GIS with Python, Shapely, and Fiona&lt;/a&gt; by Tom MacWright was particularly useful for figuring this out. I like how he wrote that post in 2012 but added a note in 2017 that it&amp;#39;s still his recommended way of getting started with GIS in Python.&lt;/p&gt;
&lt;h3 id="projections"&gt;Projections&lt;/h3&gt;
&lt;p&gt;The trickiest part of working with any GIS data is always figuring out how to deal with &lt;a href="https://xkcd.com/977/"&gt;projections&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;GeoJSON &lt;a href="https://tools.ietf.org/html/rfc7946#section-4"&gt;attempts to standardize&lt;/a&gt; on WGS 84, otherwise known as the latitude/longitude model used by GPS. But... shapefiles frequently use something else. The &lt;a href="https://www.sccgov.org/sites/parks/Parks-Maps/Maps-Data/Pages/home.aspx"&gt;Santa Clara county parks&lt;/a&gt; shapefiles for example use &lt;a href="https://epsg.io/2227"&gt;EPSG:2227&lt;/a&gt;, also known as California zone 3.&lt;/p&gt;
&lt;p&gt;(Fun fact: ESPG stands for European Petroleum Survey Group, a now defunct oil industry group that today lives on only as a database of projected coordinate systems.)&lt;/p&gt;
&lt;p&gt;I spent &lt;a href="https://github.com/simonw/shapefile-to-sqlite/issues/6"&gt;quite a while&lt;/a&gt; thinking about how to best handle projections. In the end I decided that I&amp;#39;d follow GeoJSON&amp;#39;s lead and attempt to convert everything to WGS 84, but allow users to skip that behaviour using &lt;code&gt;--crs=keep&lt;/code&gt; or to specify an alternative projection to convert to with &lt;code&gt;--crs=epsg:2227&lt;/code&gt; or similar.&lt;/p&gt;
&lt;p&gt;SpatiaLite creates its geometry columns with a baked in SRID (a code which usually maps to the EPSG identifier). You can see which SRID was used for a specific geometry using the &lt;code&gt;srid()&lt;/code&gt; function:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://national-parks-j7hipcg4aq-uc.a.run.app/nps?sql=select+srid%28geometry%29+from+%22nps-spatialite%22+limit+1"&gt;select srid(geometry) from &amp;quot;nps-spatialite&amp;quot; limit 1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;SpatiaLite can also convert to another projection using the &lt;code&gt;Transform()&lt;/code&gt; function:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://national-parks-j7hipcg4aq-uc.a.run.app/nps?sql=select+%27%3A%27+%7C%7C+AsGeoJSON%28Transform%28geometry%2C+2227%29%29+from+%22nps-spatialite%22+limit+1"&gt;select &amp;#39;:&amp;#39; || AsGeoJSON(Transform(geometry, 2227)) from &amp;quot;nps-spatialite&amp;quot; limit 1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(I&amp;#39;m using &lt;code&gt;&amp;#39;:&amp;#39; || AsGeoJSON(...)&lt;/code&gt; here to disable the &lt;code&gt;datasette-leaflet-geojson&lt;/code&gt; plugin, since it can&amp;#39;t correctly render data that has been transformed to a non-WGS-84 proection.)&lt;/p&gt;
&lt;h3 id="pulling-it-all-together"&gt;Pulling it all together&lt;/h3&gt;
&lt;p&gt;I now have two tools for imorting geospatial data into SQLite (or SpatiaLite) databases: &lt;a href="hhttps://github.com/simonw/shahpefile-to-sqlite"&gt;shapefile-to-sqlite&lt;/a&gt; and &lt;a href="https://github.com/simonw/geojson-to-sqlite"&gt;geojson-to-sqlite&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;#39;m excited about Datasette&amp;#39;s potential as a tool for GIS. I started exploring this back in 2017 when I used it to &lt;a href="https://simonwillison.net/2017/Dec/12/location-time-zone-api/"&gt;build a location to timezone API&lt;/a&gt; - but adding easy shapefile imports to the toolchain should unlock all kinds of interesting new geospatial projects.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/shapefiles"&gt;shapefiles&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geojson"&gt;geojson&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tom-macwright"&gt;tom-macwright&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/leaflet"&gt;leaflet&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="geospatial"/><category term="projects"/><category term="shapefiles"/><category term="spatialite"/><category term="sqlite"/><category term="geojson"/><category term="weeknotes"/><category term="tom-macwright"/><category term="leaflet"/></entry><entry><title>geojson-to-sqlite</title><link href="https://simonwillison.net/2020/Jan/31/geojson-sqlite/#atom-tag" rel="alternate"/><published>2020-01-31T06:40:53+00:00</published><updated>2020-01-31T06:40:53+00:00</updated><id>https://simonwillison.net/2020/Jan/31/geojson-sqlite/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/simonw/geojson-to-sqlite"&gt;geojson-to-sqlite&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I just put out the first release of geojson-to-sqlite—a CLI tool that can convert GeoJSON files (consisting of a Feature or a set of features in a FeatureCollection) into a table in a SQLite database. If you use the --spatialite option it will initalize the table with SpatiaLite and store the geometries in a spacially indexed geometry field—without that option it stores them as GeoJSON.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/simonw/status/1223133971082403841"&gt;@simonw&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geojson"&gt;geojson&lt;/a&gt;&lt;/p&gt;



</summary><category term="cli"/><category term="geospatial"/><category term="projects"/><category term="spatialite"/><category term="sqlite"/><category term="geojson"/></entry><entry><title>SpatiaLite — Datasette documentation</title><link href="https://simonwillison.net/2018/May/30/spatialite-datasette-docs/#atom-tag" rel="alternate"/><published>2018-05-30T04:34:06+00:00</published><updated>2018-05-30T04:34:06+00:00</updated><id>https://simonwillison.net/2018/May/30/spatialite-datasette-docs/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://datasette.readthedocs.io/en/latest/spatialite.html"&gt;SpatiaLite — Datasette documentation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Datasette’s documentation now includes extensive coverage of the SpatiaLite extension for SQLite: how to install it, how to import latitude/longitude points, shapefiles and GeoJSON data into SpatiaLite tables, and how to run SQL queries against it that take advantage of spatial indexes. I’m learning SpatiaLite at the moment and filling out the documentation with each new trick I learn as I go—as Mark Pilgrim once taught me, the best way to learn a new technology is to write about it.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/documentation"&gt;documentation&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mark-pilgrim"&gt;mark-pilgrim&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;



</summary><category term="documentation"/><category term="mark-pilgrim"/><category term="spatialite"/><category term="sqlite"/><category term="datasette"/></entry><entry><title>VirtualKNN for SpatiaLite</title><link href="https://simonwillison.net/2018/May/21/virtualknn/#atom-tag" rel="alternate"/><published>2018-05-21T21:23:43+00:00</published><updated>2018-05-21T21:23:43+00:00</updated><id>https://simonwillison.net/2018/May/21/virtualknn/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.gaia-gis.it/fossil/libspatialite/wiki?name=KNN"&gt;VirtualKNN for SpatiaLite&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
This looks amazing: a special virtual table shipped as part of SpatiaLite 4.4.0 which implements a fast, R-Tree backed mechanism for finding the X nearest points against a geospatial database table. There’s just one catch: it’s only available in 4.4.0, but the most recent “stable” release of SpatiaLite is 4.3.0a from September 2015 so the version you get if you install from apt-get or homebrew doesn’t yet have this functionality. I’d love to figure out a neat way to package and distribute this along with Datasette. I’d also like to figure out a clean way to ship a more recent version of SQLite than the one that is currently packaged with Python 3 (3.16.2, where the latest SQLite release is 3.23.1).


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;&lt;/p&gt;



</summary><category term="geospatial"/><category term="spatialite"/><category term="sqlite"/></entry><entry><title>GeoCouch: Geospatial queries with CouchDB</title><link href="https://simonwillison.net/2008/Oct/27/geocouch/#atom-tag" rel="alternate"/><published>2008-10-27T23:48:46+00:00</published><updated>2008-10-27T23:48:46+00:00</updated><id>https://simonwillison.net/2008/Oct/27/geocouch/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://vmx.cx/cgi-bin/blog/index.cgi/geocouch-geospatial-queries-with-couchdb:2008-10-26:en,CouchDB,Python,geo"&gt;GeoCouch: Geospatial queries with CouchDB&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Interesting approach: uses “external2”, a branch that allows external services to be called from CouchDB. SQLite’s SpatiaLite extension is then used as an external spacial index.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/couchdb"&gt;couchdb&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/erlang"&gt;erlang&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/external2"&gt;external2&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geocouch"&gt;geocouch&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/spatialite"&gt;spatialite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;&lt;/p&gt;



</summary><category term="couchdb"/><category term="erlang"/><category term="external2"/><category term="geocouch"/><category term="spatialite"/><category term="sqlite"/></entry></feed>