<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: mapping</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/mapping.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2024-11-19T23:39:18+00:00</updated><author><name>Simon Willison</name></author><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>Visualizing local election results with Datasette, Observable and MapLibre GL</title><link href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#atom-tag" rel="alternate"/><published>2024-11-09T23:32:06+00:00</published><updated>2024-11-09T23:32:06+00:00</updated><id>https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#atom-tag</id><summary type="html">
    &lt;p&gt;Alex Garcia and myself hosted the first &lt;a href="https://simonwillison.net/2024/Nov/7/datasette-public-office-hours/"&gt;Datasette Open Office Hours&lt;/a&gt; on Friday - a live-streamed video session where we hacked on a project together and took questions and tips from community members on Discord.&lt;/p&gt;
&lt;p&gt;We didn't record this one (surprisingly not a feature that Discord offers) but we hope to do more of these and record them in the future.&lt;/p&gt;
&lt;p&gt;This post is a detailed write-up of what we built during the session.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#san-mateo-county-election-results"&gt;San Mateo County election results&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#importing-csv-data-into-datasette"&gt;Importing CSV data into Datasette&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#modifying-the-schema"&gt;Modifying the schema&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#faceting-and-filtering-the-table"&gt;Faceting and filtering the table&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#importing-geospatial-precinct-shapes"&gt;Importing geospatial precinct shapes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#enriching-that-data-to-extract-the-precinct-ids"&gt;Enriching that data to extract the precinct IDs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#running-a-join"&gt;Running a join&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#creating-an-api-token-to-access-the-data"&gt;Creating an API token to access the data&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#getting-cors-working"&gt;Getting CORS working&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#working-with-datasette-in-observable"&gt;Working with Datasette in Observable&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#visualizing-those-with-maplibre-gl"&gt;Visualizing those with MapLibre GL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#observable-plot"&gt;Observable Plot&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#bringing-it-all-together"&gt;Bringing it all together&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Nov/9/visualizing-local-election-results/#we-ll-be-doing-this-again"&gt;We'll be doing this again&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h4 id="san-mateo-county-election-results"&gt;San Mateo County election results&lt;/h4&gt;
&lt;p&gt;I live in El Granada, a tiny town just north of Half Moon Bay in San Mateo County, California.&lt;/p&gt;
&lt;p&gt;Every county appears to handle counting and publishing election results differently. For San Mateo County the results are published &lt;a href="https://smcacre.gov/elections/november-5-2024-election-results"&gt;on this page&lt;/a&gt;, and detailed per-precinct and per-candidate breakdowns are made available as a CSV file.&lt;/p&gt;
&lt;p&gt;(I optimistically set up a &lt;a href="https://simonwillison.net/2020/Oct/9/git-scraping/"&gt;Git scraper&lt;/a&gt; for these results in &lt;a href="https://github.com/simonw/scrape-san-mateo-county-election-results-2024"&gt;simonw/scrape-san-mateo-county-election-results-2024&lt;/a&gt; only to learn that the CSV is updated just once a day, not continually as the ballots are counted.)&lt;/p&gt;
&lt;p&gt;I'm particularly invested in the results of the &lt;a href="http://granada.ca.gov/"&gt;Granada Community Services District&lt;/a&gt; board member elections. Our little town of El Granada is in "unincorporated San Mateo County" which means we don't have a mayor or any local officials, so the closest we get to hyper-local government is the officials that run our local sewage and parks organization! My partner Natalie ran &lt;a href="https://til.simonwillison.net/youtube/livestreaming"&gt;the candidate forum event&lt;/a&gt; (effectively the debate) featuring three of the four candidates running for the two open places on the board.&lt;/p&gt;
&lt;p&gt;Let's explore the data for that race using Datasette.&lt;/p&gt;
&lt;h4 id="importing-csv-data-into-datasette"&gt;Importing CSV data into Datasette&lt;/h4&gt;
&lt;p&gt;I ran my part of the demo using &lt;a href="https://www.datasette.cloud/"&gt;Datasette Cloud&lt;/a&gt;, the beta of my new hosted Datasette service.&lt;/p&gt;
&lt;p&gt;I started by using the pre-configured &lt;a href="https://github.com/datasette/datasette-import"&gt;datasette-import&lt;/a&gt; plugin to import the data from the CSV file into a fresh table:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/datasette-import-loop.gif" alt="Paste data to create a table - I drag and drop on a CSV file, which produces a preview of the first 100 of 15,589 rows. I click to Upload and a progress bar runs before redirecting me to the resulting table." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="modifying-the-schema"&gt;Modifying the schema&lt;/h4&gt;
&lt;p&gt;The table imported cleanly, but all of the columns from the CSV were still being treated as text. I used the &lt;a href=""&gt;datasette-edit-schema&lt;/a&gt; plugin to switch the relevant columns to integers so that we could run sums and sorts against them.&lt;/p&gt;
&lt;p&gt;(I also noted that I really should add a "detect column types" feature to that plugin!)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/datasette-edit-schema.jpg" alt="Edit table data/san_mateo_election_results - an option to rename table and then one to change existing columns, where each column is listed in turn and some have their type select box set to integer instead of the default of text" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;The resulting 15,589 rows represent counts from individual precincts around the county for each of the races and measures on the ballot, with a row per precinct per candidate/choice per race.&lt;/p&gt;
&lt;h4 id="faceting-and-filtering-the-table"&gt;Faceting and filtering the table&lt;/h4&gt;
&lt;p&gt;Since I'm interested in the Granada Community Services District election, I applied a facet on "Contest_title" and then used that to select that specific race.&lt;/p&gt;
&lt;p&gt;I applied additional facets on "candidate_name" and "Precinct name".&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/facet-candidates.jpg" alt="28 rows where Contest_title = Granada Community Services District Members, Board of Directors. Facets are precinct name (7 choices), candidate name (IRIS GRANT, JANET BRAYER, NANCY MARSH, WANDA BOWLES) and Contest_title" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;This looks right to me: we have 7 precincts and 4 candidates for 28 rows in total.&lt;/p&gt;
&lt;h4 id="importing-geospatial-precinct-shapes"&gt;Importing geospatial precinct shapes&lt;/h4&gt;
&lt;p&gt;Those precinct names are pretty non-descriptive! What does 33001 mean?&lt;/p&gt;
&lt;p&gt;To answer that question, I added a new table.&lt;/p&gt;
&lt;p&gt;San Mateo County offers &lt;a href="https://smcacre.gov/elections/precinct-maps-pdf"&gt;precinct maps&lt;/a&gt; in the form of 23 PDF files. Our precincts are in the "Unincorporated Coastside" file:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/precinct-map-from-pdf.jpg" alt="Screenshot from a PDF - label is Unincorporated Coastside, it shows the area north of Half Moon Bay with a bunch of polygons with numeric identifiers." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Thankfully the county &lt;em&gt;also&lt;/em&gt; makes that data available as &lt;a href="https://data.smcgov.org/Government/Election-Precincts/g5sj-6zp8/about_data"&gt;geospatial data&lt;/a&gt;, hosted using Socrata with an option to export as GeoJSON.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/precincts-socrata.jpg" alt="Socrata interface, Election Precincts updated March 7 2022 - 533 views, 72 downloads, and export dataset modal shows a GeoJSON option to export 783 rows." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;datasette-import&lt;/code&gt; plugin can handle JSON files... and if a JSON file contains a top-level object with a key that is an array of objects, it will import those objects as a table.&lt;/p&gt;
&lt;p&gt;Dragging that file into Datasette is enough to import it as a table with a &lt;code&gt;properties&lt;/code&gt; JSON column containing properties and a &lt;code&gt;geometry&lt;/code&gt; JSON columnn with the GeoJSON geometry.&lt;/p&gt;
&lt;p&gt;Here's where another plugin kicks in: &lt;a href="https://datasette.io/plugins/datasette-leaflet-geojson"&gt;datasette-leaflet-geojson&lt;/a&gt; looks for columns that contain valid GeoJSON geometries and... draws them on a map!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/precincts-in-datasette.jpg" alt="Datasette precincts table with 783 rows. The properties column contains JSON keys lastupdate, creationda, prencitid, notes and active - the geometry column renders maps with polygons showing the shape of the precinct." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;So now we can see the shape of the individual geometries.&lt;/p&gt;
&lt;h4 id="enriching-that-data-to-extract-the-precinct-ids"&gt;Enriching that data to extract the precinct IDs&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;precinctid&lt;/code&gt; is present in the data, but it's tucked away in a JSON object in that &lt;code&gt;properties&lt;/code&gt; JSON blob. It would be more convenient if it was a top-level column.&lt;/p&gt;
&lt;p&gt;Datasette's &lt;a href="https://simonwillison.net/2023/Dec/1/datasette-enrichments/"&gt;enrichments feature&lt;/a&gt; provides tools for running operations against every row in a table and adding new columns based on the results.&lt;/p&gt;
&lt;p&gt;My Datasette Cloud instance was missing the &lt;a href="https://github.com/datasette/datasette-enrichments-quickjs"&gt;datasette-enrichments-quickjs plugin&lt;/a&gt; that would let me run JavaScript code against the data. I used my privileged access on Datasette Cloud to add that plugin to my requirements and restarted the instance to install it.&lt;/p&gt;
&lt;p&gt;I used that to run this JavaScript code against every row in the table and saved the output in a new &lt;code&gt;precinct_id&lt;/code&gt; column:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;enrich&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;row&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;return&lt;/span&gt; &lt;span class="pl-c1"&gt;JSON&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;parse&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;row&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;properties&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;precinctid&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/enrich-precincts.jpg" alt="Enrich data in precincts. 783 rows selected. JavaScript. Enrich data with a custom JavaScript function. JavaScript function: function enrich(row) { return JSON.stringify(row) + &amp;quot; enriched&amp;quot;; } - Define an enrich(row) JavaScript function taking an object and returning a value. Row keys: properties, geometry. Output mode: store the function result in a single column. Output clumn name: precinct_id. The column to store the output in - will be created if it does not exist. Output column type: text." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;This took less than a second to run, adding and populating a new &lt;code&gt;precinct_id&lt;/code&gt; column for the table.&lt;/p&gt;
&lt;h4 id="running-a-join"&gt;Running a join&lt;/h4&gt;
&lt;p&gt;I demonstrated how to run a join between the election results and the precincts table using the Datasette SQL query editor.&lt;/p&gt;
&lt;p&gt;I tried a few different things, but the most interesting query was this one:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;select&lt;/span&gt;
  Precinct_name,
  &lt;span class="pl-c1"&gt;precincts&lt;/span&gt;.&lt;span class="pl-c1"&gt;geometry&lt;/span&gt;,
  total_ballots,
  json_group_object(
    candidate_name,
    total_votes
  ) &lt;span class="pl-k"&gt;as&lt;/span&gt; votes_by_candidate
