Busabase
Busabase is an open-source (MIT) database and workspace for AI agents — data, docs, skills, and apps in one place. Run it locally, self-host it inside your network, or use Busabase Cloud, and turn age
Map CSV or Airtable data by area and visualize rows on an interactive map
Translation pending
AI summary · website content
DataJavaScript · CSSGitHub @burritojusticeOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Joiner joins rows from a spreadsheet, a file, an Airtable view, a Google Sheet or a DataSF dataset to areas on a map. The areas include San Francisco districts, ZIPs, neighborhoods and census tracts, H3 hexagons, Bay Area counties and ZIPs, election precincts and the districts made from them, or polygons you upload. Each area is colored by how many rows it has, or by the total, average or share of any number column. Rows with a location are drawn too. The join logic is JoinKit, shared with Selecter.
Live at https://burritojustice.github.io/joiner/. Hover the Joiner logo to see the version; CHANGELOG.md says what changed in each. Nothing is uploaded: files are read in your browser.
Drop a file anywhere on the page, or click Load data. The dialog has Open a file… and a box for a link.
data.records:read and schema.bases:read.
records JSON as a file.Sharing. After loading a link or an Airtable view, the page address carries it (?url=… or ?airtable=…), so the page can be shared. The Airtable token is never put in the address: it stays in your browser tab until the tab closes, and whoever opens a shared page uses their own token. Snapshots contain every column you loaded, so remove names, emails and phone numbers before sharing one.
Big files. Files with tens or hundreds of thousands of rows show their progress as they read, place, count and color. A 26 MB CSV of 367,000 rows loads in about 3 seconds. Above 2,000 located rows, a file starts drawn as pinpoints; above 50,000, its points start hidden (the areas still count them).
Match rows by picks the column, or Location (the area that contains each row's point; lines count by their midpoint). Joiner tries every column and picks the one that places the most rows; the menu shows each column's count.
match in sf-blocks' data/boundaries/sources.json. For example, districts accept 5, D5 or District 5.The tabs along the top are District, ZIP, Neighborhood (SF Find, Analysis, Planning, Realtor or Who's on First), H3 hexbins and Other. Other holds:
Bay Area sets zoom out to show all of themselves.
H3 hexbins work anywhere, not just in SF.
Basemap. Streets draw above the areas from zoom 13 (or put the areas under the basemap). Highway shields and one-way arrows are hidden. The creek fix from Block Party and Selecter hides streams and "Creek" and "Arroyo" waterways, which Protomaps tags as rivers even where they're culverted.
When the file has locations, Format features controls how its rows are drawn:
Table (next to Areas) shows every row, below or beside the map; the two icons switch between them.
The polygon sets come from sf-blocks, the main copy of San Francisco's map data: one GeoJSON file per layer in data/boundaries/, plus index.json, which the map reads to build its tabs. The page reads sf-blocks' region.json for the paths and loads each file live from https://burritojustice.github.io/sf-blocks/; if that site can't be reached, it uses the copies in this repo at the same paths. data/sf-blocks.json lists the copies and the sf-blocks version they came from (hover the version number to see which the page is using). ?sfblocks=local forces the copies. Refresh them with python3 scripts/copy_sf_blocks.py ../sf-blocks.
Other names for areas ("Calle 24" for the Calle 24 Latino Cultural District, "South Bayshore" for Bayview) are in sf-blocks' data/aliases.json, by layer id, shared with Block Party. Add aliases there, not in sources.json.
The map never calls DataSF while it runs.
| Layer | Tab | Join key | Features | Source |
|---|---|---|---|---|
supervisor_districts | District | sup_dist (1–11) | 11 | DataSF cqbw-m5m3 |
zip_codes | ZIP | zip_code | 27 | DataSF srq6-hmpi |
sf_find_neighborhoods | Neighborhood (default) | name | 117 | DataSF pty2-tcw4 |
analysis_neighborhoods | Neighborhood | nhood | 41 | DataSF p5b7-5n3h |
planning_neighborhoods | Neighborhood | neighborho | 37 | DataSF Planning Neighborhood Groups |
realtor_neighborhoods | Neighborhood | nbrhood | 92 | DataSF 5gzd-g9ns |
wof_neighbourhoods | Neighborhood | name | 116 | Who's on First (84 current + 32 unverified), from sf-blocks' data/uploads/wof/ |
census_tracts | Other | geoid | 242 | DataSF sevw-6tgi |
Each layer's settings are in sf-blocks' data/boundaries/sources.json:
tab decides where the layer appears. Layers that share a tab become one menu, and "Other" puts a layer in the Other menu."default": true picks the set a tab opens with.regional marks a layer beyond SF.dissolve: { from, by } builds a layer by merging another layer's polygons that share a value. That's how precincts become districts, with clean outlines.A few precincts sit alone in their own district: one on the Bay Bridge and one near Angel Island.
The boundaries are built in sf-blocks by scripts/get_layers.py (standard-library Python), from its data/boundaries/sources.json; see sf-blocks' README. It keeps the listed properties, sets each feature's id to its join key, rounds coordinates to 6 decimals, merges pieces that share a key where mergeByKey is set, sorts features by key (one per line, for readable diffs), and sets bbox to the mainland so the map doesn't zoom out to the Farallones. Then copy the new files here with python3 scripts/copy_sf_blocks.py ../sf-blocks.
data/wof/ holds the San Francisco macrohoods, neighbourhoods and microhoods from Who's on First, gathered from the Spelunker's descendant list for San Francisco. Unlike the DataSF sets, WOF records a hierarchy (a microhood inside a neighbourhood inside a macrohood), which is what we need for broad names like "Sunset" or "Richmond".
| File | What |
|---|---|
places.csv | every record: id, name, placetype, status, macrohood, parent, geometry source, Spelunker and download URLs |
macrohoods.geojson | 4 macrohoods: Downtown, Mission District, Richmond District, Sunset District |
neighbourhoods.geojson | 84 current neighbourhoods (mz:is_current 1) |
neighbourhoods-unverified.geojson | 32 neighbourhoods with mz:is_current -1 |
microhoods.geojson | 46 current microhoods |
microhoods-unverified.geojson | 49 microhoods with mz:is_current -1 |
raw/ | the original WOF records, one file per id |
ids.txt, missing.txt | ids from the Spelunker; ids not found in whosonfirst-data-admin-us |
Current vs. unverified. WOF marks 84 SF neighbourhoods current (mz:is_current 1). Another 32 have mz:is_current -1: never reviewed, but not deprecated, superseded or ceased. These files are named -unverified rather than "not current" because in WOF "not current" is mz:is_current 0, the retired records. They include Excelsior, Inner and Outer Sunset, Inner and Outer Richmond, Chinatown, Bayview and Ingleside. The two sets don't overlap, and together they cover all of SF; the 84 alone cover 62% of its land. So for a full neighbourhood map, load both files. Records with mz:is_current 0 (deprecated, superseded or ceased) aren't in any layer but are listed in places.csv. None of these records set mz:is_deprecated; deprecation is in edtf:deprecated and wof:superseded_by.
node scripts/get_wof.mjs # walk the Spelunker, download new records, rebuild outputs
node scripts/get_wof.mjs --from-ids # skip the Spelunker; use data/wof/ids.txt
node scripts/get_wof.mjs --offline # rebuild outputs from data/wof/raw only
Records come from GitHub (whosonfirst-data/whosonfirst-data-admin-us); places.csv also lists each record's data.whosonfirst.org URL.
Each merged change is a new version:
_BUILD_VERSION at the top of js/app.js (v37 → v38).## v38 section at the top of CHANGELOG.md saying what changed, in terms someone using the map would notice..github/workflows/tag-version.yml) reads _BUILD_VERSION and tags the merge commit v38.To tag an older commit, run the action by hand: Actions → Tag version → Run workflow, with the tag (v1) and the commit SHA.
JoinKit is pinned to one commit in index.html, and the same commit is kept in js/vendor/joinkit.js for when the CDN fails. Update both together. CLAUDE.md has the steps, and notes on how changes are built and tested.
AGPL-3.0 — see LICENSE.
bay_area_counties | Other | county | 9 | California county boundaries, the nine Bay Area counties (to be replaced by DataSF wamw-vt4s) |
bay_area_zip_codes | Other | zip | 323 | DataSF 4kz9-76pb |
election_precincts | Other | prec_2022 | 514 | DataSF d6x4-hefw |
us_house_districts | Other | cong22 | 4 | precincts dissolved by district |
state_senate_districts | Other | sen22 | 3 | precincts dissolved by district |
assembly_districts | Other | assemb22 | 4 | precincts dissolved by district |
bart_districts | Other | bart22 | 3 | precincts dissolved by district |