Saan Tayo? Every Philippine town, sorted by vibe
Saan Tayo? (“Where are we going?”) is an interactive map of all 1,642 cities and municipalities in the Philippines. Pick what you want from 13 vibes and every town that fits lights up, including the ones you have never heard of.
The problem
Travel maps of the Philippines are hand-picked. They show the same few dozen famous places, and none of them covers every town or lets you combine wishes like “beach, heritage, and not touristy”. There are 1,642 cities and municipalities, and most people can name only a small share of them.
The idea
A quiet town in Aurora should have the same chance of being found as Boracay. So: every town from the official list, filters you can combine, and a site that says so when it doesn't know enough about a place.
Project overview
- Type
- Interactive map + data pipeline
- My role
- Planning, review, every decision
- Status
- Live
- Architecture
- Static site, no backend
Why I built it
The project started as a way to test Jev, a small decision model by TypeSafe, on something real. Tagging every town in the country was the test: 1,642 towns, 15 yes-or-no questions each, with a reference set to check the answers against. It did the whole country for about US$0.51, and 12 of the 13 vibes met my 85% bar. Its own “is there enough text?” answer turned out too strict, so plain code makes that call instead.
How it was made
I planned it, reviewed it and made the decisions. AI coding assistants (Claude Code, and Codex for some phases) wrote most of the code. A small model called Jev judged the tags. The illustrations were drawn as SVG code with AI assistance, then reviewed and edited.
What it does
- Filter by any mix of 13 vibes, how touristy a town is, and distance from Manila, Cebu or Davao
- Tara! picks a random hidden gem that fits your filters
- Search by town, province or place: “Boracay” finds Malay
- A page for each of the 1,642 towns, with the sentences behind its tags
- The method, the agreement table and its weak spots, published on the site
- Credits for every source, article revision and photographer
How a town gets its tags
- 01Sources
Open data, joined by official code
- The Philippine Statistics Authority's list: 1,642 cities and municipalities, each with a PSGC code.
- Wikipedia and Wikivoyage text, Wikidata, OpenStreetMap, and photo captions from Wikimedia Commons.
- Never joined by name: 329 towns share theirs with another, and there are nine San Joses.
- 02Prepare
One town, one trimmed text
- 2,955 articles and guides, with the exact revisions kept for the credits.
- Only the travel-relevant parts are kept: about 3,200 tokens per town on average.
- Every request is cached, so nothing is fetched twice.
- 03Judge
One yes-or-no question per vibe
- Jev, a decision model by TypeSafe, reads the text and answers 15 questions per town.
- It returns how likely “yes” is. Example: “Does the town have a beach that visitors go to?”
- A budget cap in code, and every answer cached: nothing is paid for twice.
- 04Decide
The same rules for every town
- Yes from 75% confidence, maybe from 50%.
- “Not enough info” when the text about the town itself is under 250 tokens.
- No sea vibes inland. Map facts are shown as context and never set a tag.
- 05Site
Static files, no server
- The pipeline exports JSON; Astro builds the map home and a page per town.
- MapLibre GL draws the map from the site's own borders.
- Hosted on Vercel's free plan. Visitors cost nothing.
The model judges, the code decides.
The model only answers questions. Thresholds, rules, counts and distances live in code, the same for every town. When a rule was wrong, it could be changed without paying for the model again.
Key screens
The map
Pick a vibe and every town that fits lights up.
- Cobalt for a strong match, light cobalt for a good one.
- Patterns, not only colour: dots mean “maybe” and hatching means “not enough info”, so the map still reads in greyscale.
- Distance is a straight line from Manila, Cebu or Davao, drawn as a dashed ring and labelled “as the crow flies”.
- Every filter state is in the URL, so any view can be shared as a link.
Search and the town card
Search by town, province or place.
- 269 place names find the town they belong to: “Boracay” finds Malay.
- The card shows the photo with its credit, the match strength, the top vibes and the distance.
- A click keeps your view and only nudges the map when the town is at the edge or under the card.
Tara! and the list
Tara! (“Let's go!”) takes you to a random hidden gem that fits your filters.
- A hidden gem is a town at the low end of “how touristy”.
- The same results are available as a text list, which is also the text alternative to the map.
- “Back to previous view” returns you to exactly where you were.
A page per town
All 1,642 towns get their own page.
- A bar per vibe with a strength word: Strong, Good or Maybe.
- “Why these tags” quotes up to two sentences per vibe from the town's sources, and says they are not necessarily the ones the model relied on.
- How touristy it is, map facts, distances, nearby places, similar towns and its sources.
- Headings are Filipino first, English under: “Ano'ng meron? / What's here”.
When it doesn't know
It says so, instead of guessing.
- 347 towns (21%) have too little written about them and are marked “not enough info”.
- 1,048 towns have a credited photo. The other 594 get an illustration, always labelled as one.
- For a thin-text town only a clear “yes” is shown, never a “no”.
The method, published
How the tags were made, how well they hold up, and what it cost.
- All 15 questions the model was asked are printed on the page.
- The agreement table and its weak spots are there too, not only the good rows.
- Credits cover every source, licence, article revision and photographer, split into 18 region pages.
On a phone
The vibes sit in a sideways row under the search box, and the town card opens at a third of the screen so the map stays in view.
How well the tags hold up
I checked the tags against a reference set of 150 towns. Its labels were made with the help of a separate AI assistant using web search, plus my own knowledge of some towns, without looking at the model's answers. So these figures are agreement with that reference set, not verified ground truth.
| Vibe | When the site says yes, it agrees | Towns it catches |
|---|---|---|
| Beach | 94.1% (32 of 34) | 63% (32 of 51) |
| Island hopping | 100% (11 of 11) | 50% (11 of 22) |
| Diving | 100% (13 of 13) | 50% (13 of 26) |
| Surfing | 100% (6 of 6) | 55% (6 of 11) |
| Mountains | 87.5% (35 of 40) | 81% (35 of 43) |
| Waterfalls | 100% (22 of 22) | 55% (22 of 40) |
| Caves | 100% (7 of 7) | 41% (7 of 17) |
| Hot springs | 86.7% (13 of 15) | 65% (13 of 20) |
| Heritage | 97.8% (44 of 45) | 90% (44 of 49) |
| Festivals | 100% (35 of 35) | 44% (35 of 79) |
| Food | 93.8% (15 of 16) | 50% (15 of 30) |
| Cool climateunder the bar | 80.0% (4 of 5) | 33% (4 of 12) |
| Countryside | 100% (4 of 4) | 27% (4 of 15) |
- The bar I set is 85% agreement when the site says yes. Catching every town matters less: a missed town is only hidden from one filter, but a wrong tag sends someone somewhere for the wrong reason.
- Twelve of the 13 vibes are at or over the bar. Cool climate is under it (4 of 5) and is shown anyway, with a note on the site.
- Countryside's 100% rests on 4 towns. Whether it stays is to be reviewed.
The same table, with every caveat, is on the site's How it works page (opens in a new tab).
Hard problems
Six sources that disagree with each other.
The official town list, Wikipedia, Wikivoyage, Wikidata, OpenStreetMap and Wikimedia Commons all had to line up. The official list uses an old region prefix for 55 towns that Wikidata lists under the new one; one code rule matched all 55. For 38 small towns the travel-guide link quietly redirected to the page of the whole province, which would have credited a province's sights to one town, so a page is now used only when its ID is the town's own. And OpenStreetMap's borders for coastal cities include their municipal waters, in one case about 89% sea, so they are clipped to the land.
The missing beaches.
587 of 899 coastal towns had no Beach tag, in a country of islands. The cause was not the model: in 98% of those towns the word “beach” appears nowhere in the article text. I tried the map first, but no rule like “OpenStreetMap shows a beach here” reached the 85% bar (the best was 83.3%), so the map stays context only. What worked was a different source of text: the titles and descriptions of each coastal town's photos on Wikimedia Commons, because many beaches are photographed but never written about. Two gates had to pass before I used it. Coastal towns tagged Beach went from 253 to 348, for about 7 US cents.
Deciding when to say “not enough info”.
The model has its own answer to “does the text say enough?”. I compared it with a plain rule on text length, on the first 70-town reference set. The model's answer agreed with the reference on 59 of 70 towns and would have marked about 52% of the country. The length rule agreed on 68 of 70 and marks 21%. The site uses the length rule.
A map that dropped whole islands on small phones.
On a short phone the whole-country view fell below zoom level 4, and the map dropped Samar, Leyte, Cebu, Bohol and Mindoro. It was found by testing at 360 × 600 and fixed by one simplification setting. The same round fixed the filter sheet, which showed none of the 13 vibes when it opened on a 360 × 640 phone and needed 555 px of scrolling; now all 13 show and it needs 181 px. One idea was measured and dropped: starting the biggest download earlier gained 1.1 s on the map and cost 1.9 s on the controls.
Tried and dropped
- “City life”: one mall was enough for a yes, and only 5 of its 15 yes tags agreed with the reference.
- Tags from the map: no rule passed the bar.
- The model's own “enough info” answer: too strict.
- Other-language Wikipedias: mostly short automatic stubs.
- Evidence photos: built, then switched off. Once the images were opened, half the doubtful picks that looked fine by title were wrong.
The redesign
The first version worked, and it looked like everyone else's: a cream background, a book serif for the name, teal for the data and rounded pills for everything. Two other Philippine map sites I looked at had landed on almost the same look. So I rebuilt it around a few rules, and wrote them into the project so every later change follows them.
- Each colour has one job. Cobalt is data, yellow is what you picked, red is the one action.
- One type family, Archivo, with a heavy condensed cut for town names, and IBM Plex Mono for distances and percentages.
- Square shapes, ink borders and no soft shadows.
- Only the sea and major roads come from the basemap. The land, coast and town names come from the site's own data, so no other country is labelled.
The map home
A town page
The share image
On a phone
Some counts differ between a before and its after because the data changed too, not only the look.
Each colour has one job
- Ground #FFFFFFCards, panels, land on the map
- Page #EEF0F3Page background, loading boxes
- Ink #15171CText, borders, coastline
- Rules #D5D9E1Hairlines
- Sea #D3DEEFMap sea, illustration sky
- Cobalt #1E3FB4Data: strong match
- Cobalt 2 #7A93DEData: good match
- Yellow #FFC629What you picked
- Red #D3261CThe one action, Tara!
The logo
The wordmark is set in Archivo's heaviest condensed cut. The dot of the “?” is a red map pin, and the “?” with its pin is the mark on its own. The name is what you ask before you get on a jeepney.
Illustrations
17 flat SVG scenes stand in for the 594 towns with no photo, and for share images. They use the palette's colours only, with red once per scene, and take their texture from the same hatching and dots as the map. On the site each one is labelled “Illustration, not a photo”.
Illustrations: drawn for this project as SVG code with AI assistance (Claude), then reviewed and edited. CC BY 4.0.
Share images
Every town also gets its own link-preview image, made when the site is built.
How I worked
Everything was written down.
Every decision is logged with its reason and what it replaced; the log runs to D-223. Each piece of work had a plan I approved, checkpoints where I looked at screenshots before anything went further, and a report at the end. The numbers on this page come from those reports.
The calls that mattered were mine.
I set the rule that a tag must agree at least 85% of the time when it says yes, and kept it even though it means Festivals and Food miss about half the towns, because a wrong tag is worse than a missing one. When I saw that my first version looked almost the same as two other Philippine map sites, I pushed for a full redesign. And when clicking a town zoomed the map in and lost my place, I asked for the map to stay still and move only when it has to.
Limits and what's next
Honest limits
- Tags reflect what is written, not what is there. An unwritten-about waterfall will not show.
- The reference set is AI-assisted, not hand-verified, and covers 150 of 1,642 towns.
- 21% of towns (347) have too little text to judge.
- Several vibes catch fewer than half the reference towns: Festivals 44%, Caves 41%, Cool climate 33%, Countryside 27%.
- Map facts come from OpenStreetMap and can be off. Distances are straight lines.
- 8 towns appear as dots, because no border shape exists for them yet.
- The map home is the slowest page: 76 to 78 on Lighthouse's mobile test, held down by the map itself.
- Not tested: Safari, and a full screen-reader pass beyond the TalkBack checks I did myself.
What's next
- A lighter borders file for the first view, to cut the wait on slow connections.
- “Report a wrong tag” on every town page.
- Evidence photos, with a better check.
- A decision on whether Countryside stays.
- A custom domain.
Tech stack
Jev, pinned to version 1.13.0 and called with plain fetch rather than an SDK, answers the questions; at US$0.042 per million input tokens, that is what kept the whole run near fifty cents. The data pipeline is Bun and TypeScript: each step is its own script that reads the previous step's output, so any step can be rerun alone. DuckDB reads the official Excel file directly, and SQLite caches every network request and every model answer. The site is static Astro pages reading JSON the pipeline exports, with MapLibre GL drawing the map on OpenFreeMap tiles. There is no backend and no database.
Credits and licences
- Text: Wikipedia and Wikivoyage (CC BY-SA 4.0).
- Map data: © OpenStreetMap contributors (ODbL). Basemap tiles: OpenFreeMap, © OpenMapTiles.
- Photos: Wikimedia Commons, each under its own licence, credited per photographer.
- Town list: Philippine Statistics Authority. Borders: faeldon/philippines-json-maps (MIT), plus OpenStreetMap.
- Town data: Wikidata (CC0). Airports: OurAirports (public domain).
- Illustrations: CC BY 4.0.
Every article revision and photographer is listed on the site's Credits page (opens in a new tab).
Want to build something like this?
If you have a messy pile of data that should be a product people can explore, or you want to talk through how this one was put together, reach out.