&lt;span class="pl-k"&gt;from&lt;/span&gt;
  election_results 
  &lt;span class="pl-k"&gt;join&lt;/span&gt; precincts &lt;span class="pl-k"&gt;on&lt;/span&gt; &lt;span class="pl-c1"&gt;election_results&lt;/span&gt;.&lt;span class="pl-c1"&gt;Precinct_name&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;precincts&lt;/span&gt;.&lt;span class="pl-c1"&gt;precinct_id&lt;/span&gt;
&lt;span class="pl-k"&gt;where&lt;/span&gt; 
  Contest_title &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Granada Community Services District Members, Board of Directors&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;group by&lt;/span&gt; 
  Precinct_name,
  &lt;span class="pl-c1"&gt;precincts&lt;/span&gt;.&lt;span class="pl-c1"&gt;geometry&lt;/span&gt;,
  total_ballots;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/joined-precincts.jpg" alt="The SQL query returned four columns: Precinct_name, geometry with a map of the precinct, total_ballots with a number and votes_by_candidate with a JSON object mapping each candidate name to their number of votes." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="creating-an-api-token-to-access-the-data"&gt;Creating an API token to access the data&lt;/h4&gt;
&lt;p&gt;I was nearly ready to hand over to Alex for the second half of our demo, where he would use Observable Notebooks to build some custom visualizations on top of the data.&lt;/p&gt;
&lt;p&gt;A great pattern for this is to host the data in Datasette and then fetch it into Observable via the Datasette JSON API.&lt;/p&gt;
&lt;p&gt;Since Datasette Cloud instances are private by default we would need to create an API token that could do this.&lt;/p&gt;
&lt;p&gt;I used this interface (from the &lt;a href="https://github.com/simonw/datasette-auth-tokens"&gt;datasette-auth-tokens plugin&lt;/a&gt;) to create a new token with read-only access to all databases and tables in the instance:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/create-api-token.jpg" alt="Create an API token interface. This token will allow API access with the same abilities as your current user, swillison .Token will be restricted to: all databases and tables: view-database, all databases and tables: view-table, all databases and tables: execute-sql - token is set to read-only and never expires, a list of possible permissions with checkboxes is listed below." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Since we're running a dedicated instance just for Datasette Public Office Hours there's no reason not to distribute that read-only token in publically accessible code.&lt;/p&gt;
&lt;h4 id="getting-cors-working"&gt;Getting CORS working&lt;/h4&gt;
&lt;p&gt;Embarrassingly, I had forgotten that we would need CORS headers in order to access the data from an Observable notebook. Thankfully we have another plugin for that: &lt;a href="https://datasette.io/plugins/datasette-cors"&gt;datasette-cors&lt;/a&gt;. I installed that quickly and we confirmed that it granted access to the API from Observable as intended.&lt;/p&gt;
&lt;p&gt;I handed over to Alex for the next section of the demo.&lt;/p&gt;
&lt;h4 id="working-with-datasette-in-observable"&gt;Working with Datasette in Observable&lt;/h4&gt;
&lt;p&gt;Alex started by running a SQL query from client-side JavaScript to pull in the joined data for our specific El Granada race:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-s1"&gt;sql&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;`&lt;/span&gt;
&lt;span class="pl-s"&gt;select&lt;/span&gt;
&lt;span class="pl-s"&gt;  Precinct_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;  precincts.geometry,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Split_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Reporting_flag,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Update_count,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Pct_Id,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Pct_seq_nbr,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Reg_voters,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Turn_Out,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Contest_Id,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Contest_seq_nbr,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Contest_title,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Contest_party_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Selectable_Options,&lt;/span&gt;
&lt;span class="pl-s"&gt;  candidate_id,&lt;/span&gt;
&lt;span class="pl-s"&gt;  candidate_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Candidate_Type,&lt;/span&gt;
&lt;span class="pl-s"&gt;  cand_seq_nbr,&lt;/span&gt;
&lt;span class="pl-s"&gt;  Party_Code,&lt;/span&gt;
&lt;span class="pl-s"&gt;  total_ballots,&lt;/span&gt;
&lt;span class="pl-s"&gt;  total_votes,&lt;/span&gt;
&lt;span class="pl-s"&gt;  total_under_votes,&lt;/span&gt;
&lt;span class="pl-s"&gt;  total_over_votes,&lt;/span&gt;
&lt;span class="pl-s"&gt;  [Vote Centers_ballots],&lt;/span&gt;
&lt;span class="pl-s"&gt;  [Vote Centers_votes],&lt;/span&gt;
&lt;span class="pl-s"&gt;  [Vote Centers_under_votes],&lt;/span&gt;
&lt;span class="pl-s"&gt;  [Vote Centers_over_votes],&lt;/span&gt;
&lt;span class="pl-s"&gt;  [Vote by Mail_ballots],&lt;/span&gt;
&lt;span class="pl-s"&gt;  [Vote by Mail_votes],&lt;/span&gt;
&lt;span class="pl-s"&gt;  [Vote by Mail_under_votes],&lt;/span&gt;
&lt;span class="pl-s"&gt;  [Vote by Mail_over_votes]&lt;/span&gt;
&lt;span class="pl-s"&gt;from&lt;/span&gt;
&lt;span class="pl-s"&gt;  election_results join precincts on election_results.Precinct_name = precincts.precinct_id&lt;/span&gt;
&lt;span class="pl-s"&gt;where "Contest_title" = "Granada Community Services District Members, Board of Directors"&lt;/span&gt;
&lt;span class="pl-s"&gt;limit 101;`&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And in the next cell:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-s1"&gt;raw_data&lt;/span&gt; &lt;span class="pl-c1"&gt;=&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://datasette-public-office-hours.datasette.cloud/data/-/query.json?_shape=array&amp;amp;sql=&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&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;span class="pl-s1"&gt;    &lt;span class="pl-s1"&gt;sql&lt;/span&gt;&lt;/span&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-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-c1"&gt;headers&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;Authorization&lt;/span&gt;: &lt;span class="pl-s"&gt;`Bearer &lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;${&lt;/span&gt;&lt;span class="pl-s1"&gt;secret&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-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;then&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;r&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-s1"&gt;r&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;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note the &lt;code&gt;?_shape=array&lt;/code&gt; parameter there, which causes Datasette to output the results directly as a JSON array of objects.&lt;/p&gt;
&lt;p&gt;That's all it takes to get the data into Observable. Adding another cell like this confirms that the data is now available:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-v"&gt;Inputs&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;table&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;raw_data&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/inputs-table-raw-data.jpg" alt="An Observable cell running Inputs.table(raw_data) and displaying a table of Precinct_name and geometry columns, with GeoJSON" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="visualizing-those-with-maplibre-gl"&gt;Visualizing those with MapLibre GL&lt;/h4&gt;
&lt;p&gt;There are plenty of good options for visualizing GeoJSON data using JavaScript in an Observable notebook.&lt;/p&gt;
&lt;p&gt;Alex started with &lt;a href="https://maplibre.org/maplibre-gl-js/docs/"&gt;MapLibre GL&lt;/a&gt;, using the excellent &lt;a href="https://simonwillison.net/2024/Sep/28/openfreemap/"&gt;OpenFreeMap 3D tiles&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-s1"&gt;viewof&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-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-s1"&gt;const&lt;/span&gt; container &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;html&lt;/span&gt;&lt;span class="pl-s"&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;style&lt;/span&gt;="&lt;span class="pl-s"&gt;height:800px;&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;`&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;yield&lt;/span&gt; &lt;span class="pl-s1"&gt;container&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&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-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;container&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;value&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-s1"&gt;maplibregl&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;Map&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
    container&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;zoom&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-c"&gt;//style: "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json",&lt;/span&gt;
    &lt;span class="pl-c1"&gt;style&lt;/span&gt;: &lt;span class="pl-s"&gt;"https://tiles.openfreemap.org/styles/liberty"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;scrollZoom&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-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-k"&gt;yield&lt;/span&gt; &lt;span class="pl-s1"&gt;container&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;on&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"load"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;function&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;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;d3&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;geoBounds&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-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-c1"&gt;duration&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-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;addSource&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"precincts"&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;type&lt;/span&gt;: &lt;span class="pl-s"&gt;"geojson"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;data&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-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;addLayer&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;id&lt;/span&gt;: &lt;span class="pl-s"&gt;"precincts"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;type&lt;/span&gt;: &lt;span class="pl-s"&gt;"fill"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;source&lt;/span&gt;: &lt;span class="pl-s"&gt;"precincts"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;paint&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
        &lt;span class="pl-s"&gt;"fill-opacity"&lt;/span&gt;: &lt;span class="pl-c1"&gt;0.4&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
        &lt;span class="pl-s"&gt;"fill-color"&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;
          &lt;span class="pl-s"&gt;"case"&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-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-s"&gt;"get"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s"&gt;"ratio"&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;null&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-s"&gt;"#000000"&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-s"&gt;"interpolate"&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-s"&gt;"linear"&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-s"&gt;"get"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s"&gt;"ratio"&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;0.0&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s"&gt;"#0000ff"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
            &lt;span class="pl-c1"&gt;0.5&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s"&gt;"#d3d3d3"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
            &lt;span class="pl-c1"&gt;1.0&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s"&gt;"#ff0000"&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-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;"click"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s"&gt;"precincts"&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;e&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-k"&gt;const&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; precinct&lt;span class="pl-kos"&gt;,&lt;/span&gt; ratio &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;e&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;features&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;properties&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
      &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;description&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;JSON&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;stringify&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;new&lt;/span&gt; &lt;span class="pl-s1"&gt;maplibregl&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;Popup&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;setLngLat&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;e&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;lngLat&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;setHTML&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;description&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-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
  &lt;span class="pl-s1"&gt;invalidation&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;then&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-s1"&gt;map&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;remove&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;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/maplibre-gl.jpg" alt="An Observable cell showing a map of El Granada - a black shape shows the outlines of the precincts." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;(This is just one of several iterations, I didn't capture detailed notes of every change Alex made to the code.)&lt;/p&gt;
&lt;h4 id="observable-plot"&gt;Observable Plot&lt;/h4&gt;
&lt;p&gt;Observable notebooks come pre-loaded with the excellent Observable Plot charting library - Mike Bostock's high-level charting tool built on top of D3.&lt;/p&gt;
&lt;p&gt;Alex used that to first render the shapes of the precincts directly, without even needing a tiled basemap:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-v"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;plot&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  width&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;height&lt;/span&gt;: &lt;span class="pl-c1"&gt;600&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;legend&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-c1"&gt;projection&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;type&lt;/span&gt;: &lt;span class="pl-s"&gt;"conic-conformal"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;parallels&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;37&lt;/span&gt; &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-c1"&gt;4&lt;/span&gt; &lt;span class="pl-c1"&gt;/&lt;/span&gt; &lt;span class="pl-c1"&gt;60&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;38&lt;/span&gt; &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-c1"&gt;26&lt;/span&gt; &lt;span class="pl-c1"&gt;/&lt;/span&gt; &lt;span class="pl-c1"&gt;60&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;rotate&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-c1"&gt;120&lt;/span&gt; &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-c1"&gt;30&lt;/span&gt; &lt;span class="pl-c1"&gt;/&lt;/span&gt; &lt;span class="pl-c1"&gt;60&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;domain&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-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;marks&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;
    &lt;span class="pl-v"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;geo&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-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;strokeOpacity&lt;/span&gt;: &lt;span class="pl-c1"&gt;0.1&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;fill&lt;/span&gt;: &lt;span class="pl-s"&gt;"total_votes"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-en"&gt;title&lt;/span&gt;: &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;d&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-c1"&gt;JSON&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;stringify&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;d&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;properties&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;tip&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-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;parallels&lt;/code&gt; and &lt;code&gt;rotate&lt;/code&gt; options there come from the handy &lt;a href="https://github.com/veltman/d3-stateplane?tab=readme-ov-file#nad83--california-zone-3-epsg26943"&gt;veltman/d3-stateplane&lt;/a&gt; repo, which lists recommended settings for the &lt;a href="https://en.wikipedia.org/wiki/State_Plane_Coordinate_System"&gt;State Plane Coordinate System&lt;/a&gt; used with projections in D3. Those values are for &lt;a href="https://www.conservation.ca.gov/cgs/rgm/state-plane-coordinate-system"&gt;California Zone 3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/color-precincts.jpg" alt="An Observable cell shows six five distinct colored polygons, each for a different precinct. The shape of El Granada is clearly visible despite no other map tiles or labels." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="bringing-it-all-together"&gt;Bringing it all together&lt;/h4&gt;
&lt;p&gt;For the grand finale, Alex combined everything learned so far to build an interactive map allowing a user to select any of the 110 races on the ballot and see a heatmap of results for any selected candidate and option:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/select-map.gif" alt="Animated demo. Choose a contest select - picking different contests updates the map at the bottom. For each contest the candidates or options are shown as radio buttons, and selecting those updates the map to show a heatmap of votes for that candidate in different precincts." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;You can try this out in &lt;a href="https://observablehq.com/d/2ed2ad2443d7bbb5"&gt;Alex's notebook&lt;/a&gt;. Here's the relevant code (Observable cells are divided by &lt;code&gt;// ---&lt;/code&gt; comments). Note that Observable notebooks are reactive and allow variables to be referenced out of order.&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-c"&gt;// Select the contest&lt;/span&gt;
&lt;span class="pl-s1"&gt;viewof&lt;/span&gt; &lt;span class="pl-s1"&gt;contest&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;Inputs&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;select&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;contests&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;label&lt;/span&gt;: &lt;span class="pl-s"&gt;"Choose a contest"&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;// ---&lt;/span&gt;

&lt;span class="pl-c"&gt;// And the candidate&lt;/span&gt;
&lt;span class="pl-s1"&gt;viewof&lt;/span&gt;&lt;span class="pl-kos"&gt;&lt;/span&gt; &lt;span class="pl-s1"&gt;candidate&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;Inputs&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;radio&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
  &lt;span class="pl-s1"&gt;candidates&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;label&lt;/span&gt;: &lt;span class="pl-s"&gt;"Choose a candidate"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;value&lt;/span&gt;: &lt;span class="pl-s1"&gt;candidates&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-kos"&gt;)&lt;/span&gt;

&lt;span class="pl-c"&gt;// ---&lt;/span&gt;

&lt;span class="pl-c"&gt;// Show the map itself&lt;/span&gt;
&lt;span class="pl-v"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;plot&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  width&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;height&lt;/span&gt;: &lt;span class="pl-c1"&gt;600&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;legend&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-c1"&gt;color&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-c1"&gt;scheme&lt;/span&gt;: &lt;span class="pl-s"&gt;"blues"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;legend&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-c1"&gt;projection&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;type&lt;/span&gt;: &lt;span class="pl-s"&gt;"mercator"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;domain&lt;/span&gt;: &lt;span class="pl-s1"&gt;data2&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;marks&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;
    &lt;span class="pl-v"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;geo&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;data2&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;strokeOpacity&lt;/span&gt;: &lt;span class="pl-c1"&gt;0.1&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;fill&lt;/span&gt;: &lt;span class="pl-s"&gt;"ratio"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;tip&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-kos"&gt;}&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-c1"&gt;-&lt;/span&gt;
&lt;span class="pl-s1"&gt;data2&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-c1"&gt;type&lt;/span&gt;: &lt;span class="pl-s"&gt;"FeatureCollection"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-c1"&gt;features&lt;/span&gt;: &lt;span class="pl-s1"&gt;raw_data2&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-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;d&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-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-c1"&gt;type&lt;/span&gt;: &lt;span class="pl-s"&gt;"Feature"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-c1"&gt;properties&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;precinct&lt;/span&gt;: &lt;span class="pl-s1"&gt;d&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;Precinct_name&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;total_ballots&lt;/span&gt;: &lt;span class="pl-s1"&gt;d&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;total_ballots&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;ratio&lt;/span&gt;: &lt;span class="pl-c1"&gt;JSON&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;parse&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;d&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;votes_by_candidate&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;candidate&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-s1"&gt;d&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;total_ballots&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;geometry&lt;/span&gt;: &lt;span class="pl-c1"&gt;JSON&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;parse&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;d&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;geometry&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;// ---&lt;/span&gt;

&lt;span class="pl-s1"&gt;raw_data2&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;query&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
  &lt;span class="pl-s"&gt;`select&lt;/span&gt;
&lt;span class="pl-s"&gt;  Precinct_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;  precincts.geometry,&lt;/span&gt;
&lt;span class="pl-s"&gt;  total_ballots,&lt;/span&gt;
&lt;span class="pl-s"&gt;  json_grop_object(&lt;/span&gt;
&lt;span class="pl-s"&gt;    candidate_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;    total_votes&lt;/span&gt;
&lt;span class="pl-s"&gt;  ) as votes_by_candidate&lt;/span&gt;
&lt;span class="pl-s"&gt;from&lt;/span&gt;
&lt;span class="pl-s"&gt;  election_results &lt;/span&gt;
&lt;span class="pl-s"&gt;  join precincts on election_results.Precinct_name = precincts.precinct_id&lt;/span&gt;
&lt;span class="pl-s"&gt;where Contest_title = :contest&lt;/span&gt;
&lt;span class="pl-s"&gt;group by &lt;/span&gt;
&lt;span class="pl-s"&gt;  Precinct_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;  precincts.geometry,&lt;/span&gt;
&lt;span class="pl-s"&gt;  total_ballots;`&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-kos"&gt;{&lt;/span&gt; contest &lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-kos"&gt;)&lt;/span&gt;

&lt;span class="pl-c"&gt;// ---&lt;/span&gt;

&lt;span class="pl-s1"&gt;raw_data2&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;query&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
  &lt;span class="pl-s"&gt;`select&lt;/span&gt;
&lt;span class="pl-s"&gt;  Precinct_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;  precincts.geometry,&lt;/span&gt;
&lt;span class="pl-s"&gt;  total_ballots,&lt;/span&gt;
&lt;span class="pl-s"&gt;  json_group_object(&lt;/span&gt;
&lt;span class="pl-s"&gt;    candidate_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;    total_votes&lt;/span&gt;
&lt;span class="pl-s"&gt;  ) as votes_by_candidate&lt;/span&gt;
&lt;span class="pl-s"&gt;from&lt;/span&gt;
&lt;span class="pl-s"&gt;  election_results &lt;/span&gt;
&lt;span class="pl-s"&gt;  join precincts on election_results.Precinct_name = precincts.precinct_id&lt;/span&gt;
&lt;span class="pl-s"&gt;where Contest_title = :contest&lt;/span&gt;
&lt;span class="pl-s"&gt;group by &lt;/span&gt;
&lt;span class="pl-s"&gt;  Precinct_name,&lt;/span&gt;
&lt;span class="pl-s"&gt;  precincts.geometry,&lt;/span&gt;
&lt;span class="pl-s"&gt;  total_ballots;`&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-kos"&gt;{&lt;/span&gt; contest &lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-kos"&gt;)&lt;/span&gt;

