<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: openstreetmap</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/openstreetmap.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-03-17T22:49:59+00:00</updated><author><name>Simon Willison</name></author><entry><title>OpenTimes</title><link href="https://simonwillison.net/2025/Mar/17/opentimes/#atom-tag" rel="alternate"/><published>2025-03-17T22:49:59+00:00</published><updated>2025-03-17T22:49:59+00:00</updated><id>https://simonwillison.net/2025/Mar/17/opentimes/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://sno.ws/opentimes/"&gt;OpenTimes&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Spectacular new open geospatial project by &lt;a href="https://sno.ws/"&gt;Dan Snow&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenTimes is a database of pre-computed, point-to-point travel times between United States Census geographies. It lets you download bulk travel time data for free and with no limits.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://opentimes.org/?id=060816135022&amp;amp;mode=car#9.76/37.5566/-122.3085"&gt;what I get&lt;/a&gt; for travel times by car from El Granada, California:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Isochrone map showing driving times from the El Granada census tract to other places in the San Francisco Bay Area" src="https://static.simonwillison.net/static/2025/opentimes.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The technical details are &lt;em&gt;fascinating&lt;/em&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The entire OpenTimes backend is just static Parquet files on &lt;a href="https://www.cloudflare.com/developer-platform/products/r2/"&gt;Cloudflare's R2&lt;/a&gt;. There's no RDBMS or running service, just files and a CDN. The whole thing costs about $10/month to host and costs nothing to serve. In my opinion, this is a &lt;em&gt;great&lt;/em&gt; way to serve infrequently updated, large public datasets at low cost (as long as you partition the files correctly).&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sure enough, &lt;a href="https://developers.cloudflare.com/r2/pricing/"&gt;R2 pricing&lt;/a&gt; charges "based on the total volume of data stored" - $0.015 / GB-month for standard storage, then $0.36 / million requests for "Class B" operations which include reads. They charge nothing for outbound bandwidth.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;All travel times were calculated by pre-building the inputs (OSM, OSRM networks) and then distributing the compute over &lt;a href="https://github.com/dfsnow/opentimes/actions/workflows/calculate-times.yaml"&gt;hundreds of GitHub Actions jobs&lt;/a&gt;. This worked shockingly well for this specific workload (and was also completely free).&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's a &lt;a href="https://github.com/dfsnow/opentimes/actions/runs/13094249792"&gt;GitHub Actions run&lt;/a&gt; of the &lt;a href="https://github.com/dfsnow/opentimes/blob/a6a5f7abcdd69559b3e29f360fe0ff0399dbb400/.github/workflows/calculate-times.yaml#L78-L80"&gt;calculate-times.yaml workflow&lt;/a&gt; which uses a matrix to run 255 jobs!&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitHub Actions run: calculate-times.yaml run by workflow_dispatch taking 1h49m to execute 255 jobs with names like run-job (2020-01) " src="https://static.simonwillison.net/static/2025/opentimes-github-actions.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Relevant YAML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  matrix:
    year: ${{ fromJSON(needs.setup-jobs.outputs.years) }}
    state: ${{ fromJSON(needs.setup-jobs.outputs.states) }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where those JSON files were created by the previous step, which reads in the year and state values from &lt;a href="https://github.com/dfsnow/opentimes/blob/a6a5f7abcdd69559b3e29f360fe0ff0399dbb400/data/params.yaml#L72-L132"&gt;this params.yaml file&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The query layer uses a single DuckDB database file with &lt;em&gt;views&lt;/em&gt; that point to static Parquet files via HTTP. This lets you query a table with hundreds of billions of records after downloading just the ~5MB pointer file.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a really creative use of DuckDB's feature that lets you run queries against large data from a laptop using HTTP range queries to avoid downloading the whole thing.&lt;/p&gt;
&lt;p&gt;The README shows &lt;a href="https://github.com/dfsnow/opentimes/blob/3439fa2c54af227e40997b4a5f55678739e0f6df/README.md#using-duckdb"&gt;how to use that from R and Python&lt;/a&gt; - I got this working in the &lt;code&gt;duckdb&lt;/code&gt; client (&lt;code&gt;brew install duckdb&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSTALL httpfs;
LOAD httpfs;
ATTACH 'https://data.opentimes.org/databases/0.0.1.duckdb' AS opentimes;

SELECT origin_id, destination_id, duration_sec
  FROM opentimes.public.times
  WHERE version = '0.0.1'
      AND mode = 'car'
      AND year = '2024'
      AND geography = 'tract'
      AND state = '17'
      AND origin_id LIKE '17031%' limit 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In answer to a question about adding public transit times &lt;a href="https://news.ycombinator.com/item?id=43392521#43393183"&gt;Dan said&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the next year or so maybe. The biggest obstacles to adding public transit are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Collecting all the necessary scheduling data (e.g. GTFS feeds) for every transit system in the county. Not insurmountable since there are services that do this currently.&lt;/li&gt;
&lt;li&gt;Finding a routing engine that can compute nation-scale travel time matrices quickly. Currently, the two fastest open-source engines I've tried (OSRM and Valhalla) don't support public transit for matrix calculations and the engines that do support public transit (R5, OpenTripPlanner, etc.) are too slow.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://gtfs.org/"&gt;GTFS&lt;/a&gt; is a popular CSV-based format for sharing transit schedules - here's &lt;a href="https://gtfs.org/resources/data/"&gt;an official list&lt;/a&gt; of available feed directories.&lt;/p&gt;
&lt;p&gt;This whole project feels to me like a great example of the &lt;a href="https://simonwillison.net/2021/Jul/28/baked-data/"&gt;baked data&lt;/a&gt; architectural pattern in action.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/census"&gt;census&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/open-data"&gt;open-data&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cloudflare"&gt;cloudflare&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/parquet"&gt;parquet&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/baked-data"&gt;baked-data&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/duckdb"&gt;duckdb&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/http-range-requests"&gt;http-range-requests&lt;/a&gt;&lt;/p&gt;



</summary><category term="census"/><category term="geospatial"/><category term="open-data"/><category term="openstreetmap"/><category term="cloudflare"/><category term="parquet"/><category term="github-actions"/><category term="baked-data"/><category term="duckdb"/><category term="http-range-requests"/></entry><entry><title>OpenStreetMap embed URL</title><link href="https://simonwillison.net/2024/Nov/25/openstreetmap-embed-url/#atom-tag" rel="alternate"/><published>2024-11-25T19:29:16+00:00</published><updated>2024-11-25T19:29:16+00:00</updated><id>https://simonwillison.net/2024/Nov/25/openstreetmap-embed-url/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.openstreetmap.org/export/embed.html?bbox=-122.61343002319336,37.43138681508927,-122.38220214843751,37.5594114838176&amp;amp;layer=mapnik&amp;amp;marker=37.4954206394371,-122.4979019165039"&gt;OpenStreetMap embed URL&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I just found out OpenStreetMap have a "share" button which produces HTML for an iframe targetting &lt;code&gt;https://www.openstreetmap.org/export/embed.html&lt;/code&gt;, making it easy to drop an OpenStreetMap map onto any web page that allows iframes.&lt;/p&gt;
&lt;p&gt;As far as I can tell the supported parameters are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bbox=&lt;/code&gt; then min longitude, min latitude, max longitude, max latitude&lt;/li&gt;
&lt;li&gt;&lt;code&gt;marker=&lt;/code&gt; optional latitude, longitude coordinate for a marker (only a single marker is supported)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;layer=mapnik&lt;/code&gt; - other values I've found that work are &lt;code&gt;cyclosm&lt;/code&gt;, &lt;code&gt;cyclemap&lt;/code&gt;, &lt;code&gt;transportmap&lt;/code&gt; and &lt;code&gt;hot&lt;/code&gt; (for humanitarian)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's HTML for embedding this on a page using a sandboxed iframe - the &lt;code&gt;allow-scripts&lt;/code&gt; is necessary for the map to display.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;iframe
  sandbox="allow-scripts"
  style="border: none; width: 100%; height: 20em;"
  src="https://www.openstreetmap.org/export/embed.html?bbox=-122.613%2C37.431%2C-122.382%2C37.559&amp;amp;amp;layer=mapnik&amp;amp;amp;marker=37.495%2C-122.497"
&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;iframe
      sandbox="allow-scripts"
      style="border: none; width: 100%; height: 20em;"
      src="https://www.openstreetmap.org/export/embed.html?bbox=-122.613%2C37.431%2C-122.382%2C37.559&amp;amp;layer=mapnik&amp;amp;marker=37.495%2C-122.497"
&gt;&lt;/iframe&gt;
&lt;/p&gt;
&lt;p&gt;Thanks to this post I learned that iframes are rendered correctly in &lt;a href="https://fedi.simonwillison.net/@simon/113545275313339806"&gt;NetNewsWire&lt;/a&gt;, &lt;a href="https://fosstodon.org/@carlton/113545449230432890"&gt;NewsExplorer&lt;/a&gt;, &lt;a href="https://mstdn.social/@nriley/113545545163094439"&gt;NewsBlur&lt;/a&gt; and &lt;a href="https://fosstodon.org/@omad/113545693553360791"&gt;Feedly on Android&lt;/a&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/iframes"&gt;iframes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;&lt;/p&gt;



</summary><category term="geospatial"/><category term="iframes"/><category term="openstreetmap"/><category term="sandboxing"/></entry><entry><title>OpenStreetMap vector tiles demo</title><link href="https://simonwillison.net/2024/Nov/19/openstreetmap-vector-tiles-demo/#atom-tag" rel="alternate"/><published>2024-11-19T23:39:18+00:00</published><updated>2024-11-19T23:39:18+00:00</updated><id>https://simonwillison.net/2024/Nov/19/openstreetmap-vector-tiles-demo/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://pnorman.github.io/tilekiln-shortbread-demo/#9.23/37.5982/-122.2625"&gt;OpenStreetMap vector tiles demo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Long-time OpenStreetMap developer &lt;a href="https://www.paulnorman.ca/"&gt;Paul Norman&lt;/a&gt; has been working on adding vector tile support to OpenStreetMap for &lt;a href="https://community.openstreetmap.org/t/minutely-updated-vector-tiles-demo/110121"&gt;quite a while&lt;/a&gt;. Paul &lt;a href="https://community.openstreetmap.org/t/vector-tiles-on-osmf-hardware/121501"&gt;recently announced&lt;/a&gt; that &lt;code&gt;vector.openstreetmap.org&lt;/code&gt; is now serving vector tiles (in &lt;a href="https://github.com/mapbox/vector-tile-spec"&gt;Mapbox Vector Tiles (MVT) format&lt;/a&gt;) - here's his interactive demo for seeing what they look like.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://tech.marksblogg.com/osm-mvt-vector-tiles.html"&gt;Mark Litwintschik&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/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;&lt;/p&gt;



</summary><category term="geospatial"/><category term="mapping"/><category term="openstreetmap"/></entry><entry><title>OpenFreeMap</title><link href="https://simonwillison.net/2024/Sep/28/openfreemap/#atom-tag" rel="alternate"/><published>2024-09-28T21:41:15+00:00</published><updated>2024-09-28T21:41:15+00:00</updated><id>https://simonwillison.net/2024/Sep/28/openfreemap/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://openfreemap.org/"&gt;OpenFreeMap&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
New free map tile hosting service from Zsolt Ero:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenFreeMap lets you display custom maps on your website and apps for free. […] Using our &lt;strong&gt;public instance&lt;/strong&gt; is completely free: there are no limits on the number of map views or requests. There’s no registration, no user database, no API keys, and no cookies. We aim to cover the running costs of our public instance through donations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The site serves static vector tiles that work with &lt;a href="https://maplibre.org/maplibre-gl-js/docs/"&gt;MapLibre GL&lt;/a&gt;. It deliberately doesn’t offer any other services such as search or routing.&lt;/p&gt;
&lt;p&gt;From &lt;a href="https://github.com/hyperknot/openfreemap"&gt;the project README&lt;/a&gt; looks like it’s hosted on two Hetzner machines. I don’t think the public server is behind a CDN.&lt;/p&gt;
&lt;p&gt;Part of the trick to serving the tiles efficiently is the way it takes advantage of &lt;a href="https://en.m.wikipedia.org/wiki/Btrfs"&gt;Btrfs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Production-quality hosting of 300 million tiny files is hard. The average file size is just 450 byte. Dozens of tile servers have been written to tackle this problem, but they all have their limitations.&lt;/p&gt;
&lt;p&gt;The original idea of this project is to avoid using tile servers altogether. Instead, the tiles are directly served from Btrfs partition images + hard links using an optimised nginx config.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href="https://github.com/hyperknot/openfreemap/blob/main/docs/self_hosting.md"&gt;self-hosting guide&lt;/a&gt; describes the scripts that are provided for downloading their pre-built tiles (needing a fresh Ubuntu server with 300GB of SSD and 4GB of RAM) or building the tiles yourself using &lt;a href="https://github.com/onthegomap/planetiler"&gt;Planetiler&lt;/a&gt; (needs 500GB of disk and 64GB of RAM).&lt;/p&gt;
&lt;p&gt;Getting started is delightfully straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const map = new maplibregl.Map({
  style: 'https://tiles.openfreemap.org/styles/liberty',
  center: [13.388, 52.517],
  zoom: 9.5,
  container: 'map',
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I &lt;a href="https://gist.github.com/simonw/da2b20711b96f745873ccb44a3347ce9"&gt;got Claude to help&lt;/a&gt; build &lt;a href="http://tools.simonwillison.net/openfreemap-demo"&gt;this demo&lt;/a&gt; showing a thousand random markers dotted around San Francisco. The 3D tiles even include building shapes!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Map of San Francisco in 3D with building shapes and small blue random markers dotted around." src="https://static.simonwillison.net/static/2024/openfreemap.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;Zsolt built OpenFreeMap based on his experience running &lt;a href="https://maphub.net"&gt;MapHub&lt;/a&gt; over the last 9 years. Here’s &lt;a href="https://blog.opencagedata.com/post/interview-zsolt-ero-maphub"&gt;a 2018 interview about that project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s pretty incredible that the OpenStreetMap and open geospatial stack has evolved to the point now where it’s economically feasible for an individual to offer a service like this. I hope this turns out to be sustainable. Hetzner charge &lt;a href="https://docs.hetzner.com/robot/general/traffic/"&gt;just €1 per TB&lt;/a&gt; for bandwidth (S3 can cost $90/TB) which should help a lot.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://cosocial.ca/@timbray/113216132761896850"&gt;Tim Bray&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/maps"&gt;maps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;&lt;/p&gt;



</summary><category term="geospatial"/><category term="maps"/><category term="openstreetmap"/><category term="ai-assisted-programming"/></entry><entry><title>Optimizing Large-Scale OpenStreetMap Data with SQLite</title><link href="https://simonwillison.net/2024/Jul/2/openstreetmap-data-sqlite/#atom-tag" rel="alternate"/><published>2024-07-02T14:33:09+00:00</published><updated>2024-07-02T14:33:09+00:00</updated><id>https://simonwillison.net/2024/Jul/2/openstreetmap-data-sqlite/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://jtarchie.com/posts/2024-07-02-optimizing-large-scale-openstreetmap-data-with-sqlite"&gt;Optimizing Large-Scale OpenStreetMap Data with SQLite&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
JT Archie describes his project to take 9GB of compressed OpenStreetMap protobufs data for the whole of the United States and load it into a queryable SQLite database.&lt;/p&gt;
&lt;p&gt;OSM tags are key/value pairs. The trick used here for FTS-accelerated tag queries is really neat: build a SQLite FTS table containing the key/value pairs as space concatenated text, then run queries that look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT
    id
FROM
    entries e
    JOIN search s ON s.rowid = e.id
WHERE
    -- use FTS index to find subset of possible results
    search MATCH 'amenity cafe'
    -- use the subset to find exact matches
    AND tags-&amp;gt;&amp;gt;'amenity' = 'cafe';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JT ended up building a custom SQLite Go extension, &lt;a href="https://github.com/jtarchie/sqlitezstd"&gt;SQLiteZSTD&lt;/a&gt;, to further accelerate things by supporting queries against read-only zstd compresses SQLite files. Apparently zstd has &lt;a href="https://github.com/facebook/zstd/blob/3de0541aef8da51f144ef47fb86dcc38b21afb00/contrib/seekable_format/zstd_seekable_compression_format.md"&gt;a feature&lt;/a&gt; that allows "compressed data to be stored so that subranges of the data can be efficiently decompressed without requiring the entire document to be decompressed", which works well with SQLite's page format.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://www.reddit.com/r/sqlite/comments/1dtls62/optimizing_largescale_openstreetmap_data_with/"&gt;r/sqlite&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/zstd"&gt;zstd&lt;/a&gt;&lt;/p&gt;



</summary><category term="go"/><category term="openstreetmap"/><category term="sqlite"/><category term="zstd"/></entry><entry><title>How The Post is replacing Mapbox with open source solutions</title><link href="https://simonwillison.net/2023/Feb/17/replacing-mapbox/#atom-tag" rel="alternate"/><published>2023-02-17T18:45:33+00:00</published><updated>2023-02-17T18:45:33+00:00</updated><id>https://simonwillison.net/2023/Feb/17/replacing-mapbox/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.kschaul.com/post/2023/02/16/how-the-post-is-replacing-mapbox-with-open-source-solutions/"&gt;How The Post is replacing Mapbox with open source solutions&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Kevin Schaul describes the Washington Post’s emerging open source GIS stack: OpenMapTiles, Maputnik, PMTiles and Maplibre-gl-js.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/maps"&gt;maps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/opensearch"&gt;opensearch&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/washington-post"&gt;washington-post&lt;/a&gt;&lt;/p&gt;



</summary><category term="maps"/><category term="opensearch"/><category term="openstreetmap"/><category term="washington-post"/></entry><entry><title>A tiny web app to create images from OpenStreetMap maps</title><link href="https://simonwillison.net/2022/Jun/12/url-map/#atom-tag" rel="alternate"/><published>2022-06-12T05:49:35+00:00</published><updated>2022-06-12T05:49:35+00:00</updated><id>https://simonwillison.net/2022/Jun/12/url-map/#atom-tag</id><summary type="html">
    &lt;p&gt;Earlier today I found myself wanting to programmatically generate some images of maps.&lt;/p&gt;
&lt;p&gt;I wanted to create a map centered around a location, at a specific zoom level, and with a marker in a specific place.&lt;/p&gt;
&lt;p&gt;Some cursory searches failed to turn up exactly what I wanted, so I decided to build a tiny project to solve the problem, taking advantage of my &lt;a href="https://shot-scraper.datasette.io/"&gt;shot-scraper tool&lt;/a&gt; for automating screenshots of web pages.&lt;/p&gt;
&lt;p&gt;The result is &lt;a href="https://map.simonwillison.net/"&gt;map.simonwillison.net&lt;/a&gt; - hosted on GitHub Pages from my &lt;a href="https://github.com/simonw/url-map"&gt;simonw/url-map&lt;/a&gt; repository.&lt;/p&gt;
&lt;p&gt;Here's how to generate a map image of Washington DC:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;shot-scraper 'https://map.simonwillison.net/?q=washington+dc' \
  --retina --width 600 --height 400 --wait 3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That command generates a PNG 1200x800 image that's a retina screenshot of the map displayed at &lt;a href="https://map.simonwillison.net/?q=washington+dc"&gt;https://map.simonwillison.net/?q=washington+dc&lt;/a&gt; - after waiting three seconds to esure all of the tiles have fully loaded.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2022/map-simonwillison-net.jpg" alt="A map of Washington DC, with a Leaflet / OpenStreetMap attribution in the bottom right" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;The website itself is &lt;a href="https://github.com/simonw/url-map/blob/main/README.md"&gt;documented here&lt;/a&gt;. It displays a map with no visible controls, though you can use gestures to zoom in and pan around - and the URL bar will update to reflect your navigation, so you can bookmark or share the URL once you've got it to the right spot.&lt;/p&gt;
&lt;p&gt;You can also use query string parameters to specify the map that should be initially displayed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://map.simonwillison.net/?center=51.49,0&amp;amp;zoom=8"&gt;https://map.simonwillison.net/?center=51.49,0&amp;amp;zoom=8&lt;/a&gt; displays a map at zoom level 8 centered on the specified latitude, longitude coordinate pair.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.simonwillison.net/?q=islington+london"&gt;https://map.simonwillison.net/?q=islington+london&lt;/a&gt; geocodes the &lt;code&gt;?q=&lt;/code&gt; text using &lt;a href="https://nominatim.openstreetmap.org/ui/search.html"&gt;OpenStreetMap Nominatim&lt;/a&gt; and zooms to the level that best fits the bounding box of the first returned result.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.simonwillison.net/?q=islington+london&amp;amp;zoom=12"&gt;https://map.simonwillison.net/?q=islington+london&amp;amp;zoom=12&lt;/a&gt; does that but zooms to level 12 instead of using the best fit for the bounding box&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://map.simonwillison.net/?center=51.49,0&amp;amp;zoom=8&amp;amp;marker=51.49,0&amp;amp;marker=51.3,0.2"&gt;https://map.simonwillison.net/?center=51.49,0&amp;amp;zoom=8&amp;amp;marker=51.49,0&amp;amp;marker=51.3,0.2&lt;/a&gt; adds two blue markers to the specified map. You can pass &lt;code&gt;&amp;amp;marker=lat,lon&lt;/code&gt; as many times as you like to add multiple markers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Annotated source code&lt;/h4&gt;
&lt;p&gt;The entire mapping application is contained in a single 68 line &lt;code&gt;index.html&lt;/code&gt; file that mixes HTML and JavaScript. It's built using the fantastic &lt;a href="https://leafletjs.com/"&gt;Leaflet&lt;/a&gt; open source mapping library.&lt;/p&gt;
&lt;p&gt;Since the code is so short, I'll enclude the entire thing here with some additional annotating comments.&lt;/p&gt;
&lt;p&gt;It started out as a copy of the first example in &lt;a href="https://leafletjs.com/examples/quick-start/"&gt;the Leaflet quick start guide&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-c1"&gt;&amp;lt;!DOCTYPE html&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&amp;lt;!-- Regular HTML boilerplate --&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;html&lt;/span&gt; &lt;span class="pl-c1"&gt;lang&lt;/span&gt;="&lt;span class="pl-s"&gt;en&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;head&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;meta&lt;/span&gt; &lt;span class="pl-c1"&gt;charset&lt;/span&gt;="&lt;span class="pl-s"&gt;utf-8&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;meta&lt;/span&gt; &lt;span class="pl-c1"&gt;name&lt;/span&gt;="&lt;span class="pl-s"&gt;viewport&lt;/span&gt;" &lt;span class="pl-c1"&gt;content&lt;/span&gt;="&lt;span class="pl-s"&gt;width=device-width, initial-scale=1&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;title&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;map.simonwillison.net&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;title&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&amp;lt;!--&lt;/span&gt;
&lt;span class="pl-c"&gt;  Leaflet's CSS and JS are loaded from the unpgk.com CDN, with the&lt;/span&gt;
&lt;span class="pl-c"&gt;  Subresource Integrity (SRI) integrity="sha512..." attribute to ensure&lt;/span&gt;
&lt;span class="pl-c"&gt;  that the exact expected code is served by the CDN.&lt;/span&gt;
&lt;span class="pl-c"&gt;--&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;link&lt;/span&gt; &lt;span class="pl-c1"&gt;rel&lt;/span&gt;="&lt;span class="pl-s"&gt;stylesheet&lt;/span&gt;" &lt;span class="pl-c1"&gt;href&lt;/span&gt;="&lt;span class="pl-s"&gt;https://unpkg.com/leaflet@1.8.0/dist/leaflet.css&lt;/span&gt;" &lt;span class="pl-c1"&gt;integrity&lt;/span&gt;="&lt;span class="pl-s"&gt;sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ==&lt;/span&gt;" &lt;span class="pl-c1"&gt;crossorigin&lt;/span&gt;=""/&amp;gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt; &lt;span class="pl-c1"&gt;src&lt;/span&gt;="&lt;span class="pl-s"&gt;https://unpkg.com/leaflet@1.8.0/dist/leaflet.js&lt;/span&gt;" &lt;span class="pl-c1"&gt;integrity&lt;/span&gt;="&lt;span class="pl-s"&gt;sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ==&lt;/span&gt;" &lt;span class="pl-c1"&gt;crossorigin&lt;/span&gt;=""&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&amp;lt;!-- I want the map to occupy the entire browser window with no margins --&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;style&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-ent"&gt;html&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-ent"&gt;body&lt;/span&gt; {
  &lt;span class="pl-c1"&gt;height&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-c1"&gt;100&lt;span class="pl-smi"&gt;%&lt;/span&gt;&lt;/span&gt;;
  &lt;span class="pl-c1"&gt;margin&lt;/span&gt;&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-c1"&gt;0&lt;/span&gt;;
}
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;style&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;head&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;body&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&amp;lt;!-- The Leaflet map renders in this 100% high/wide div --&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt; &lt;span class="pl-c1"&gt;id&lt;/span&gt;="&lt;span class="pl-s"&gt;map&lt;/span&gt;" &lt;span class="pl-c1"&gt;style&lt;/span&gt;="&lt;span class="pl-s"&gt;width: 100%; height: 100%;&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;div&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;toPoint&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;s&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c"&gt;// Convert "51.5,2.1" into [51.5, 2.1]&lt;/span&gt;
  &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-s1"&gt;s&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;split&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;","&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;parseFloat&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-c"&gt;// An async function so we can 'await fetch(...)' later on&lt;/span&gt;
&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;load&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c"&gt;// URLSearchParams is a fantastic browser API - it makes it easy to both read&lt;/span&gt;
  &lt;span class="pl-c"&gt;// query string parameters from the URL and later to generate new ones&lt;/span&gt;
  &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;params&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;new&lt;/span&gt; &lt;span class="pl-v"&gt;URLSearchParams&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;location&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;search&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-c"&gt;// If the starting URL is /?center=51,32&amp;amp;zoom=3 this will pull those values out&lt;/span&gt;
  &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;center&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;params&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'center'&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-c1"&gt;||&lt;/span&gt; &lt;span class="pl-s"&gt;'0,0'&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;initialZoom&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;params&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'zoom'&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;zoom&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;parseInt&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;initialZoom&lt;/span&gt; &lt;span class="pl-c1"&gt;||&lt;/span&gt; &lt;span class="pl-s"&gt;'2'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;10&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;q&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;params&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'q'&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-c"&gt;// .getAll() turns &amp;amp;marker=51.49,0&amp;amp;marker=51.3,0.2 into ['51.49,0', '51.3,0.2']&lt;/span&gt;
  &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;markers&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;params&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;getAll&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'marker'&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-c"&gt;// zoomControl: false turns off the visible +/- zoom buttons in Leaflet&lt;/span&gt;
  &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;map&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;L&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'map'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-c1"&gt;zoomControl&lt;/span&gt;: &lt;span class="pl-c1"&gt;false&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;setView&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-en"&gt;toPoint&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;center&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;zoom&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-v"&gt;L&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;tileLayer&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;maxZoom&lt;/span&gt;: &lt;span class="pl-c1"&gt;19&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;attribution&lt;/span&gt;: &lt;span class="pl-s"&gt;'&amp;amp;copy; &amp;lt;a href="http://www.openstreetmap.org/copyright"&amp;gt;OpenStreetMap&amp;lt;/a&amp;gt;'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c"&gt;// This option means retina-capable devices will get double-resolution tiles:&lt;/span&gt;
    &lt;span class="pl-c1"&gt;detectRetina&lt;/span&gt;: &lt;span class="pl-c1"&gt;true&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;addTo&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-c"&gt;// We only pay attention to ?q= if ?center= was not provided:&lt;/span&gt;
  &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;q&lt;/span&gt; &lt;span class="pl-c1"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="pl-c1"&gt;!&lt;/span&gt;&lt;span class="pl-s1"&gt;params&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'center'&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c"&gt;// We use fetch to pass ?q= to the Nominatim API and get back JSON&lt;/span&gt;
    &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-en"&gt;fetch&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
      &lt;span class="pl-s"&gt;`https://nominatim.openstreetmap.org/search.php?q=&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;${&lt;/span&gt;&lt;span class="pl-en"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;q&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;&amp;amp;format=jsonv2`&lt;/span&gt;
    &lt;span class="pl-kos"&gt;)&lt;/span&gt;
    &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;data&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;response&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;json&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-c"&gt;// data[0] is the first result - it has a boundingbox array of four floats&lt;/span&gt;
    &lt;span class="pl-c"&gt;// which we can convert into a Leaflet-compatible bounding box like this:&lt;/span&gt;
    &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;bounds&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-kos"&gt;[&lt;/span&gt;
      &lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-s1"&gt;data&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;0&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;boundingbox&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;0&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;&lt;span class="pl-s1"&gt;data&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;0&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;boundingbox&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;2&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-s1"&gt;data&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;0&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;boundingbox&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;1&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;&lt;span class="pl-s1"&gt;data&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;0&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;boundingbox&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;3&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt;
    &lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-c"&gt;// This sets both the map center and zooms to the correct level for the bbox:&lt;/span&gt;
    &lt;span class="pl-s1"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;fitBounds&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;bounds&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-c"&gt;// User-provided zoom over-rides this&lt;/span&gt;
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;initialZoom&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-s1"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;setZoom&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-en"&gt;parseInt&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;initialZoom&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;
  &lt;span class="pl-c"&gt;// This is the code that updates the URL as the user pans or zooms around.&lt;/span&gt;
  &lt;span class="pl-c"&gt;// You can subscribe to both the moveend and zoomend Leaflet events in one go:&lt;/span&gt;
  &lt;span class="pl-s1"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;on&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'moveend zoomend'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c"&gt;// Update URL bar with current location&lt;/span&gt;
    &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;newZoom&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;getZoom&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;center&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;getCenter&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-c"&gt;// This time we use URLSearchParams to construct a center...=&amp;amp;zoom=... URL&lt;/span&gt;
    &lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;u&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;new&lt;/span&gt; &lt;span class="pl-v"&gt;URLSearchParams&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-c"&gt;// Copy across ?marker=x&amp;amp;marker=y from existing URL, if they were set:&lt;/span&gt;
    &lt;span class="pl-s1"&gt;markers&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;forEach&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;s&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-s1"&gt;u&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;append&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'marker'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;s&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-s1"&gt;u&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;append&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'center'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s"&gt;`&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;${&lt;/span&gt;&lt;span class="pl-s1"&gt;center&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;lat&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;,&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;${&lt;/span&gt;&lt;span class="pl-s1"&gt;center&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;lng&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt;`&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-s1"&gt;u&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;append&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'zoom'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;newZoom&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-c"&gt;// replaceState() is a weird API - the third argument is the one we care about:&lt;/span&gt;
    &lt;span class="pl-s1"&gt;history&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;replaceState&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-c1"&gt;null&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;null&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s"&gt;'?'&lt;/span&gt; &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-s1"&gt;u&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;toString&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-c"&gt;// This bit adds Leaflet markers to the map for ?marker= query string arguments:&lt;/span&gt;
  &lt;span class="pl-s1"&gt;markers&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;forEach&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;s&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-v"&gt;L&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;marker&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-en"&gt;toPoint&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;s&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;addTo&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-en"&gt;load&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;body&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;html&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&amp;lt;!-- See https://github.com/simonw/url-map for documentation --&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&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/maps"&gt;maps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/shot-scraper"&gt;shot-scraper&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="maps"/><category term="openstreetmap"/><category term="projects"/><category term="shot-scraper"/><category term="leaflet"/></entry><entry><title>Quoting Joe Morrison</title><link href="https://simonwillison.net/2020/Nov/20/joe-morrison/#atom-tag" rel="alternate"/><published>2020-11-20T21:11:36+00:00</published><updated>2020-11-20T21:11:36+00:00</updated><id>https://simonwillison.net/2020/Nov/20/joe-morrison/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://joemorrison.medium.com/openstreetmap-is-having-a-moment-dcc7eef1bb01"&gt;&lt;p&gt;The open secret Jennings filled me in on is that OpenStreetMap (OSM) is now at the center of an unholy alliance of the world’s largest and wealthiest technology companies. The most valuable companies in the world are treating OSM as critical infrastructure for some of the most-used software ever written. The four companies in the inner circle— Facebook, Apple, Amazon, and Microsoft— have a combined market capitalization of over six trillion dollars.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://joemorrison.medium.com/openstreetmap-is-having-a-moment-dcc7eef1bb01"&gt;Joe Morrison&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/amazon"&gt;amazon&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/apple"&gt;apple&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/facebook"&gt;facebook&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/microsoft"&gt;microsoft&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;&lt;/p&gt;



</summary><category term="amazon"/><category term="apple"/><category term="facebook"/><category term="microsoft"/><category term="openstreetmap"/></entry><entry><title>Announcing Daylight Map Distribution</title><link href="https://simonwillison.net/2020/Mar/12/announcing-daylight-map-distribution/#atom-tag" rel="alternate"/><published>2020-03-12T11:44:55+00:00</published><updated>2020-03-12T11:44:55+00:00</updated><id>https://simonwillison.net/2020/Mar/12/announcing-daylight-map-distribution/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.openstreetmap.org/user/migurski/diary/392416"&gt;Announcing Daylight Map Distribution&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Mike Migurski announces a new distribution of OpenStreetMap: a 42GB  dump of the version of the data used by Facebook, carefully moderated to minimize the chance of incorrect or maliciously offensive edits. Lots of constructive conversation in the comments about the best way for Facebook to make their moderation decisions more available to the OSM community.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/facebook"&gt;facebook&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/michal-migurski"&gt;michal-migurski&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;&lt;/p&gt;



</summary><category term="facebook"/><category term="michal-migurski"/><category term="openstreetmap"/></entry><entry><title>Statistical NLP on OpenStreetMap</title><link href="https://simonwillison.net/2018/Jan/8/statistical-nlp-openstreetmap/#atom-tag" rel="alternate"/><published>2018-01-08T19:33:23+00:00</published><updated>2018-01-08T19:33:23+00:00</updated><id>https://simonwillison.net/2018/Jan/8/statistical-nlp-openstreetmap/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://machinelearnings.co/statistical-nlp-on-openstreetmap-b9d573e6cc86"&gt;Statistical NLP on OpenStreetMap&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
libpostal is ferociously clever: it’s a library for parsing and understanding worldwide addresses, built on top of a machine learning model trained on millions of addresses from OpenStreetMap. Al Barrentine describes how it works in this fascinating and detailed essay.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/machine-learning"&gt;machine-learning&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/nlp"&gt;nlp&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;&lt;/p&gt;



</summary><category term="machine-learning"/><category term="nlp"/><category term="openstreetmap"/></entry><entry><title>Building a location to time zone API with SpatiaLite, OpenStreetMap and Datasette</title><link href="https://simonwillison.net/2017/Dec/12/location-time-zone-api/#atom-tag" rel="alternate"/><published>2017-12-12T15:52:20+00:00</published><updated>2017-12-12T15:52:20+00:00</updated><id>https://simonwillison.net/2017/Dec/12/location-time-zone-api/#atom-tag</id><summary type="html">
    &lt;p&gt;Given a latitude and longitude, how can we tell what time zone that point lies within? Here’s how I built a simple JSON  API to answer that question, using a combination of data from OpenStreetMap, the SpatiaLite extension for SQLite and my &lt;a href="https://github.com/simonw/datasette"&gt;Datasette&lt;/a&gt; API tool.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This article is out of date and no longer relevant&lt;/strong&gt;. It has been replaced by a new tutorial: &lt;a href="https://datasette.io/tutorials/spatialite"&gt;Building a location to time zone API with SpatiaLite&lt;/a&gt;, on the official Datasette website.&lt;/p&gt;
&lt;h3&gt;&lt;a id="The_API_4"&gt;&lt;/a&gt;The API&lt;/h3&gt;
&lt;p&gt;You can try the API out here: feed it a latitude and longitude and it will return the corresponding time zone ID: &lt;a href="https://timezones.datasette.io/timezones/by_point"&gt;https://timezones.datasette.io/timezones/by_point&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://timezones.datasette.io/timezones/by_point?longitude=-0.1406632&amp;amp;latitude=50.8246776"&gt;Brighton, England&lt;/a&gt; is in Europe/London (&lt;a href="https://timezones.datasette.io/timezones/by_point.json?longitude=-0.1406632&amp;amp;latitude=50.8246776"&gt;in JSON&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://timezones.datasette.io/timezones/by_point?longitude=-122.4494224&amp;amp;latitude=37.8022071"&gt;San Francisco, USA&lt;/a&gt; is in America/Los_Angeles (&lt;a href="https://timezones.datasette.io/timezones/by_point.json?longitude=-122.4494224&amp;amp;latitude=37.8022071"&gt;in JSON&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://timezones.datasette.io/timezones/by_point?longitude=139.7819661&amp;amp;latitude=35.6631424"&gt;Tokyo, Japan&lt;/a&gt; is Asia/Tokyo (&lt;a href="https://timezones.datasette.io/timezones/by_point.json?longitude=139.7819661&amp;amp;latitude=35.6631424"&gt;in JSON&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;a id="The_data_14"&gt;&lt;/a&gt;The data&lt;/h3&gt;
&lt;p&gt;I was first introduced to Eric Muller’s &lt;a href="http://efele.net/maps/tz/world/"&gt;tz_world shapefile&lt;/a&gt; by &lt;a href="https://twitter.com/Java_Nick"&gt;Nick Williams&lt;/a&gt; at Eventbrite, who used it to build us an internal time zone lookup API on top of &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/spatial-types.html"&gt;MySQL’s geospatial data types&lt;/a&gt;. Eric’s project is no longer updated and he recommends Evan Siroky’s &lt;a href="https://github.com/evansiroky/timezone-boundary-builder"&gt;timezone-boundary-builder&lt;/a&gt; project as an alternative, which derives time zone shapes from OpenStreetMap and makes the resulting data available under the &lt;a href="https://opendatacommons.org/licenses/odbl/"&gt;Open Database License&lt;/a&gt;. The shapefile itself can be downloaded from &lt;a href="https://github.com/evansiroky/timezone-boundary-builder/releases/tag/2017c"&gt;the GitHub releases page&lt;/a&gt; for the project.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Loading_the_data_into_SpatiaLite_18"&gt;&lt;/a&gt;Loading the data into SpatiaLite&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.gaia-gis.it/fossil/libspatialite/index"&gt;SpatiaLite&lt;/a&gt; is a powerful open source extension for SQLite, which adds a comprehensive &lt;a href="http://www.gaia-gis.it/gaia-sins/spatialite-sql-4.3.0.html"&gt;suite of geospatial functions&lt;/a&gt; - including the ability to ingest shapefiles, convert them into geometries and run point within queries against the resulting shapes.&lt;/p&gt;
&lt;p&gt;The easiest way to get it running on OS X is via &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ brew install spatialite-tools
$ brew install gdal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Having installed SpatiaLite, we can ingest the shapefile using &lt;code&gt;.loadshp combined_shapefile timezones CP1252 23032&lt;/code&gt; - here’s the full process, from downloading the shapefile to ingesting it into a new SQLite database file called &lt;code&gt;timezones.db&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ wget https://github.com/evansiroky/timezone-boundary-builder/releases/download/2017c/timezones.shapefile.zip
$ unzip timezones.shapefile.zip
$ cd dist
$ spatialite timezones.db
SpatiaLite version ..: 4.3.0a   Supported Extensions:
...
Enter SQL statements terminated with a &amp;quot;;&amp;quot;
spatialite&amp;gt; .loadshp combined_shapefile timezones CP1252 23032
========
Loading shapefile at 'combined_shapefile' into SQLite table 'timezones'

BEGIN;
CREATE TABLE &amp;quot;timezones&amp;quot; (
&amp;quot;PK_UID&amp;quot; INTEGER PRIMARY KEY AUTOINCREMENT,
&amp;quot;tzid&amp;quot; TEXT);
SELECT AddGeometryColumn('timezones', 'Geometry', 23032, 'MULTIPOLYGON', 'XY');
COMMIT;

Inserted 414 rows into 'timezones' from SHAPEFILE
========
spatialite&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s try it out with a query. Here’s the SQL needed to find the time zone for a point in Tokyo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select tzid
from
    timezones
where
    within(GeomFromText('POINT(139.7819661 35.6631424)'), timezones.Geometry);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s run that in SpatiaLite and see what we get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spatialite&amp;gt; select tzid
   ...&amp;gt; from
   ...&amp;gt;     timezones
   ...&amp;gt; where
   ...&amp;gt;     within(GeomFromText('POINT(139.7819661 35.6631424)'), timezones.Geometry);
Asia/Tokyo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks good so far! How long is it taking though? We can find out by running &lt;code&gt;.timer on&lt;/code&gt; in the spatialite prompt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spatialite&amp;gt; .timer on
spatialite&amp;gt; select tzid
   ...&amp;gt; from
   ...&amp;gt;     timezones
   ...&amp;gt; where
   ...&amp;gt;     within(GeomFromText('POINT(139.7819661 35.6631424)'), timezones.Geometry);
Asia/Tokyo
CPU Time: user 0.108479 sys 0.064778
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s a tenth of a second, or 100ms. Fast, but not brilliant.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Speeding_it_up_with_a_geospatial_index_81"&gt;&lt;/a&gt;Speeding it up with a geospatial index&lt;/h3&gt;
&lt;p&gt;It turns out SpatiaLite includes support for spatial indices, based on SQLite’s &lt;a href="https://sqlite.org/rtree.html"&gt;R*Tree module&lt;/a&gt;. R-Tree indexes can massively accelerate boundary box searches. Our searches are a lot more complex than that, acting as they do against extremely complex polygon shapes - but we can use a boundary box search to dramatically reduce the candidates we need to consider. Let’s create an index against our Geometry column:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT CreateSpatialIndex('timezones', 'geometry');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To take advantage of this index, we need to expand our original SQL to first filter by geometries where their bounding box contains the point we are searching for. SpatiaLite has created an index table called &lt;code&gt;idx_timezones_Geometry&lt;/code&gt; against which we can run an R-Tree optimized query. Here’s the SQL we will use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select tzid
from
    timezones
where
    within(GeomFromText('POINT(139.7819661 35.6631424)'), timezones.Geometry)
    and rowid in (
        SELECT pkid FROM idx_timezones_Geometry
        where xmin &amp;lt; 139.7819661
        and xmax &amp;gt; 139.7819661
        and ymin &amp;lt; 35.6631424
        and ymax &amp;gt; 35.6631424
    );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How does this affect our performance?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spatialite&amp;gt; select tzid
   ...&amp;gt; from
   ...&amp;gt;     timezones
   ...&amp;gt; where
   ...&amp;gt;     within(GeomFromText('POINT(139.7819661 35.6631424)'), timezones.Geometry)
   ...&amp;gt;     and rowid in (
   ...&amp;gt;         SELECT pkid FROM idx_timezones_Geometry
   ...&amp;gt;         where xmin &amp;lt; 139.7819661
   ...&amp;gt;         and xmax &amp;gt; 139.7819661
   ...&amp;gt;         and ymin &amp;lt; 35.6631424
   ...&amp;gt;         and ymax &amp;gt; 35.6631424
   ...&amp;gt;     );
Asia/Tokyo
CPU Time: user 0.001541 sys 0.000111
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From 100ms down to 1.5ms - nearly a 70x speedup! Not bad at all.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Building_and_publishing_the_API_with_Datasette_121"&gt;&lt;/a&gt;Building and publishing the API with Datasette&lt;/h3&gt;
&lt;p&gt;Now that we have a fast SQL query for finding a time zone for a latitude and longitude we can use Datasette to turn it into a JSON API.&lt;/p&gt;
&lt;p&gt;The simplest way to do that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette timezones.db \
    --load-extension=/usr/local/lib/mod_spatialite.dylib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start Datasette on port 8001 and load the SpatiaLite extension. You can then navigate to &lt;code&gt;localhost:8001/timezones&lt;/code&gt; in your browser and paste in the SQL query… which should look something like this:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://timezones.datasette.io/timezones?sql=select%20tzid%0Afrom%0A%20%20%20%20timezones%0Awhere%0A%20%20%20%20within%28GeomFromText%28%27POINT%28139.7819661%2035.6631424%29%27%29%2C%20timezones.Geometry%29%0A%20%20%20%20and%20rowid%20in%20%28%0A%20%20%20%20%20%20%20%20SELECT%20pkid%20FROM%20idx_timezones_Geometry%0A%20%20%20%20%20%20%20%20where%20xmin%20%3C%20139.7819661%0A%20%20%20%20%20%20%20%20and%20xmax%20%3E%20139.7819661%0A%20%20%20%20%20%20%20%20and%20ymin%20%3C%2035.6631424%0A%20%20%20%20%20%20%20%20and%20ymax%20%3E%2035.6631424%0A%20%20%20%20%29%3B"&gt;https://timezones.datasette.io/timezones?sql=select+tzid%0D%0Afrom%0D%0A&lt;ins&gt;&lt;ins&gt;timezones%0D%0Awhere%0D%0A&lt;/ins&gt;&lt;/ins&gt;within(GeomFromText(‘POINT(139.7819661+35.6631424)’)%2C+timezones.Geometry)%0D%0A&lt;ins&gt;&lt;ins&gt;and+rowid+in+(%0D%0A++++&lt;/ins&gt;&lt;/ins&gt;SELECT+pkid+FROM+idx_timezones_Geometry%0D%0A&lt;ins&gt;&lt;ins&gt;&lt;ins&gt;&lt;ins&gt;where+xmin+&amp;lt;+139.7819661%0D%0A&lt;/ins&gt;&lt;/ins&gt;&lt;/ins&gt;&lt;/ins&gt;and+xmax+&amp;gt;+139.7819661%0D%0A&lt;ins&gt;&lt;ins&gt;&lt;ins&gt;&lt;ins&gt;and+ymin+&amp;lt;+35.6631424%0D%0A&lt;/ins&gt;&lt;/ins&gt;&lt;/ins&gt;&lt;/ins&gt;and+ymax+&amp;gt;+35.6631424%0D%0A++++)%3B&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This works (click the JSON link to get the result as JSON) but it’s a little inconvenient to use: you have to construct a URL with the same latitude and longitude repeated in multiple places.&lt;/p&gt;
&lt;p&gt;We can improve things using Datasette’s support for SQLite named parameters. Here’s that same SQL query using parameters instead of hard-coded latitude and longitude points:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select tzid
from
    timezones
where
    within(GeomFromText('POINT(' || :longitude || ' ' || :latitude || ')'), timezones.Geometry)
    and rowid in (
        SELECT pkid FROM idx_timezones_Geometry
        where xmin &amp;lt; :longitude
        and xmax &amp;gt; :longitude
        and ymin &amp;lt; :latitude
        and ymax &amp;gt; :latitude)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you &lt;a href="https://timezones.datasette.io/timezones?sql=select%20tzid%0Afrom%0A%20%20%20%20timezones%0Awhere%0A%20%20%20%20within%28GeomFromText%28%27POINT%28%27%20%7C%7C%20%3Alongitude%20%7C%7C%20%27%20%27%20%7C%7C%20%3Alatitude%20%7C%7C%20%27%29%27%29%2C%20timezones.Geometry%29%0A%20%20%20%20and%20rowid%20in%20%28%0A%20%20%20%20%20%20%20%20SELECT%20pkid%20FROM%20idx_timezones_Geometry%0A%20%20%20%20%20%20%20%20where%20xmin%20%3C%20%3Alongitude%0A%20%20%20%20%20%20%20%20and%20xmax%20%3E%20%3Alongitude%0A%20%20%20%20%20%20%20%20and%20ymin%20%3C%20%3Alatitude%0A%20%20%20%20%20%20%20%20and%20ymax%20%3E%20%3Alatitude%29"&gt;paste this into Datasette&lt;/a&gt; it will detect the named parameters and turn them into URL querystring parameters hooked up (in thu UI) to HTML form fields.&lt;/p&gt;
&lt;p&gt;&lt;img style="width: 100%" src="https://static.simonwillison.net/static/2017/datasette-timezone.png" alt="Datasette time zone query showing longitude and latitude form fields" /&gt;&lt;/p&gt;
&lt;p&gt;To save us from having to include the full SQL in the URL every time we call our new API, let’s take advantage of a new feature &lt;a href="https://github.com/simonw/datasette/releases/tag/0.14"&gt;introduced in Datasette 0.14&lt;/a&gt;: &lt;a href="https://datasette.readthedocs.io/en/latest/sql_queries.html#canned-queries"&gt;canned queries&lt;/a&gt;. These are named, pre-packaged queries that can be defined in a &lt;code&gt;metadata.json&lt;/code&gt; file. The file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &amp;quot;title&amp;quot;: &amp;quot;OpenStreetMap Time Zone Boundaries&amp;quot;,
    &amp;quot;license&amp;quot;: &amp;quot;ODbL&amp;quot;,
    &amp;quot;license_url&amp;quot;: &amp;quot;http://opendatacommons.org/licenses/odbl/&amp;quot;,
    &amp;quot;source&amp;quot;: &amp;quot;timezone-boundary-builder&amp;quot;,
    &amp;quot;source_url&amp;quot;: &amp;quot;https://github.com/evansiroky/timezone-boundary-builder&amp;quot;,
    &amp;quot;databases&amp;quot;: {
        &amp;quot;timezones&amp;quot;: {
            &amp;quot;queries&amp;quot;: {
                &amp;quot;by_point&amp;quot;: &amp;quot;select tzid\nfrom\n    timezones\nwhere\n    within(GeomFromText('POINT(' || :longitude || ' ' || :latitude || ')'), timezones.Geometry)\n    and rowid in (\n        SELECT pkid FROM idx_timezones_Geometry\n        where xmin &amp;lt; :longitude\n        and xmax &amp;gt; :longitude\n        and ymin &amp;lt; :latitude\n        and ymax &amp;gt; :latitude\n    )&amp;quot;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The canned query is defined as the &lt;code&gt;by_point&lt;/code&gt; key in the &lt;code&gt;queries&lt;/code&gt; nested object. I’m also adding license and source information here for the project, because it’s good manners.&lt;/p&gt;
&lt;p&gt;We can try this in Datasette on our local machine like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette timezones.db -m metadata.json \
    --load-extension=/usr/local/lib/mod_spatialite.dylib 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now visiting &lt;code&gt;localhost:8001/timezones/by_point&lt;/code&gt; will provide the interface for the query - and adding &lt;code&gt;.json&lt;/code&gt; to the URL will turn it into an API.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Vizualizing_time_zones_with_Leaflet_and_GeoJSON_180"&gt;&lt;/a&gt;Vizualizing time zones with Leaflet and GeoJSON&lt;/h3&gt;
&lt;p&gt;If you browse around in Datasette you’ll quickly run into a rather unfortunate problem. The &lt;code&gt;localhost:8001/timezones/timezones&lt;/code&gt; page, which shows the first 100 rows in the table, takes a shockingly long time to load. When it eventually does load you’ll see why: each record includes an enormous binary string containing its the geometry. On my machine just that one page weighs in at 62MB of HTML!&lt;/p&gt;
&lt;p&gt;This is bad: rendering that much HTML in one go can block the event loop and cause the application to become unresponsive. That giant blob of binary data isn’t exactly useful for humans, either.&lt;/p&gt;
&lt;p&gt;We can make some major improvements here using another Datasette 0.14 feature: &lt;a href="https://datasette.readthedocs.io/en/latest/custom_templates.html"&gt;custom templates&lt;/a&gt;. Let’s start with a replacement template that shows just the length of the binary string instead of attempting to render it.&lt;/p&gt;
&lt;p&gt;We’ll do that by over-riding the &lt;code&gt;_rows_and_columns.html&lt;/code&gt; include template, which is used by Datasette on both the table page and the page used to display individual rows. Since we only want to over-ride this template for one particular table we’ll create a file called &lt;code&gt;_rows_and_columns-timezones-timezones.html&lt;/code&gt; - the file name means this will only over-ride the template &lt;code&gt;timezones&lt;/code&gt; table in our &lt;code&gt;timezones&lt;/code&gt; database. Here’s our new template:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;table&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;
            {% for column in display_columns %}
                &amp;lt;th scope=&amp;quot;col&amp;quot;&amp;gt;{{ column }}&amp;lt;/th&amp;gt;
            {% endfor %}
        &amp;lt;/tr&amp;gt;
    &amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
    {% for row in display_rows %}
        &amp;lt;tr&amp;gt;
            {% for cell in row %}
                &amp;lt;td&amp;gt;
                    {% if cell.column == 'Geometry' %}
                        {{ cell.value|length }} bytes
                    {% else %}
                        {{ cell.value }}
                    {% endif %}
                &amp;lt;/td&amp;gt;
            {% endfor %}
        &amp;lt;/tr&amp;gt;
    {% endfor %}
    &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we put that in a directory called &lt;code&gt;templates/&lt;/code&gt; we can tell Datasette to use it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette timezones.db -m metadata.json \
    --load-extension=/usr/local/lib/mod_spatialite.dylib \
    --template-dir=templates/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our &lt;code&gt;localhost:8001/timezones/timezones&lt;/code&gt; page now looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img style="width: 100%" src="https://static.simonwillison.net/static/2017/datasette-timezone-index.png" alt="Datasette time zone index showing lengths" /&gt;&lt;/p&gt;
&lt;p&gt;But wouldn’t it be neat if we could see the actual shapes of these time zones? It turns out that’s actually pretty easy, using the combination of GeoJSON and the &lt;a href="http://leafletjs.com/"&gt;Leaflet&lt;/a&gt; mapping library.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/GeoJSON"&gt;GeoJSON&lt;/a&gt; is a neat, widely supported standard for encoding geographic information such as polygon shapes as JSON. SpatiaLite ships with built-in GeoJSON support in the form of the &lt;code&gt;AsGeoJSON&lt;/code&gt; SQL function. We can use that function to turn any of our time zone geometries into a GeoJSON string:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select AsGeoJSON(Geometry) from timezones where tzid = 'Asia/Tokyo';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you &lt;a href="https://timezones.datasette.io/timezones?sql=select+AsGeoJSON(Geometry)+from+timezones+where+tzid+%3D+%27Asia%2FTokyo%27%3B"&gt;run that with Datasette&lt;/a&gt; you’ll get back a string of GeoJSON. You can paste that into &lt;a href="http://geojson.io"&gt;geojson.io&lt;/a&gt; to instantly visualize it.&lt;/p&gt;
&lt;p&gt;The Leaflet mapping library &lt;a href="http://leafletjs.com/examples/geojson/"&gt;supports GeoJSON out of the box&lt;/a&gt;. We can construct a custom &lt;code&gt;row.html&lt;/code&gt; template for our Datasette that loads Leaflet from &lt;a href="https://unpkg.com"&gt;unpkg.com&lt;/a&gt;, uses &lt;code&gt;fetch()&lt;/code&gt; to execute the &lt;code&gt;AsGeoJSON&lt;/code&gt; query and renders the result in a map on the page. Here’s &lt;a href="https://timezones.datasette.io/timezones/timezones/12"&gt;the result&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://timezones.datasette.io/timezones/timezones/12"&gt;&lt;img style="width: 100%" src="https://static.simonwillison.net/static/2017/datasette-geojson.png" alt="Custom Datasette page rendering a GeoJSON map" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And here’s the &lt;code&gt;row-timezones-timezones.html&lt;/code&gt; template:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{% extends &amp;quot;row.html&amp;quot; %}
{% block extra_head %}
&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://unpkg.com/leaflet@1.2.0/dist/leaflet.css&amp;quot; integrity=&amp;quot;sha512-M2wvCLH6DSRazYeZRIm1JnYyh22purTM+FDB5CsyxtQJYeKq83arPe5wgbNmcFXGqiSH2XR8dT/fJISVA1r/zQ==&amp;quot; crossorigin=&amp;quot;&amp;quot;/&amp;gt;
&amp;lt;script src=&amp;quot;https://unpkg.com/leaflet@1.2.0/dist/leaflet.js&amp;quot; integrity=&amp;quot;sha512-lInM/apFSqyy1o6s89K4iQUKg6ppXEgsVxT35HbzUupEVRh2Eu9Wdl4tHj7dZO0s1uvplcYGmt3498TtHq+log==&amp;quot; crossorigin=&amp;quot;&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;style&amp;gt;
#map {
  margin-top: 1em;
  width: 100%;
  height: 400px;
}
&amp;lt;/style&amp;gt;
{% endblock %}

{% block content %}
{{ super() }}
&amp;lt;div id=&amp;quot;map&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script&amp;gt;
var pk = location.pathname.split('/').slice(-1)[0];
var tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    detectRetina: true,
    attribution: '&amp;amp;copy; &amp;lt;a href=&amp;quot;https://www.openstreetmap.org/copyright&amp;quot;&amp;gt;OpenStreetMap&amp;lt;/a&amp;gt; contributors, Points &amp;amp;copy 2012 LINZ'
});
var sql = 'select AsGeoJSON(Geometry) from timezones where PK_UID = ' + pk;
fetch('/timezones.json?sql=' + encodeURIComponent(sql)).then(r =&amp;gt; r.json()).then(d =&amp;gt; {
  var map = L.map('map', {layers: [tiles]});
  var geoJSON = JSON.parse(d.rows[0][0]);
  var layer = L.geoJSON(geoJSON)
  layer.addTo(map);
  map.fitBounds(layer.getBounds());
});
&amp;lt;/script&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a id="Publishing_it_to_the_internet_273"&gt;&lt;/a&gt;Publishing it to the internet&lt;/h3&gt;
&lt;p&gt;Normally we would use the &lt;code&gt;datasette publish&lt;/code&gt; command to publish our database to Heroku or Zeit Now, but the SpatiaLite dependency means that won’t work for this case. Instead, we need to construct a custom Dockerfile that builds the SpatiaLite module.&lt;/p&gt;
&lt;p&gt;Since we’re using Docker, we may as well have the Dockerfile download the shapefiles and build the SpatiaLite database for us all in one go. Here’s a Dockerfile that does exactly that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM python:3.6-slim-stretch

RUN apt update
RUN apt install -y python3-dev gcc spatialite-bin libsqlite3-mod-spatialite wget unzip

RUN pip install https://github.com/simonw/datasette/archive/master.zip

# Download and import the shapefiles
RUN wget https://github.com/evansiroky/timezone-boundary-builder/releases/download/2017c/timezones.shapefile.zip \
    &amp;amp;&amp;amp; unzip timezones.shapefile.zip &amp;amp;&amp;amp; \
    cd dist &amp;amp;&amp;amp; \
    echo &amp;quot;.loadshp combined_shapefile timezones CP1252 23032\nSELECT CreateSpatialIndex('timezones', 'geometry');&amp;quot; | spatialite timezones.db &amp;amp;&amp;amp; \
    mv timezones.db .. &amp;amp;&amp;amp; \
    cd .. &amp;amp;&amp;amp; rm -rf dist &amp;amp;&amp;amp; rm timezones.shapefile.zip

ENV SQLITE_EXTENSIONS /usr/lib/x86_64-linux-gnu/mod_spatialite.so

ADD metadata.json metadata.json

ADD templates templates

RUN datasette inspect timezones.db --inspect-file inspect-data.json

EXPOSE 8001

CMD [&amp;quot;datasette&amp;quot;, &amp;quot;serve&amp;quot;, &amp;quot;timezones.db&amp;quot;, &amp;quot;--host&amp;quot;, &amp;quot;0.0.0.0&amp;quot;, &amp;quot;--cors&amp;quot;, &amp;quot;--port&amp;quot;, &amp;quot;8001&amp;quot;, &amp;quot;--inspect-file&amp;quot;, &amp;quot;inspect-data.json&amp;quot;, &amp;quot;-m&amp;quot;, &amp;quot;metadata.json&amp;quot;, &amp;quot;--template-dir&amp;quot;, &amp;quot;templates&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full code, including the supporting templates, can be found in &lt;a href="https://github.com/simonw/timezones-api"&gt;simonw/timezones-api&lt;/a&gt; on GitHub.&lt;/p&gt;
&lt;p&gt;If you have Docker installed (&lt;a href="https://www.docker.com/docker-mac"&gt;Docker for Mac&lt;/a&gt; is a one-click install package these days, it’s impressively painless) you can build the container like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker build . -t timezones-api
# Wait for the image to build
docker run -p 8001:8001 timezones-api
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can visit &lt;code&gt;http://localhost:8001/&lt;/code&gt; to see your freshly built Datasette in your browser.&lt;/p&gt;
&lt;p&gt;The easiest way to publish it online is using &lt;a href="https://zeit.co/now"&gt;Zeit Now&lt;/a&gt;. Simply run the &lt;code&gt;now&lt;/code&gt; command in the directory containing the Dockerfile and Zeit will upload the entire directory, build the container in the cloud and deploy it with a fresh URL. It’s by far the easiest Docker deployment environment I’ve ever used.&lt;/p&gt;
&lt;p&gt;Now can even deploy directly from a public GitHub repository… so you can deploy your own copy of the API by running the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ now simonw/timezones-api
&amp;gt; Didn't find directory. Searching on GitHub...
&amp;gt; Deploying GitHub repository &amp;quot;simonw/timezones-api&amp;quot; under simonw
&amp;gt; Ready! https://simonw-timezones-api-fbihjcbnog.now.sh (copied to clipboard) [2s]
&amp;gt; Initializing…
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a id="Canned_queries__SpatiaLite__instant_geospatial_APIs_326"&gt;&lt;/a&gt;Canned queries + SpatiaLite = instant geospatial APIs&lt;/h3&gt;
&lt;p&gt;Hopefully this has helped illustrate the ease with which Datasette, SpatiaLite and canned queries can be used to create and publish geospatial APIs. Thanks to projects like OpenStreetMap the world is full of high quality open geospatial data. Go build something cool with it!&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/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/timezones"&gt;timezones&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/docker"&gt;docker&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&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="openstreetmap"/><category term="sqlite"/><category term="timezones"/><category term="docker"/><category term="datasette"/><category term="leaflet"/></entry><entry><title>simonepri/geo-maps</title><link href="https://simonwillison.net/2017/Nov/21/geo-maps/#atom-tag" rel="alternate"/><published>2017-11-21T16:06:38+00:00</published><updated>2017-11-21T16:06:38+00:00</updated><id>https://simonwillison.net/2017/Nov/21/geo-maps/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/simonepri/geo-maps"&gt;simonepri/geo-maps&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Neat project which publishes GeoJSON maps of the world automatically derived from OpenStreetMap. Three variants are available: country political maritime boundaries, country political coastline boundaries and a general outline of the world’s land territories.


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



</summary><category term="geospatial"/><category term="openstreetmap"/><category term="geojson"/></entry><entry><title>We Need to Stop Google's Exploitation of Open Communities</title><link href="https://simonwillison.net/2011/Apr/22/mapmaker/#atom-tag" rel="alternate"/><published>2011-04-22T10:00:00+00:00</published><updated>2011-04-22T10:00:00+00:00</updated><id>https://simonwillison.net/2011/Apr/22/mapmaker/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://brainoff.com/weblog/2011/04/11/1635"&gt;We Need to Stop Google&amp;#x27;s Exploitation of Open Communities&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Mikel Maron from OpenStreetMap is justifiably angry about Google MapMaker, which copies OpenStreetMap’s model of crowdsourcing geographic data (even copying the OSM idea of Mapping Parties) but keeps the data under a much more restrictive license, and uses the Google brand to market itself to African governments.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/google"&gt;google&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapmaker"&gt;mapmaker&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/recovered"&gt;recovered&lt;/a&gt;&lt;/p&gt;



</summary><category term="google"/><category term="mapmaker"/><category term="openstreetmap"/><category term="recovered"/></entry><entry><title>Polymaps</title><link href="https://simonwillison.net/2010/Aug/20/polymaps/#atom-tag" rel="alternate"/><published>2010-08-20T18:46:00+00:00</published><updated>2010-08-20T18:46:00+00:00</updated><id>https://simonwillison.net/2010/Aug/20/polymaps/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://polymaps.org/"&gt;Polymaps&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Absurdly classy: “a JavaScript library for image- and vector-tiled maps using SVG”. It can pull in image tiles from sources such as OpenStreetMap, then overlay SVG paths specified using GeoJSON. The demos make use of GeoJSON tiles for US states and counties hosted on AppEngine. The library is developed by Stamen and SimpleGeo, and released under a BSD license. SVG support in the browser is required.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/google-app-engine"&gt;google-app-engine&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/stamen-design"&gt;stamen-design&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/svg"&gt;svg&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/recovered"&gt;recovered&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geojson"&gt;geojson&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/polymaps"&gt;polymaps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/simplegeo"&gt;simplegeo&lt;/a&gt;&lt;/p&gt;



</summary><category term="google-app-engine"/><category term="javascript"/><category term="mapping"/><category term="openstreetmap"/><category term="stamen-design"/><category term="svg"/><category term="recovered"/><category term="geojson"/><category term="polymaps"/><category term="simplegeo"/></entry><entry><title>MapOSMatic</title><link href="https://simonwillison.net/2010/Jul/11/maposmatic/#atom-tag" rel="alternate"/><published>2010-07-11T12:15:00+00:00</published><updated>2010-07-11T12:15:00+00:00</updated><id>https://simonwillison.net/2010/Jul/11/maposmatic/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.maposmatic.org/"&gt;MapOSMatic&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Clever service built on top of OpenStreetMap, which renders double sided city maps with a map and grid on one size and an A-Z street name index on the other. Runs on top of Mapnik, PostGIS and Cairo, with a few thousand additional lines of Python and Django.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cairo"&gt;cairo&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/postgis"&gt;postgis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/postgresql"&gt;postgresql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/recovered"&gt;recovered&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/maposmatic"&gt;maposmatic&lt;/a&gt;&lt;/p&gt;



</summary><category term="cairo"/><category term="django"/><category term="mapping"/><category term="openstreetmap"/><category term="postgis"/><category term="postgresql"/><category term="python"/><category term="recovered"/><category term="maposmatic"/></entry><entry><title>getlatlon.com commit dae961a...</title><link href="https://simonwillison.net/2010/Jul/10/commit/#atom-tag" rel="alternate"/><published>2010-07-10T12:22:00+00:00</published><updated>2010-07-10T12:22:00+00:00</updated><id>https://simonwillison.net/2010/Jul/10/commit/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://github.com/simonw/getlatlon.com/commit/dae961a014979b711bbb36d0c6c129bcc37e7a4a"&gt;getlatlon.com commit dae961a...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I’ve finally added an OpenStreetMap tab to getlatlon.com—here’s the diff, it turns out adding a custom OpenStreetMap layer to an existing Google Maps application only takes a few lines of boilerplate code.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/getlatlon"&gt;getlatlon&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/google-maps"&gt;google-maps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/recovered"&gt;recovered&lt;/a&gt;&lt;/p&gt;



</summary><category term="getlatlon"/><category term="google-maps"/><category term="javascript"/><category term="openstreetmap"/><category term="projects"/><category term="recovered"/></entry><entry><title>Why Google MapMaker is not Open</title><link href="https://simonwillison.net/2010/Mar/16/mapmaker/#atom-tag" rel="alternate"/><published>2010-03-16T10:41:39+00:00</published><updated>2010-03-16T10:41:39+00:00</updated><id>https://simonwillison.net/2010/Mar/16/mapmaker/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://brainoff.com/weblog/2010/03/16/1541"&gt;Why Google MapMaker is not Open&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Non-commercial use only, strict attribution requirements and you aren’t allowed to use the data for services that might compete with Google. This is why I’m disappointed every time I see Google encouraging people to contribute to Map Make, especially in the developing world—if those people contributed to OpenStreetMap instead they would be building something far more valuable for their community.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/google"&gt;google&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapmaker"&gt;mapmaker&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mikel-maron"&gt;mikel-maron&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;&lt;/p&gt;



</summary><category term="google"/><category term="mapmaker"/><category term="mikel-maron"/><category term="openstreetmap"/></entry><entry><title>On walking into a disaster zone</title><link href="https://simonwillison.net/2010/Feb/10/iconoclast/#atom-tag" rel="alternate"/><published>2010-02-10T15:45:35+00:00</published><updated>2010-02-10T15:45:35+00:00</updated><id>https://simonwillison.net/2010/Feb/10/iconoclast/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.iconocla.st/index.cgi/2010/Feb/10#on-walking-into-a-disaster-zone"&gt;On walking into a disaster zone&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Schuyler Erle: “The World Bank was looking for technical GIS professionals, ideally French-speaking, to go and advise the government [...] I can sort of speak French. Sure, why not?”


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/haiti"&gt;haiti&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/schuylererle"&gt;schuylererle&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/worldbank"&gt;worldbank&lt;/a&gt;&lt;/p&gt;



</summary><category term="geospatial"/><category term="haiti"/><category term="openstreetmap"/><category term="schuylererle"/><category term="worldbank"/></entry><entry><title>OSM the default map in Haiti</title><link href="https://simonwillison.net/2010/Jan/25/haiti/#atom-tag" rel="alternate"/><published>2010-01-25T21:26:57+00:00</published><updated>2010-01-25T21:26:57+00:00</updated><id>https://simonwillison.net/2010/Jan/25/haiti/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.opengeodata.org/2010/01/24/osm-the-default-map-in-haiti/"&gt;OSM the default map in Haiti&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A search and rescue team member in Haiti sends word that digital maps constructed by the OpenStreetMap community are spreading by word of mouth and being loaded on to GPS units on the ground.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/gps"&gt;gps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/haiti"&gt;haiti&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/inspiring"&gt;inspiring&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;&lt;/p&gt;



</summary><category term="gps"/><category term="haiti"/><category term="inspiring"/><category term="mapping"/><category term="openstreetmap"/></entry><entry><title>The View from Above</title><link href="https://simonwillison.net/2009/Dec/11/imagery/#atom-tag" rel="alternate"/><published>2009-12-11T09:32:10+00:00</published><updated>2009-12-11T09:32:10+00:00</updated><id>https://simonwillison.net/2009/Dec/11/imagery/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.gravitystorm.co.uk/shine/archives/2009/12/09/the-view-from-above/"&gt;The View from Above&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Andy Allan’s notes on three different projects that aerial imagery with OpenStreetMap. Andy and friends hired a small plane and took their own aerial photographs of Stratford-upon-Avon as a demo for a GIS conference. Aid agencies in the Philippines benefitted from OSM and a donation of high quality satellite imagery. Rural Georgia now has hiqh quality images from 2007 thanks to the Department of Agriculture.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/aerialimagery"&gt;aerialimagery&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/andy-allan"&gt;andy-allan&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/satellites"&gt;satellites&lt;/a&gt;&lt;/p&gt;



</summary><category term="aerialimagery"/><category term="andy-allan"/><category term="mapping"/><category term="openstreetmap"/><category term="satellites"/></entry><entry><title>Quoting Ivan Sanchez</title><link href="https://simonwillison.net/2009/Nov/12/cake/#atom-tag" rel="alternate"/><published>2009-11-12T10:52:31+00:00</published><updated>2009-11-12T10:52:31+00:00</updated><id>https://simonwillison.net/2009/Nov/12/cake/#atom-tag</id><summary type="html">
    &lt;blockquote cite="http://www.opengeodata.org/2009/11/11/921/"&gt;&lt;p&gt;A set of geodata, or a map, is libre only if somebody can give you a cake with that map on top, as a present.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="http://www.opengeodata.org/2009/11/11/921/"&gt;Ivan Sanchez&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/caketest"&gt;caketest&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ivansanchez"&gt;ivansanchez&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;&lt;/p&gt;



</summary><category term="caketest"/><category term="geospatial"/><category term="ivansanchez"/><category term="mapping"/><category term="openstreetmap"/></entry><entry><title>Temporary Mapping: Solar Decathlon</title><link href="https://simonwillison.net/2009/Oct/13/temporary/#atom-tag" rel="alternate"/><published>2009-10-13T15:18:13+00:00</published><updated>2009-10-13T15:18:13+00:00</updated><id>https://simonwillison.net/2009/Oct/13/temporary/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://highearthorbit.com/temporary-mapping-solar-decathlon/"&gt;Temporary Mapping: Solar Decathlon&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The OpenStreetMap default renderer supports start_date and end_date tags, meaning you can map temporary installations (in this case the 2009 Solar Decathlon on the DC National Mall) and have them automatically appear and disappear at the correct times.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/andrew-turner"&gt;andrew-turner&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/solardecathlon"&gt;solardecathlon&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tagging"&gt;tagging&lt;/a&gt;&lt;/p&gt;



</summary><category term="andrew-turner"/><category term="mapping"/><category term="openstreetmap"/><category term="solardecathlon"/><category term="tagging"/></entry><entry><title>OSM static map api</title><link href="https://simonwillison.net/2009/Oct/12/osm/#atom-tag" rel="alternate"/><published>2009-10-12T13:37:42+00:00</published><updated>2009-10-12T13:37:42+00:00</updated><id>https://simonwillison.net/2009/Oct/12/osm/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://old-dev.openstreetmap.org/~pafciu17/"&gt;OSM static map api&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A very welcome addition to the OpenStreetMap world (with plenty of options for overlaying points, polygons etc) slightly marred by the size and relative ugliness of the OpenStreetMap watermark.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/apis"&gt;apis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/staticmaps"&gt;staticmaps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/watermarks"&gt;watermarks&lt;/a&gt;&lt;/p&gt;



</summary><category term="apis"/><category term="mapping"/><category term="openstreetmap"/><category term="staticmaps"/><category term="watermarks"/></entry><entry><title>OpenStreetMap Rendering Database</title><link href="https://simonwillison.net/2009/Oct/10/amazon/#atom-tag" rel="alternate"/><published>2009-10-10T13:05:43+00:00</published><updated>2009-10-10T13:05:43+00:00</updated><id>https://simonwillison.net/2009/Oct/10/amazon/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=2844"&gt;OpenStreetMap Rendering Database&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Amazon have added an OpenStreetMap snapshot as a public data set, thanks to some smart prompting by Jeremy Dunck.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/amazon"&gt;amazon&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ec2"&gt;ec2&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jeremy-dunck"&gt;jeremy-dunck&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/publicdatasets"&gt;publicdatasets&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/s3"&gt;s3&lt;/a&gt;&lt;/p&gt;



</summary><category term="amazon"/><category term="ec2"/><category term="jeremy-dunck"/><category term="mapping"/><category term="openstreetmap"/><category term="publicdatasets"/><category term="s3"/></entry><entry><title>openstreetmap genuine advantage</title><link href="https://simonwillison.net/2009/Sep/29/crypto/#atom-tag" rel="alternate"/><published>2009-09-29T09:49:52+00:00</published><updated>2009-09-29T09:49:52+00:00</updated><id>https://simonwillison.net/2009/Sep/29/crypto/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://mike.teczno.com/notes/gosm.html"&gt;openstreetmap genuine advantage&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The OpenStreetMap data model (points, ways and relations, all allowing arbitrary key/value tags) is a real thing of beauty—simple to understand but almost infinitely extensible. Mike Migurski’s latest project adds PGP signing to OpenStreetMap, allowing organisations (such as local government) to add a signature to a way (a sequence of points) and a subset of its tags, then write that signature in to a new tag on the object.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cryptography"&gt;cryptography&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/michal-migurski"&gt;michal-migurski&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pgp"&gt;pgp&lt;/a&gt;&lt;/p&gt;



</summary><category term="cryptography"/><category term="geospatial"/><category term="mapping"/><category term="michal-migurski"/><category term="openstreetmap"/><category term="pgp"/></entry><entry><title>"That's maybe a bit too dorky, even for us."</title><link href="https://simonwillison.net/2009/Sep/28/way/#atom-tag" rel="alternate"/><published>2009-09-28T22:39:27+00:00</published><updated>2009-09-28T22:39:27+00:00</updated><id>https://simonwillison.net/2009/Sep/28/way/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://code.flickr.com/blog/2009/09/28/thats-maybe-a-bit-too-dorky-even-for-us/"&gt;&amp;quot;That&amp;#x27;s maybe a bit too dorky, even for us.&amp;quot;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Astonishingly exciting: Flickr now have machine tag support for OpenStreetMap—tag a photo with osm:way=WAY_ID and Flickr will figure out what OSM feature you are talking about and link to it with a human readable description.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/flickr"&gt;flickr&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/machinetags"&gt;machinetags&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/photography"&gt;photography&lt;/a&gt;&lt;/p&gt;



</summary><category term="flickr"/><category term="machinetags"/><category term="openstreetmap"/><category term="photography"/></entry><entry><title>OpenStreetMap: QuadTiles</title><link href="https://simonwillison.net/2009/Sep/10/quadtiles/#atom-tag" rel="alternate"/><published>2009-09-10T15:54:36+00:00</published><updated>2009-09-10T15:54:36+00:00</updated><id>https://simonwillison.net/2009/Sep/10/quadtiles/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://wiki.openstreetmap.org/wiki/Quadtile"&gt;OpenStreetMap: QuadTiles&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Fascinating explanation of a proposal for replacing lat, lon pairs in the OpenStreetMap database with a QuadTile-based addressing system.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/algorithms"&gt;algorithms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/quadtiles"&gt;quadtiles&lt;/a&gt;&lt;/p&gt;



</summary><category term="algorithms"/><category term="geospatial"/><category term="openstreetmap"/><category term="quadtiles"/></entry><entry><title>Tile Drawer</title><link href="https://simonwillison.net/2009/Aug/26/tiledrawer/#atom-tag" rel="alternate"/><published>2009-08-26T09:32:56+00:00</published><updated>2009-08-26T09:32:56+00:00</updated><id>https://simonwillison.net/2009/Aug/26/tiledrawer/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://tiledrawer.com/"&gt;Tile Drawer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The most inspired use of EC2 I’ve seen yet: center a map on an area, pick a Cascadenik stylesheet URL (or write and link to your own) and Tile Drawer gives you an Amazon EC2 AMI and a short JSON snippet. Launch the AMI with the JSON as the “user data” parameter and you get your own OpenStreetMap tile rendering server, which self-configures on startup and starts rendering and serving tiles using your custom design.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://mike.teczno.com/notes/tile-drawer.html"&gt;Mike Migurski&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/amazon"&gt;amazon&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cascadenik"&gt;cascadenik&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cloud-computing"&gt;cloud-computing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ec2"&gt;ec2&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/json"&gt;json&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapnik"&gt;mapnik&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/michal-migurski"&gt;michal-migurski&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/userdata"&gt;userdata&lt;/a&gt;&lt;/p&gt;



</summary><category term="amazon"/><category term="cascadenik"/><category term="cloud-computing"/><category term="ec2"/><category term="json"/><category term="mapnik"/><category term="mapping"/><category term="michal-migurski"/><category term="openstreetmap"/><category term="userdata"/></entry><entry><title>Best of OpenStreetMap</title><link href="https://simonwillison.net/2009/Aug/13/osm/#atom-tag" rel="alternate"/><published>2009-08-13T12:30:05+00:00</published><updated>2009-08-13T12:30:05+00:00</updated><id>https://simonwillison.net/2009/Aug/13/osm/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://bestofosm.org/"&gt;Best of OpenStreetMap&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I keep on telling people OpenStreetMap is this year’s Wikipedia—at its best, it beats commercially available maps. This “best of” site highlights the areas where OSM really shines (the yellow stars)—the German mapping community in particular have produced some outstanding cartography.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://www.opengeodata.org/?p=647"&gt;OpenGeoData&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cartography"&gt;cartography&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/maps"&gt;maps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/wikipedia"&gt;wikipedia&lt;/a&gt;&lt;/p&gt;



</summary><category term="cartography"/><category term="mapping"/><category term="maps"/><category term="openstreetmap"/><category term="wikipedia"/></entry><entry><title>Hack Day tools for non-developers</title><link href="https://simonwillison.net/2009/Jul/28/tools/#atom-tag" rel="alternate"/><published>2009-07-28T14:23:53+00:00</published><updated>2009-07-28T14:23:53+00:00</updated><id>https://simonwillison.net/2009/Jul/28/tools/#atom-tag</id><summary type="html">
    &lt;p&gt;We're about to run our second internal hack day at the Guardian. The first was &lt;a href="http://www.guardian.co.uk/global/insideguardian/2008/nov/18/guardian-hack-day-results" title="Results from Hack Day at the Guardian"&gt;an enormous amount of fun&lt;/a&gt; and the second one looks set to be even more productive.&lt;/p&gt;

&lt;p&gt;There's only one rule at hack day: build something you can demonstrate at the end of the event (Powerpoint slides don't count). Importantly though, our hack days are not restricted to just our development team: anyone from the technology department can get involved, and we extend the invitation to other parts of the organisation as well. At the Guardian, this includes journalists.&lt;/p&gt;

&lt;p&gt;For our first hack day, I put together a list of "tools for non-developers" - sites, services and software that could be used for hacking without programming knowledge as a pre-requisite. I'm now updating that list with recommendations from elsewhere. Here's the list so far:&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://www.freebase.com/"&gt;Freebase&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Originally a kind of structured version of Wikipedia, Freebase changed its focus last year towards being a "social database about things you know and love". In other words, it's the most powerful OCD-enabler in the history of the world. Create your own "Base" on any subject you like, set up your own types and start gathering together topics from the millions already available in Freebase - or add your own. Examples include the &lt;a href="http://battlestargalactica.freebase.com/"&gt;Battlestar Galactica base&lt;/a&gt;, the &lt;a href="http://tallships.freebase.com/"&gt;Tall Ships base&lt;/a&gt; and the fabulous &lt;a href="http://database.freebase.com/"&gt;Database base&lt;/a&gt;. If you &lt;em&gt;are&lt;/em&gt; a developer the tools in the &lt;a href="http://www.freebase.com/make"&gt;Make Things with Freebase&lt;/a&gt; section are top notch.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://www.dabbledb.com/"&gt;Dabble DB&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Dabble is a weird combination of a spreadsheet, an online database and a set of visualisation tools. Watch the 8 minute demo to get an idea of how powerful this is - you can start off by loading in an existing spreadsheet and take it from there. You'll need to sign up for the free 30 day trial.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://docs.google.com/"&gt;Google Docs&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;You can always build a hack in Excel, but &lt;a href="http://docs.google.com/"&gt;Google Spreadsheets&lt;/a&gt; is surprisingly powerful and means that you can collaborate with others on your hack (including developers, who can use the Google Docs API to get at the data in your spreadsheet). Check out the following tutorials, which describe ways of using Google Spreadsheets to scrape in data from other webpages and output it in interesting formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://ouseful.wordpress.com/2008/10/14/data-scraping-wikipedia-with-google-spreadsheets/"&gt;Data Scraping Wikipedia with Google Spreadsheets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://ouseful.wordpress.com/2008/10/23/calling-amazon-associatesecommerce-web-services-from-a-google-spreadsheet/"&gt;Calling Amazon Associates/Ecommerce Web Services from a Google Spreadsheet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's also a simple way to &lt;a href="http://docs.google.com/support/bin/answer.py?hl=en&amp;amp;answer=87809"&gt;create a form&lt;/a&gt; that submits data in to a Google Spreadsheet.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://pipes.yahoo.com/"&gt;Yahoo! Pipes&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Visual tools for combining, filtering and modifying RSS feeds. Combine with the large number of &lt;a href="http://www.guardian.co.uk/help/insideguardian/2008/oct/22/full-fat-rss-feed-upgrade" title="Upgrading our RSS feeds"&gt;full-content feeds on guardian.co.uk&lt;/a&gt; for all sorts of interesting possibilities. Here's &lt;a href="http://ouseful.wordpress.com/2008/10/20/mashup-reuse-are-you-lazy-enough/" title="Mashup Reuse – Are You Lazy Enough?"&gt;a tutorial&lt;/a&gt; that incorporates Google Docs as well.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://maps.google.com/help/maps/mymaps/create.html"&gt;Google My Maps&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Google provide a really neat interface for adding your own points, lines and areas to a Google Map. Outputs KML, a handy file format for carting geographic data around between different tools.&lt;/p&gt;

&lt;p&gt;If you already have a KML or GeoRSS feed URL from somewhere (e.g. the output of a Yahoo! Pipe), you can paste it directly in to the Google Maps search box to see the points rendered on a map.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://sketchup.google.com/"&gt;Google SketchUp&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;A simple to use 3D drawing package that lets you create 3D models of real-world buildings and then import them in to &lt;a href="http://earth.google.com/"&gt;Google Earth&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://www.openstreetmap.org/"&gt;OpenStreetMap&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Try your hand at some open source cartography on OpenStreetMap, the geographic world's answer to Wikipedia. If you have the equipment you can contribute GPS traces, otherwise there's a clever online editor that will let you trace out roads from satellite photos - or you could just make sure your favourite pub is included on the map. The export tools can provide vector or static maps, and if you export as SVG you can further edit your map in Illustrator or Inkscape.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://maps.cloudmade.com/"&gt;CloudMade Maps&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Commercial tools built on top of &lt;a href="http://www.openstreetmap.org/"&gt;OpenStreetMap&lt;/a&gt;, the most exciting of which allows you to create your own map theme by setting your preferred colours and line widths for various types of map feature.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://manyeyes.alphaworks.ibm.com/manyeyes/"&gt;Many Eyes&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;IBM Research's suite of data visualisation tools, with a wiki-style collaboration platform for publishing data and creating visualisations.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://www.dapper.net/open/"&gt;Dapper&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Dapper provides a powerful tool for screen scraping websites, without needing to write any code. Output formats include RSS, iCalendar and Google Maps.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://www.tiddlywiki.com/"&gt;TiddlyWiki&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;TiddlyWiki is a complete wiki in a single HTML file, which you can save locally and use as a notebook, collaboration tool and much more. There's a large ecosystem of plugins and macros which can be used to extend it with new features - see &lt;a href="http://tiddlyvault.tiddlyspot.com/"&gt;TiddlyVault&lt;/a&gt; for an index.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://www.wolframalpha.com/"&gt;WolframAlpha&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;The "computational knowledge engine" with the &lt;a href="http://unqualified-reservations.blogspot.com/2009/07/wolfram-alpha-and-hubristic-user.html"&gt;hubristic search-based interface&lt;/a&gt;, potentially useful as a source of data and a tool for processing and visualising that data.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://www.tumblr.com/"&gt;Tumblr&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;Useful as both an input and an output for feeds processed using other tools, and with a smart bookmarklet for collecting bits and pieces from around the web.&lt;/p&gt;

&lt;h4&gt;&lt;a href="http://wiki.english.ucsb.edu/index.php/Toy_Chest_(Online_or_Downloadable_Tools_for_Building_Projects)"&gt;The UCSB Toy Chest&lt;/a&gt;&lt;/h4&gt;

&lt;p&gt;An outstanding list of tools that people "without programming skills (but with basic computer and Internet literacy) can use to create interesting projects", compiled by the English department at UC Santa Barbara.&lt;/p&gt;

&lt;h3&gt;Your help needed&lt;/h3&gt;

&lt;p&gt;There must be dozens, if not hundreds of useful tools missing from the above. Tell me in the comments and I'll add them to the list.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/freebase"&gt;freebase&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/google"&gt;google&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/google-maps"&gt;google-maps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/guardian"&gt;guardian&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/hackday"&gt;hackday&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/nondevelopers"&gt;nondevelopers&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openstreetmap"&gt;openstreetmap&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pipes"&gt;pipes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sketchup"&gt;sketchup&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tools"&gt;tools&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/yahoo-pipes"&gt;yahoo-pipes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/computer-literacy"&gt;computer-literacy&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="freebase"/><category term="google"/><category term="google-maps"/><category term="guardian"/><category term="hackday"/><category term="mapping"/><category term="nondevelopers"/><category term="openstreetmap"/><category term="pipes"/><category term="sketchup"/><category term="tools"/><category term="yahoo-pipes"/><category term="computer-literacy"/></entry></feed>