&lt;span class="pl-c"&gt;// ---&lt;/span&gt;

&lt;span class="pl-c"&gt;// Fetch the available contests&lt;/span&gt;
&lt;span class="pl-s1"&gt;contests&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;query&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"select distinct Contest_title from election_results"&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;then&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;d&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-s1"&gt;d&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-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;d&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-s1"&gt;d&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;Contest_title&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;// ---&lt;/span&gt;

&lt;span class="pl-c"&gt;// Extract available candidates for selected contest&lt;/span&gt;

&lt;span class="pl-s1"&gt;candidates&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;Object&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;keys&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;
  &lt;span class="pl-c1"&gt;JSON&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;parse&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;raw_data2&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;votes_by_candidate&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;// ---&lt;/span&gt;

&lt;span class="pl-k"&gt;function&lt;/span&gt; &lt;span class="pl-en"&gt;query&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;sql&lt;/span&gt;&lt;span class="pl-kos"&gt;,&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-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-k"&gt;return&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://datasette-public-office-hours.datasette.cloud/data/-/query.json?&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&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&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; sql&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;_shape&lt;/span&gt;: &lt;span class="pl-s"&gt;"array"&lt;/span&gt;&lt;span class="pl-kos"&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&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-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&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;headers&lt;/span&gt;: &lt;span class="pl-kos"&gt;{&lt;/span&gt;
        &lt;span class="pl-c1"&gt;Authorization&lt;/span&gt;: &lt;span class="pl-s"&gt;`Bearer &lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;${&lt;/span&gt;&lt;span class="pl-s1"&gt;secret&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-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;then&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;r&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-s1"&gt;r&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-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="we-ll-be-doing-this-again"&gt;We'll be doing this again&lt;/h4&gt;
&lt;p&gt;This was our first time trying something like this and I think it worked &lt;em&gt;really&lt;/em&gt; well. We're already thinking about ways to improve it next time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I want to record these sessions and make them available on YouTube for people who couldn't be there live&lt;/li&gt;
&lt;li&gt;It would be fun to mix up the format. I'm particularly keen on getting more people involved giving demos - maybe having 5-10 minute lightning demo slots so we can see what other people are working on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep an eye on this blog or on the &lt;a href="https://datasette.io/discord"&gt;Datasette Discord&lt;/a&gt; for news about future sessions.&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/politics"&gt;politics&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette-cloud"&gt;datasette-cloud&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/alex-garcia"&gt;alex-garcia&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette-public-office-hours"&gt;datasette-public-office-hours&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="mapping"/><category term="politics"/><category term="projects"/><category term="datasette"/><category term="datasette-cloud"/><category term="alex-garcia"/><category term="datasette-public-office-hours"/><category term="leaflet"/></entry><entry><title>tiny-world-map</title><link href="https://simonwillison.net/2024/Apr/21/tiny-world-map/#atom-tag" rel="alternate"/><published>2024-04-21T22:11:14+00:00</published><updated>2024-04-21T22:11:14+00:00</updated><id>https://simonwillison.net/2024/Apr/21/tiny-world-map/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/tinyworldmap/tiny-world-map"&gt;tiny-world-map&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I love this project. It’s a JavaScript file (694K uncompressed, 283KB compressed) which can be used with the Leaflet mapping library and provides a SVG base map of the world with country borders and labels for every world city with a population more than 48,000—10,000 cities total.&lt;/p&gt;

&lt;p&gt;This means you can bundle an offline map of the world as part of any application that doesn’t need a higher level of detail. A lot of smaller island nations are missing entirely though, so this may not be right for every project.&lt;/p&gt;

&lt;p&gt;It even includes a service worker to help implement offline mapping support, plus several variants of the map with less cities that are even smaller.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=40104774"&gt;Hacker News&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/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/svg"&gt;svg&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/service-workers"&gt;service-workers&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="javascript"/><category term="mapping"/><category term="svg"/><category term="service-workers"/><category term="leaflet"/></entry><entry><title>DALL-E 3, GPT4All, PMTiles, sqlite-migrate, datasette-edit-schema</title><link href="https://simonwillison.net/2023/Oct/30/weeknotes/#atom-tag" rel="alternate"/><published>2023-10-30T23:59:12+00:00</published><updated>2023-10-30T23:59:12+00:00</updated><id>https://simonwillison.net/2023/Oct/30/weeknotes/#atom-tag</id><summary type="html">
    &lt;p&gt;I wrote a lot this week. I also did some fun research into new options for self-hosting vector maps and pushed out several new releases of plugins.&lt;/p&gt;
&lt;h4 id="user-content-on-the-blog"&gt;On the blog&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://simonwillison.net/2023/Oct/26/add-a-walrus/"&gt;Now add a walrus: Prompt engineering in DALL-E 3&lt;/a&gt; talked about my explorations of the new DALL-E 3 image generation model, including some reverse engineering showing how OpenAI prompt engineered ChatGPT to pass generate its own prompts for DALL-E 3. And a lot of pictures of pelicans. I also wrote a TIL about &lt;a href="https://til.simonwillison.net/css/simple-two-column-grid"&gt;the CSS grids I used in that post&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;a href="https://simonwillison.net/2023/Oct/26/llm-embed-jina/"&gt;Execute Jina embeddings with a CLI using llm-embed-jina&lt;/a&gt; I released &lt;a href="https://github.com/simonw/llm-embed-jina"&gt;a new plugin&lt;/a&gt; to run the new Jina AI 8K text embedding model using my &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; command-line tool.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://simonwillison.net/2023/Oct/23/embeddings/"&gt;Embeddings: What they are and why they matter&lt;/a&gt; is the big write-up of my talk about embeddings from PyBay this year. This has received a lot of traffic, presumably because it provides one of the more accessible answers to the question "what are embeddings?".&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="user-content-pmtiles-and-maplibre-gl"&gt;PMTiles and MapLibre GL&lt;/h4&gt;
&lt;p&gt;I saw a post about &lt;a href="https://protomaps.com/"&gt;Protomaps&lt;/a&gt; on &lt;a href="https://news.ycombinator.com/item?id=37982621"&gt;Hacker News&lt;/a&gt;. It's absolutely fantastic technology.&lt;/p&gt;
&lt;p&gt;The Protomaps &lt;a href="https://docs.protomaps.com/pmtiles/"&gt;PMTiles&lt;/a&gt; file format lets you bundle together vector tiles in a single file which is designed to be queried using HTTP range header requests.&lt;/p&gt;
&lt;p&gt;This means you can drop &lt;a href="https://maps.protomaps.com/builds/"&gt;a single 107GB file&lt;/a&gt; on cloud hosting and use it to efficiently serve vector maps to clients, fetching just the data they need for the current map area.&lt;/p&gt;
&lt;p&gt;Even better than that, you can create &lt;a href="https://docs.protomaps.com/guide/getting-started#_3-extract-any-area"&gt;your own subset&lt;/a&gt; of the larger map covering just the area you care about.&lt;/p&gt;
&lt;p&gt;I tried this out against my hometown of Half Moon Bay ond get a building-outline-level vector map for the whole town in just a 2MB file!&lt;/p&gt;
&lt;p&gt;You can see the result (which also includes business listing markers &lt;a href="https://til.simonwillison.net/overture-maps/overture-maps-parquet#user-content-exporting-the-places-to-sqlite"&gt;from Overture maps&lt;/a&gt;) at &lt;strong&gt;&lt;a href="https://simonw.github.io/hmb-map/"&gt;simonw.github.io/hmb-map&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2023/protomaps.jpg" alt="A vector map of El Granada showing the area around the harbor, with lots of little markers for different businesses. Protomaps (c) OpenStreetMap in the corner." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Lots more details of how I built this, including using Vite as a build tool and the &lt;a href="https://maplibre.org/"&gt;MapLibre GL&lt;/a&gt; JavaScript library to serve the map, in my TIL &lt;a href="https://til.simonwillison.net/gis/pmtiles"&gt;Serving a custom vector web map using PMTiles and maplibre-gl&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I'm so excited about this: we now have the ability to entirely self-host vector maps of any location in the world, using openly licensed data, without depending on anything other than our own static file hosting web server.&lt;/p&gt;
&lt;h4 id="user-content-llm-gpt4all"&gt;llm-gpt4all&lt;/h4&gt;
&lt;p&gt;This was a tiny release - literally a &lt;a href="https://github.com/simonw/llm-gpt4all/commit/377ebf5c911e1a6bb8039a23c3ca37bcf83a1b79#diff-945dfb6aca00ffce39b7f0152bb540fce2d1ed1bb569a7a2688f2f9fb0aeb0d2"&gt;one line code change&lt;/a&gt; - with a huge potential impact.&lt;/p&gt;
&lt;p&gt;Nomic AI's &lt;a href="https://gpt4all.io/"&gt;GPT4All&lt;/a&gt; is a really cool project. They describe their focus as "a free-to-use, locally running, privacy-aware chatbot. No GPU or internet required." - they've taken &lt;a href="https://github.com/ggerganov/llama.cpp"&gt;llama.cpp&lt;/a&gt; (and other libraries) and wrapped them in a much nicer experience, complete with Windows, macOS and Ubuntu installers.&lt;/p&gt;
&lt;p&gt;Under the hood it's mostly Python, and Nomic have done a fantastic job releasing that Python core as an &lt;a href="https://docs.gpt4all.io/gpt4all_python.html"&gt;installable Python package&lt;/a&gt; - meaning you can literally &lt;code&gt;pip install gpt4all&lt;/code&gt; to get almost everything you need to run a local language model!&lt;/p&gt;
&lt;p&gt;Unlike alternative Python libraries &lt;a href="https://llm.mlc.ai/docs/install/mlc_llm.html"&gt;MLC&lt;/a&gt; and &lt;a href="https://pypi.org/project/llama-cpp-python/"&gt;llama-cpp-python&lt;/a&gt;, Nomic have done the work to publish compiled binary wheels to PyPI... which means &lt;code&gt;pip install gpt4all&lt;/code&gt; works without needing a compiler toolchain or any extra steps!&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; tool has had a &lt;a href="https://github.com/simonw/llm-gpt4all"&gt;llm-gpt4all&lt;/a&gt; plugin since I first added alternative model backends via plugins &lt;a href="https://simonwillison.net/2023/Jul/12/llm/"&gt;in July&lt;/a&gt;. Unfortunately, it spat out weird debugging information that I had been unable to hide (a problem that &lt;a href="https://github.com/simonw/llm-llama-cpp/issues/22"&gt;still affects llm-llama-cpp&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Nomic have &lt;a href="https://github.com/nomic-ai/gpt4all/issues/1159"&gt;fixed this&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;As a result, &lt;code&gt;llm-gpt4all&lt;/code&gt; is now my recommended plugin for getting started running local LLMs:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;pipx install llm
llm install llm-gpt4all
llm -m mistral-7b-instruct-v0 &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;ten facts about pelicans&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The latest plugin can also now use the GPU on macOS, a key feature of Nomic's &lt;a href="https://blog.nomic.ai/posts/gpt4all-gpu-inference-with-vulkan"&gt;big release in September&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="user-content-sqlite-migrate"&gt;sqlite-migrate&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/sqlite-migrate"&gt;sqlite-migrate&lt;/a&gt; is my plugin that adds a simple migration system to &lt;a href="https://sqlite-utils.datasette.io/"&gt;sqlite-utils&lt;/a&gt;, for applying changes to a database schema in a controlled, repeatable way.&lt;/p&gt;
&lt;p&gt;Alex Garcia spotted &lt;a href="https://github.com/simonw/sqlite-migrate/issues/11"&gt;a bug&lt;/a&gt; in the way it handled multiple migration sets with overlapping migration names, which is now fixed in &lt;a href="https://github.com/simonw/sqlite-migrate/releases/tag/0.1b0"&gt;sqlite-migrate 0.1b0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ironically the fix involved changing the schema of the &lt;code&gt;_sqlite_migrations&lt;/code&gt; table used to track which migrations have been applied... which is the one part of the system that isn't itself managed by its own migration system! I had to implement &lt;a href="https://github.com/simonw/sqlite-migrate/blob/613ecd5c4aa8493525879d2db7363fa5bfbe4ffb/sqlite_migrate/__init__.py#L103-L105"&gt;a conditional check&lt;/a&gt; instead that checks if the table needs to be updated.&lt;/p&gt;
&lt;p&gt;A &lt;a href="https://news.ycombinator.com/item?id=38036921"&gt;recent thread about SQLite&lt;/a&gt; on Hacker News included a surprising number of complaints about the difficulty of running migrations, due to the lack of features of the core &lt;code&gt;ALTER TABLE&lt;/code&gt; implementation.&lt;/p&gt;
&lt;p&gt;The combination &lt;code&gt;sqlite-migrate&lt;/code&gt; and the &lt;a href="https://sqlite-utils.datasette.io/en/stable/python-api.html#python-api-transform"&gt;table.transform() method&lt;/a&gt; in &lt;code&gt;sqlite-utils&lt;/code&gt; offers a pretty robust solution to this problem. Clearly I need to put more work into promoting it!&lt;/p&gt;
&lt;h4 id="user-content-homebrew-trouble-for-llm"&gt;Homebrew trouble for LLM&lt;/h4&gt;
&lt;p&gt;I started getting confusing bug reports for my various LLM projects, all of which boiled down to a failure to install plugins that depended on PyTorch.&lt;/p&gt;
&lt;p&gt;It turns out the LLM package for Homebrew &lt;a href="https://github.com/Homebrew/homebrew-core/pull/151467"&gt;upgraded to Python 3.12&lt;/a&gt; last week... but PyTorch &lt;a href="https://github.com/pytorch/pytorch/issues/110436"&gt;isn't yet available for Python 3.12&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This means that while base LLM installed from Homebrew works fine, attempts to install things like my new &lt;a href="https://github.com/simonw/llm-embed-jina"&gt;llm-embed-jina&lt;/a&gt; plugin fail with &lt;a href="https://github.com/simonw/llm-embed-jina/issues/5"&gt;weird errors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I'm not sure the best way to address this. For the moment I've removed the recommendation to install using Homebrew and replaced it with &lt;a href="https://pypa.github.io/pipx/"&gt;pipx&lt;/a&gt; in a few places. I have &lt;a href="https://github.com/simonw/llm/issues/315"&gt;an open issue&lt;/a&gt; to find a better solution for this.&lt;/p&gt;
&lt;p&gt;The difficulty of debugging this issue prompted me to ship a new plugin that I've been contemplating for a while: &lt;a href="https://github.com/simonw/llm-python"&gt;llm-python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Installing this plugin adds a new &lt;code&gt;llm python&lt;/code&gt; command, which runs a Python interpreter in same virtual environment as LLM - useful for if you installed LLM via &lt;code&gt;pipx&lt;/code&gt; or Homebrew and don't know where that virtual environment is located.&lt;/p&gt;
&lt;p&gt;It's great for debugging: I can ask people to run &lt;code&gt;llm python -c 'import sys; print(sys.path)'&lt;/code&gt; for example to figure out what their Python path looks like.&lt;/p&gt;
&lt;p&gt;It's also promising as a tool for future tutorials about the &lt;a href="https://llm.datasette.io/en/stable/python-api.html"&gt;LLM Python library&lt;/a&gt;. I can tell people to &lt;code&gt;pipx install llm&lt;/code&gt; and then run &lt;code&gt;llm python&lt;/code&gt; to get a Python interpreter with the library already installed, without them having to mess around with virtual environments directly.&lt;/p&gt;
&lt;h4 id="user-content-add-and-remove-indexes-in-datasette-edit-schema"&gt;Add and remove indexes in datasette-edit-schema&lt;/h4&gt;
&lt;p&gt;We're iterating on Datasette Cloud based on feedback from people using the preview. One request was the ability to add and remove indexes from larger tables, to help speed up faceting.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/datasette-edit-schema/releases/tag/0.7"&gt;datasette-edit-schema 0.7&lt;/a&gt; adds that feature.&lt;/p&gt;
&lt;p&gt;That plugin &lt;a href="https://github.com/simonw/datasette-edit-schema/blob/main/update-screenshot.sh"&gt;includes this script&lt;/a&gt; for automatically updating the screenshot in the README using &lt;a href="https://shot-scraper.datasette.io/"&gt;shot-scraper&lt;/a&gt;. Here's the latest result:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2023/datasette-edit-schema.png" alt="Screenshot of the edit schema UI - you can rename a table, change existing columns, add a column, update foreign key relationships, change the primary key, delete the table and now edit the table indexes." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="user-content-releases-this-week"&gt;Releases this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/sqlite-migrate/releases/tag/0.1b0"&gt;sqlite-migrate 0.1b0&lt;/a&gt;&lt;/strong&gt; - 2023-10-27&lt;br /&gt;A simple database migration system for SQLite, based on sqlite-utils&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/llm-python/releases/tag/0.1"&gt;llm-python 0.1&lt;/a&gt;&lt;/strong&gt; - 2023-10-27&lt;br /&gt;"llm python" is a command to run a Python interpreter in the LLM virtual environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/llm-embed-jina/releases/tag/0.1.2"&gt;llm-embed-jina 0.1.2&lt;/a&gt;&lt;/strong&gt; - 2023-10-26&lt;br /&gt;Embedding models from Jina AI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-edit-schema/releases/tag/0.7"&gt;datasette-edit-schema 0.7&lt;/a&gt;&lt;/strong&gt; - 2023-10-26&lt;br /&gt;Datasette plugin for modifying table schemas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-ripgrep/releases/tag/0.8.2"&gt;datasette-ripgrep 0.8.2&lt;/a&gt;&lt;/strong&gt; - 2023-10-25&lt;br /&gt;Web interface for searching your code using ripgrep, built as a Datasette plugin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/llm-gpt4all/releases/tag/0.2"&gt;llm-gpt4all 0.2&lt;/a&gt;&lt;/strong&gt; - 2023-10-24&lt;br /&gt;Plugin for LLM adding support for the GPT4All collection of models&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="user-content-til-this-week"&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://til.simonwillison.net/css/simple-two-column-grid"&gt;A simple two column CSS grid&lt;/a&gt; - 2023-10-27&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://til.simonwillison.net/gis/pmtiles"&gt;Serving a custom vector web map using PMTiles and maplibre-gl&lt;/a&gt; - 2023-10-24&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://til.simonwillison.net/github-actions/vite-github-pages"&gt;Serving a JavaScript project built using Vite from GitHub Pages&lt;/a&gt; - 2023-10-24&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/overture"&gt;overture&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="mapping"/><category term="projects"/><category term="weeknotes"/><category term="sqlite-utils"/><category term="llm"/><category term="overture"/></entry><entry><title>GPSJam</title><link href="https://simonwillison.net/2022/Jul/30/gpsjam/#atom-tag" rel="alternate"/><published>2022-07-30T19:51:31+00:00</published><updated>2022-07-30T19:51:31+00:00</updated><id>https://simonwillison.net/2022/Jul/30/gpsjam/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://gpsjam.org/"&gt;GPSJam&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
John Wiseman’s “Daily maps of GPS interference” —a beautiful interactive globe (powered by Mapbox GL) which you can use to see points of heaviest GPS interference over a 24 hour period, using data collected from commercial airline radios by ADS-B Exchange. “From what I can tell the most common reason for aircraft GPS systems to have degraded accuracy is jamming by military systems. At least, the vast majority of aircraft that I see with bad GPS accuracy are flying near conflict zones where GPS jamming is known to occur.”

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/lemonodor/status/1553097820860600320"&gt;@lemonodor&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/gps"&gt;gps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/john-wiseman"&gt;john-wiseman&lt;/a&gt;&lt;/p&gt;



</summary><category term="geospatial"/><category term="gps"/><category term="mapping"/><category term="john-wiseman"/></entry><entry><title>Serving map tiles from SQLite with MBTiles and datasette-tiles</title><link href="https://simonwillison.net/2021/Feb/4/datasette-tiles/#atom-tag" rel="alternate"/><published>2021-02-04T01:09:30+00:00</published><updated>2021-02-04T01:09:30+00:00</updated><id>https://simonwillison.net/2021/Feb/4/datasette-tiles/#atom-tag</id><summary type="html">
    &lt;p&gt;Working on &lt;a href="https://simonwillison.net/2021/Jan/31/weeknotes/"&gt;datasette-leaflet&lt;/a&gt; last week re-kindled my interest in using Datasette as a GIS (Geographic Information System) platform. SQLite already has strong GIS functionality in the form of &lt;a href="https://docs.datasette.io/en/stable/spatialite.html"&gt;SpatiaLite&lt;/a&gt; and &lt;a href="https://datasette.io/plugins/datasette-cluster-map"&gt;datasette-cluster-map&lt;/a&gt; is currently the &lt;a href="https://datasette.io/plugins?sort=downloads-this-week"&gt;most downloaded&lt;/a&gt; plugin. Most importantly, maps are fun!&lt;/p&gt;
&lt;h4&gt;MBTiles&lt;/h4&gt;
&lt;p&gt;I was talking to &lt;a href="https://macwright.com/"&gt;Tom MacWright&lt;/a&gt; on Monday and I mentioned that I'd been thinking about how SQLite might make a good mechanism for distributing tile images for use with libraries like Leaflet. "I might be able to save you some time there" he said... and he showed me &lt;a href="https://github.com/mapbox/mbtiles-spec"&gt;MBTiles&lt;/a&gt;, a specification he started developing ten years ago at Mapbox which does exactly that - bundles tile images up in SQLite databases.&lt;/p&gt;
&lt;p&gt;(My best guess is I read about MBTiles a while ago, then managed to forget about the spec entirely while the idea of using SQLite for tile distribution wedged itself in my head somewhere.)&lt;/p&gt;
&lt;h4&gt;The new datasette-tiles plugin&lt;/h4&gt;
&lt;p&gt;I found some example MBTiles files on the internet and started playing around with them. My first prototype used the &lt;a href="https://datasette.io/plugins/datasette-media"&gt;datasette-media&lt;/a&gt; plugin, described here previously in &lt;a href="https://simonwillison.net/2020/Jul/30/fun-binary-data-and-sqlite/"&gt;Fun with binary data and SQLite&lt;/a&gt;. I used some convoluted SQL to teach it that hits to &lt;code&gt;/-/media/tiles/{z},{x},{y}&lt;/code&gt; should serve up content from the &lt;code&gt;tiles&lt;/code&gt; table in my MBTiles database - you can see details of that prototype in &lt;a href="https://til.simonwillison.net/datasette/serving-mbtiles"&gt;this TIL: Serving MBTiles with datasette-media&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The obvious next step was to write a dedicated plugin: &lt;a href="https://datasette.io/plugins/datasette-tiles"&gt;datasette-tiles&lt;/a&gt;. Install it and run Datasette against any MBTiles database file and the plugin will set up a &lt;code&gt;/-/tiles/db-name/z/x/y.png&lt;/code&gt; endpoint that serves the specified tiles.&lt;/p&gt;
&lt;p&gt;It also adds a tile explorer view with a pre-configured Leaflet map. Here's &lt;a href="https://datasette-tiles-demo.datasette.io/-/tiles/japan-toner"&gt;a live demo&lt;/a&gt; serving up a subset of Stamen's toner map - just zoom levels 6 and 7 for the country of Japan.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://datasette-tiles-demo.datasette.io/-/tiles/japan-toner"&gt;&lt;img alt="The tile explorer showing a toner map for Japan" src="https://static.simonwillison.net/static/2021/datasette-tiles-japan-toner-demo.png" style="max-width:100%;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here's how to run this on your own computer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Install Datasette
brew install datasette
# Install the plugin
datasette install datasette-tiles
# Download the japan-toner.db database
curl -O https://datasette-tiles-demo.datasette.io/japan-toner.db
# Launch Datasette and open a browser
datasette japan-toner.db -o
# Use the cog menu to access the tile explorer
# Or visit http://127.0.0.1:8001/-/tiles/japan-toner
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Creating MBTiles files with my download-tiles tool&lt;/h4&gt;
&lt;p&gt;A sticking point when I started playing with MBTiles was finding example files to work with.&lt;/p&gt;
&lt;p&gt;After some digging, I came across the amazing &lt;a href="https://export.hotosm.org/en/v3/"&gt;HOT Export Tool&lt;/a&gt;. It's a project by the &lt;a href="https://www.hotosm.org/"&gt;Humanitarian OpenStreetMap Team&lt;/a&gt; that allows anyone to export subsets of data from OpenStreetMap in a wide variety of formats, including MBTiles.&lt;/p&gt;
&lt;p&gt;I filed &lt;a href="https://github.com/hotosm/osm-export-tool/issues/371"&gt;a minor bug report&lt;/a&gt; against it, and in doing so took a look at the source code (it's all open source)... and found &lt;a href="https://github.com/hotosm/osm-export-tool-python/blob/8e4165a454303abbea2bd18cf5ffcdd5b9d0370d/osm_export_tool/nontabular.py#L103-L108"&gt;the code that assembles MBTiles files&lt;/a&gt;. It uses another open source library called &lt;a href="https://github.com/makinacorpus/landez"&gt;Landez&lt;/a&gt;, which provides functions for downloading tiles from existing providers and bundling those up as an MBTiles SQLite file.&lt;/p&gt;
&lt;p&gt;I prefer command-line tools for this kind of thing over using Python libraries directly, so I fired up my &lt;a href="https://github.com/simonw/click-app"&gt;click-app cookiecutter template&lt;/a&gt; and built a thin command-line interface over the top of the library.&lt;/p&gt;
&lt;p&gt;The new tool is called &lt;a href="https://datasette.io/tools/download-tiles"&gt;download-tiles&lt;/a&gt; and it does exactly that: downloads tiles from a tile server and creates an MBTiles SQLite database on disk containing those tiles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please use this tool responsibly&lt;/strong&gt;. Downloading large numbers of tiles is bad manners. Be sure to familiarize yourself with the &lt;a href="https://operations.osmfoundation.org/policies/tiles/"&gt;OpenStreetMap Tile Usage Policy&lt;/a&gt;, and use the tool politely when pointing it at other tile servers.&lt;/p&gt;
&lt;p&gt;Basic usage is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;download-tiles world.mbtiles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default the tool pulls tiles from OpenStreetMap. The above command will fetch zoom levels 0-3 of the entire world - 85 tiles total, well within acceptable usage limits.&lt;/p&gt;
&lt;p&gt;Various options (described in &lt;a href="https://datasette.io/tools/download-tiles"&gt;the README&lt;/a&gt;) can be used to customize the tiles that are downloaded. Here's how I created the &lt;a href="https://datasette-tiles-demo.datasette.io/japan-toner"&gt;japan-toner.db&lt;/a&gt; demo database, linked to above:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;download-tiles japan-toner.mbtiles \ 
    --zoom-levels 6-7 \
    --country Japan \
    --tiles-url "http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png" \
    --tiles-subdomains "a,b,c,d" \
    --attribution 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA.'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;--country Japan&lt;/code&gt; option here looks up the bounding box for Japan &lt;a href="https://nominatim.openstreetmap.org/ui/search.html?country=japan"&gt;using Nominatim&lt;/a&gt;. &lt;code&gt;--zoom-levels 6-7&lt;/code&gt; fetches zoom levels 6 and 7 (in this case that makes for 193 tiles total). &lt;code&gt;--tiles-url&lt;/code&gt; and &lt;code&gt;--tiles-subdomain&lt;/code&gt; configure the tile server to fetch them from. The &lt;code&gt;--attribution&lt;/code&gt; option bakes that string into the &lt;a href="https://datasette-tiles-demo.datasette.io/japan-toner/metadata"&gt;metadata table&lt;/a&gt; for the database - which is then used to display it correctly in the tile explorer (and eventually in other Datasette plugins).&lt;/p&gt;
&lt;h4&gt;datasette-basemap&lt;/h4&gt;
&lt;p&gt;Out of the box, Datasette's current Leaflet plugins (&lt;a href="https://datasette.io/plugins/datasette-cluster-map"&gt;datasette-cluster-map&lt;/a&gt;, &lt;a href="https://datasette.io/plugins/datasette-leaflet-geojson"&gt;datasette-leaflet-geojson&lt;/a&gt; and so on) serve tiles directly from the OpenStreetMap tile server.&lt;/p&gt;
&lt;p&gt;I've never felt particularly comfortable about this. Users can configure the plugins to run against other tile servers, but pointing to OpenStreetMap as a default was the easiest way to ensure these plugins would work for people who just wanted to try them out.&lt;/p&gt;
&lt;p&gt;Now that I have the tooling for bundling map subsets, maybe I can do better.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://datasette.io/plugins/datasette-basemap"&gt;datasette-basemap&lt;/a&gt; offers an alternative: it's a plugin that bundles a 22.7MB SQLite file containing zoom levels 0-6 of OpenStreetMap - &lt;a href="https://datasette-tiles-demo.datasette.io/basemap/tiles?_facet=zoom_level"&gt;5,461 tiles total&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;pip install datasette-basemap&lt;/code&gt; (or &lt;code&gt;datasette install datasette-basemap&lt;/code&gt;) will install the plugin, complete with that database - and register it with Datasette.&lt;/p&gt;
&lt;p&gt;Start Datasette with the plugin installed and &lt;code&gt;/basemap&lt;/code&gt; will expose &lt;a href="https://datasette-tiles-demo.datasette.io/basemap"&gt;the bundled database&lt;/a&gt;. Install &lt;code&gt;datasette-tiles&lt;/code&gt; and you'll be able to browse it as a tile server: &lt;a href="https://datasette-tiles-demo.datasette.io/-/tiles/basemap"&gt;here's a demo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(I recommend also installing &lt;a href="https://datasette.io/plugins/datasette-render-images"&gt;datasette-render-images&lt;/a&gt; so you can see the tile images themselves in the regular table view, &lt;a href="https://datasette-tiles-demo.datasette.io/basemap/tiles"&gt;like this&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Zoom level 6 is close enough that major cities and the roads between them are visible, for all of the countries in the world. Not bad for 22.7MB!&lt;/p&gt;
&lt;p&gt;This is the first time I've built a Datasette plugin that bundles a full SQLite database as part of the Python package. The pattern seems to work well - I'm excited to explore it further with other projects.&lt;/p&gt;
&lt;h4&gt;Bonus feature: tile stacks&lt;/h4&gt;
&lt;p&gt;I added one last feature to &lt;code&gt;datasette-tiles&lt;/code&gt; before writing everything up for my blog. I'm calling this feature &lt;strong&gt;tile stacks&lt;/strong&gt; - it lets you serve tiles from multiple MBTiles files, falling back to other files if a tile is missing.&lt;/p&gt;
&lt;p&gt;Imagine you had a low-zoom-level world map (similar to &lt;code&gt;datasette-basemap&lt;/code&gt;) and a number of other databases providing packages of tiles for specific countries or cities. You could run Datasette like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette basemap.mbtiles japan.mbtiles london.mbtiles tokyo.mbtiles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hitting &lt;code&gt;/-/tiles-stack/1/1/1.png&lt;/code&gt; would seek out the specified tile in the &lt;code&gt;tokyo.mbtiles&lt;/code&gt; file, then fall back to &lt;code&gt;london.mbtiles&lt;/code&gt; and then &lt;code&gt;japan.mbtiles&lt;/code&gt; and finally &lt;code&gt;basemap.mbtiles&lt;/code&gt; if it couldn't find it.&lt;/p&gt;
&lt;p&gt;For a demo, visit &lt;a href="https://datasette-tiles-demo.datasette.io/-/tiles-stack"&gt;https://datasette-tiles-demo.datasette.io/-/tiles-stack&lt;/a&gt; and zoom in on Japan. It should start to display the Stamen toner map once you get to zoom levels 6 and 7.&lt;/p&gt;
&lt;h4&gt;Next steps&lt;/h4&gt;
&lt;p&gt;I've been having a lot of fun exploring MBTiles - it's such a natural fit for Datasette, and it's exciting to be able to build new things on top of nearly a decade of innovation by other geo-hackers.&lt;/p&gt;
&lt;p&gt;There are plenty of features missing from &lt;code&gt;datasette-tiles&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It currently only handles &lt;code&gt;.png&lt;/code&gt; image data, but the &lt;a href="https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md"&gt;MBTiles 1.3 specification&lt;/a&gt; also defines &lt;code&gt;.jpg&lt;/code&gt; and &lt;code&gt;.webp&lt;/code&gt; tiles, plus vector tiles using Mapbox's &lt;code&gt;.pbf&lt;/code&gt; gzip-compressed protocol buffers.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/mapbox/utfgrid-spec"&gt;UTFGrid&lt;/a&gt; is a related specification for including "rasterized interaction data" in MBTiles databases - it helps efficiently provide maps &lt;a href="https://blog.mapbox.com/how-interactivity-works-with-utfgrid-3b7d437f9ca9"&gt;with millions of embedded objects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a newcomer to the MBTiles world I'd love to hear suggestions for new features and feedback on how I can improve what I've got so far in the &lt;a href="https://github.com/simonw/datasette-tiles/issues"&gt;datasette-tiles issues&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Being able to serve your own map tiles like this feels very much in the spirit of the OpenStreetMap project. I'm looking forward to using my own tile subsets for any future projects that fit within a sensible tile subset.&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;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tom-macwright"&gt;tom-macwright&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/leaflet"&gt;leaflet&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="geospatial"/><category term="mapping"/><category term="openstreetmap"/><category term="projects"/><category term="sqlite"/><category term="datasette"/><category term="tom-macwright"/><category term="leaflet"/></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>Doing things with Ordnance Survey OpenData</title><link href="https://simonwillison.net/2010/May/20/os/#atom-tag" rel="alternate"/><published>2010-05-20T15:22:00+00:00</published><updated>2010-05-20T15:22:00+00:00</updated><id>https://simonwillison.net/2010/May/20/os/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://frot.org/t/talks/techmeetup.html"&gt;Doing things with Ordnance Survey OpenData&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Jo Walsh’s guide to processing Ordnance Survey OpenData using PostgreSQL and PostGIS.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&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/ordnancesurvey"&gt;ordnancesurvey&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/recovered"&gt;recovered&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jo-walsh"&gt;jo-walsh&lt;/a&gt;&lt;/p&gt;



</summary><category term="mapping"/><category term="open-data"/><category term="ordnancesurvey"/><category term="postgis"/><category term="postgresql"/><category term="recovered"/><category term="jo-walsh"/></entry><entry><title>ClearMaps: A Mapping Framework for Data Visualization</title><link href="https://simonwillison.net/2010/Feb/28/clearmaps/#atom-tag" rel="alternate"/><published>2010-02-28T15:52:04+00:00</published><updated>2010-02-28T15:52:04+00:00</updated><id>https://simonwillison.net/2010/Feb/28/clearmaps/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://sunlightlabs.com/blog/2010/clearmaps-mapping-framework/"&gt;ClearMaps: A Mapping Framework for Data Visualization&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
An open source library for map visualisations using ActionScript, with an Adobe AIR based encoding tool for translating data from shapefiles in to vector data suitable for use with the library.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/clearmaps"&gt;clearmaps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/flash"&gt;flash&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/visualisations"&gt;visualisations&lt;/a&gt;&lt;/p&gt;



</summary><category term="clearmaps"/><category term="flash"/><category term="mapping"/><category term="visualisations"/></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>GeoPlanet data available again</title><link href="https://simonwillison.net/2009/Dec/11/geoplanet/#atom-tag" rel="alternate"/><published>2009-12-11T08:17:27+00:00</published><updated>2009-12-11T08:17:27+00:00</updated><id>https://simonwillison.net/2009/Dec/11/geoplanet/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.ygeoblog.com/2009/10/wheres-my-data-ah-there-it-is/"&gt;GeoPlanet data available again&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Good news: the Yahoo! GeoPlanet data dump is available again. An issue with one of their data providers meant they had to remove that supplier’s data from the dump, but it’s now been separated and the dataset is live gain. By the end of 2010 they intend to derive all of the data from completely open sources.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://hublog.hubmed.org/archives/001888.html"&gt;Importing GeoPlanet data into MySQL&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/geoplanet"&gt;geoplanet&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/yahoo"&gt;yahoo&lt;/a&gt;&lt;/p&gt;



</summary><category term="geoplanet"/><category term="geospatial"/><category term="mapping"/><category term="yahoo"/></entry><entry><title>Quoting Stephen Timms</title><link href="https://simonwillison.net/2009/Nov/17/stephen/#atom-tag" rel="alternate"/><published>2009-11-17T18:10:38+00:00</published><updated>2009-11-17T18:10:38+00:00</updated><id>https://simonwillison.net/2009/Nov/17/stephen/#atom-tag</id><summary type="html">
    &lt;blockquote cite="http://www.communities.gov.uk/news/corporate/1385429"&gt;&lt;p&gt;About 80 per cent of public sector data mentions a place. Making Ordnance Survey data more freely available will encourage more effective exploitation of public data by businesses, individuals and community organisations.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="http://www.communities.gov.uk/news/corporate/1385429"&gt;Stephen Timms&lt;/a&gt;, Minister for Digital Britain&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datagov"&gt;datagov&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ordnancesurvey"&gt;ordnancesurvey&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/stephen-timms"&gt;stephen-timms&lt;/a&gt;&lt;/p&gt;



</summary><category term="datagov"/><category term="mapping"/><category term="ordnancesurvey"/><category term="stephen-timms"/></entry><entry><title>Re-mapping the future for Ordnance Survey - making public data public</title><link href="https://simonwillison.net/2009/Nov/17/remapping/#atom-tag" rel="alternate"/><published>2009-11-17T18:09:29+00:00</published><updated>2009-11-17T18:09:29+00:00</updated><id>https://simonwillison.net/2009/Nov/17/remapping/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.communities.gov.uk/news/corporate/1385429"&gt;Re-mapping the future for Ordnance Survey - making public data public&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
“The Prime Minister and Communities Secretary John Denham will today announce that the public will have more access to Ordnance Survey maps from next year, as part of a Government drive to open up data to improve transparency.”


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datagov"&gt;datagov&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ordnancesurvey"&gt;ordnancesurvey&lt;/a&gt;&lt;/p&gt;



</summary><category term="datagov"/><category term="mapping"/><category term="ordnancesurvey"/></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>How to Make a US County Thematic Map Using Free Tools</title><link href="https://simonwillison.net/2009/Nov/12/choropleths/#atom-tag" rel="alternate"/><published>2009-11-12T10:49:23+00:00</published><updated>2009-11-12T10:49:23+00:00</updated><id>https://simonwillison.net/2009/Nov/12/choropleths/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://flowingdata.com/2009/11/12/how-to-make-a-us-county-thematic-map-using-free-tools/"&gt;How to Make a US County Thematic Map Using Free Tools&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
This is the trick I’ve been using to generate choropleths at the Guardian for the past year: figure out the preferred colours for a set of data in a Python script and then rewrite an SVG file to colour in the areas. I use ElementTree rather than BeautifulSoup but the technique is exactly the same. The best thing about SVG is that our graphics department can export them directly out of Illustrator, with named layers and paths automatically becoming SVG ID attributes. Bonus tip: sometimes you don’t have to rewrite the SVG XML at all, instead you can generate CSS to colour areas by ID selector and inject it in to the top of the file.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/beautifulsoup"&gt;beautifulsoup&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/choropleths"&gt;choropleths&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/elementtree"&gt;elementtree&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/infographics"&gt;infographics&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/svg"&gt;svg&lt;/a&gt;&lt;/p&gt;



</summary><category term="beautifulsoup"/><category term="choropleths"/><category term="css"/><category term="elementtree"/><category term="infographics"/><category term="mapping"/><category term="python"/><category term="svg"/></entry><entry><title>Cartographer.js</title><link href="https://simonwillison.net/2009/Nov/1/cartographerjs/#atom-tag" rel="alternate"/><published>2009-11-01T13:20:05+00:00</published><updated>2009-11-01T13:20:05+00:00</updated><id>https://simonwillison.net/2009/Nov/1/cartographerjs/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://cartographer.visualmotive.com/"&gt;Cartographer.js&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
“Thematic mapping for Google Maps”—which means an easy way of adding heat maps (aka chloropleths), pie charts and point clusters as a layer over a Google map.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/catography"&gt;catography&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/chloropleths"&gt;chloropleths&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/graphs"&gt;graphs&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/heatmaps"&gt;heatmaps&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/infographics"&gt;infographics&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/piecharts"&gt;piecharts&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/visualisation"&gt;visualisation&lt;/a&gt;&lt;/p&gt;



</summary><category term="catography"/><category term="chloropleths"/><category term="google"/><category term="google-maps"/><category term="graphs"/><category term="heatmaps"/><category term="infographics"/><category term="mapping"/><category term="maps"/><category term="piecharts"/><category term="visualisation"/></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>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>Static Maps API v2</title><link href="https://simonwillison.net/2009/Aug/26/google/#atom-tag" rel="alternate"/><published>2009-08-26T09:01:43+00:00</published><updated>2009-08-26T09:01:43+00:00</updated><id>https://simonwillison.net/2009/Aug/26/google/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://googlegeodevelopers.blogspot.com/2009/08/static-maps-api-v2-encoded-paths.html"&gt;Static Maps API v2&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The new version of the Google Static Maps API (static images generated using arguments in a URL, no JavaScript required) adds support for paths, areas and automatically geocoding addresses to specify locations of markers and the centre of the map.


    &lt;p&gt;Tags: &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/mapping"&gt;mapping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/staticmapsapi"&gt;staticmapsapi&lt;/a&gt;&lt;/p&gt;



</summary><category term="google"/><category term="google-maps"/><category term="mapping"/><category term="staticmapsapi"/></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><entry><title>walking papers lives</title><link href="https://simonwillison.net/2009/Jun/7/walking/#atom-tag" rel="alternate"/><published>2009-06-07T13:47:30+00:00</published><updated>2009-06-07T13:47:30+00:00</updated><id>https://simonwillison.net/2009/Jun/7/walking/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://mike.teczno.com/notes/walking-papers-lives.html"&gt;walking papers lives&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Round trip mapping: print out a map from OpenStreetMap, walk around annotating it with a pen, then scan the result back in (a QR code ensures the area and orientation is recognised) . Specifically targeted at eye-level stuff which can’t be collected using GPS or aerial imagery alone. When I grow up, I want to be Mike Migurski.


    &lt;p&gt;Tags: &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/papernet"&gt;papernet&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/walkingpapers"&gt;walkingpapers&lt;/a&gt;&lt;/p&gt;



</summary><category term="mapping"/><category term="michal-migurski"/><category term="openstreetmap"/><category term="papernet"/><category term="walkingpapers"/></entry><entry><title>Mapstraction API Sandbox</title><link href="https://simonwillison.net/2009/Jun/7/mapstraction/#atom-tag" rel="alternate"/><published>2009-06-07T11:41:20+00:00</published><updated>2009-06-07T11:41:20+00:00</updated><id>https://simonwillison.net/2009/Jun/7/mapstraction/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://mapstraction.appspot.com/"&gt;Mapstraction API Sandbox&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Andrew Turner’s new tool for exploring the Mapstraction JavaScript library, which provides a unified code interface to 12 different mapping services


    &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/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/mapstraction"&gt;mapstraction&lt;/a&gt;&lt;/p&gt;



</summary><category term="andrew-turner"/><category term="javascript"/><category term="mapping"/><category term="mapstraction"/></entry><entry><title>Announcing Google Maps API v3</title><link href="https://simonwillison.net/2009/May/28/googlemaps/#atom-tag" rel="alternate"/><published>2009-05-28T01:22:45+00:00</published><updated>2009-05-28T01:22:45+00:00</updated><id>https://simonwillison.net/2009/May/28/googlemaps/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://googlegeodevelopers.blogspot.com/2009/05/announcing-google-maps-api-v3.html"&gt;Announcing Google Maps API v3&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Sounds like a complete rewrite, with performance as the key goal. Only a developer preview at the moment, but my favourite feature is that API keys are no longer required.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/api-keys"&gt;api-keys&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/googlemaps3"&gt;googlemaps3&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mapping"&gt;mapping&lt;/a&gt;&lt;/p&gt;



</summary><category term="api-keys"/><category term="google"/><category term="google-maps"/><category term="googlemaps3"/><category term="mapping"/></entry><entry><title>slippy faumaxion, take two</title><link href="https://simonwillison.net/2009/Mar/15/faumaxion/#atom-tag" rel="alternate"/><published>2009-03-15T15:40:57+00:00</published><updated>2009-03-15T15:40:57+00:00</updated><id>https://simonwillison.net/2009/Mar/15/faumaxion/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://mike.teczno.com/notes/slippy-faumaxion-II.html"&gt;slippy faumaxion, take two&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Mike Migurski made a slippy map using triangular tiles, based on the same principle as Buckminster Fuller’s famous Dymaxion World Map.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/buckminsterfuller"&gt;buckminsterfuller&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/faumaxion"&gt;faumaxion&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;/p&gt;



</summary><category term="buckminsterfuller"/><category term="faumaxion"/><category term="mapping"/><category term="michal-migurski"/></entry></feed>