⏱️ Lectura: 9 min
A map of San Francisco that loads OpenStreetMap layers and open city data without relying on its own server: that’s how Isopolis works, published at sf.isopolis.city. The site overlays the official neighborhood boundaries published by DataSF on a CARTO cartographic base, and it does so entirely in the browser, with no backend or intermediate database.
📑 En este artículo
What’s notable isn’t just the visual result, but how the code is put together: the site’s JavaScript bundle includes a note about module load order, a classic and rarely discussed problem in modern web development, the order in which the browser needs to find a script before that same script can resolve its own relative paths.
TL;DR
- Isopolis (sf.isopolis.city) is an interactive map of San Francisco’s neighborhoods that runs with no backend, entirely in the browser.
- The cartographic base uses CARTO tiles built on OpenStreetMap data.
- Neighborhood boundaries come from open datasets published by DataSF, the city’s data portal.
- The site’s JavaScript bundle documents a note on load order: the script must be in the DOM before it resolves its relative paths.
- It doesn’t require paid API keys: it uses public tiles and open data under permissive licenses.
- It’s an example of the map as a static site pattern: HTML, JS, and GeoJSON deployable on any CDN.
Introduction
For more than a decade, more and more cities have been publishing geographic data in open formats: administrative boundaries, transit routes, zoning. San Francisco was one of the first cities in the United States to open up its municipal data, and today that catalog lives on DataSF, the city’s open data portal.
Isopolis takes a specific slice of that catalog, neighborhood boundaries, and turns it into a navigable map. It’s not a commercial product or a service with paid plans: it’s an example of the map as a static site pattern, where all the heavy lifting (rendering tiles, drawing polygons, showing popups) happens in the visitor’s browser, with no server of its own involved.
What happened: the launch of Isopolis
The site sf.isopolis.city presents a full-screen map centered on San Francisco. The visual base uses the minimalist styles typical of CARTO, built on data from OpenStreetMap, the collaborative cartography project anyone can edit. On top of that base, neighborhood polygons are drawn, each identifiable by hovering the cursor or tapping on mobile.
What makes the project interesting isn’t just the final result, but the simplicity of its approach: there’s no API of its own, no authentication, no geospatial database running on a server. The browser requests tiles from CARTO, requests the neighborhood GeoJSON from DataSF, and draws everything with JavaScript.
Context and history
OpenStreetMap started as an open collaborative project and is now the most widely used map data source outside the major commercial providers. OpenStreetMap lets anyone contribute streets, buildings, and points of interest, and that catalog feeds free or low-cost tile services like CARTO’s.
CARTO, for its part, specializes in location intelligence: it offers ready-to-use basemaps (Positron, Voyager, Dark Matter) that many open data sites adopt because they’re free for moderate traffic and don’t require designing a cartographic style from scratch.
DataSF fits into a broader open government movement that gained momentum in the United States in the late 2000s, when different cities began publishing their municipal datasets in reusable formats. That catalog includes, among hundreds of datasets, the administrative boundaries Isopolis uses to draw each neighborhood.
Technical details and performance
The most noteworthy technical detail in Isopolis appears in its own bundle: a note stating that a script has to be in the DOM before the parser reaches the module’s own relative path. This isn’t arbitrary, it’s a direct consequence of how ES modules work in the browser.
When a script loads with type="module", the browser resolves relative imports using the module’s own URL (import.meta.url). If the project also uses an import map to redirect package names to specific URLs, that import map has to exist in the DOM before the parser reaches the script that needs it; if it’s inserted afterward, the browser has already tried to resolve the imports using the default rules.
💡 Tip: you can confirm your scripts’ load order by opening developer tools, the Network tab, and enabling the Initiator column: it shows you which document or script triggered each request.
A minimal example of a map with Leaflet and CARTO tiles looks like this:
const map = L.map('map').setView([37.7749, -122.4194], 12);
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
attribution: 'OpenStreetMap contributors, CARTO'
}).addTo(map);
That snippet creates the map centered on San Francisco and adds the tile layer. There’s no server of its own: the browser requests the tiles directly from CARTO.
When deciding how to render a map like this, there are three common options worth comparing:
| Option | When to use it | Advantage | Limitation |
|---|---|---|---|
| Leaflet | Simple maps with raster tiles and few vector layers | Lightweight, simple API, large plugin ecosystem | No native WebGL rendering, gets slow with thousands of polygons |
| Mapbox GL JS | Vector maps with custom styles and many layers | Smooth WebGL rendering, declarative styles | Requires a Mapbox token and bills based on map view volume |
| MapLibre GL JS | Same as Mapbox GL JS, without relying on a paid provider | Same WebGL performance, it’s open source | Doesn’t include its own basemap, you have to choose or host vector tiles |
The following diagram summarizes the order in which the browser processes the HTML until it reaches a rendered map:
flowchart TD
A["HTML Parser"] --> B["script type importmap"]
B --> C["script type module src bundle.js"]
C --> D["ES module engine"]
D --> E["Resolves relative imports"]
E --> F["Map rendered in the DOM"]
How to start trying Isopolis
You don’t need to install anything to see Isopolis: just open sf.isopolis.city in your browser. To reproduce the same pattern (static map, no backend) with your own city, a minimal starting point is serving an HTML file locally:
# Windows (PowerShell)
npx serve .
# macOS
npx serve .
# Linux
npx serve .
The npx serve command works the same way on all three platforms because it runs on Node.js. Then, in your HTML, add Leaflet and a GeoJSON layer with your city’s boundaries:
fetch('GEOJSON_DATASET_URL')
.then(function (response) { return response.json(); })
.then(function (neighborhoods) {
L.geoJSON(neighborhoods, {
style: { color: '#2a6f97', weight: 1, fillOpacity: 0.15 },
onEachFeature: function (feature, layer) {
layer.bindPopup(feature.properties.name);
}
}).addTo(map);
});
You get the dataset URL from your own city’s open data portal: almost every Socrata-based portal (like DataSF) has an export button that generates a direct link to GeoJSON.
⚠️ Heads up: CARTO’s free basemaps have usage limits meant for moderate traffic; if your map gets heavy traffic, check their terms or consider hosting your own tiles.
Impact and analysis
The value of a project like Isopolis isn’t in the novelty of the technology (Leaflet, CARTO, and GeoJSON have been around for years), but in demonstrating that a useful map doesn’t need infrastructure of its own. All the operational cost falls on third-party services that already exist: OpenStreetMap, CARTO, and the city’s data portal.
That architecture has an honest limit: you depend entirely on those three services staying available under the same terms. If CARTO changes its free basemap policy or DataSF restructures a dataset, the map breaks without warning, and there’s no backend of its own where you could apply a caching layer or a fallback plan.
For a developer who wants to prototype quickly, that trade-off is usually worth it: you gain development speed and zero infrastructure cost, in exchange for giving up control over long-term availability.
What’s next
The same pattern Isopolis uses (open tiles plus municipal datasets) is replicable in any city that publishes its administrative boundaries in an open format. The rise of MapLibre GL JS as a free alternative to Mapbox GL JS opens the door to better-performing vector maps without relying on a paid provider, something indie projects like this one will likely start adopting.
📖 Summary on Telegram: View summary
Try it yourself: go to sf.isopolis.city and open the browser’s Network tab to watch live which tiles and which GeoJSON the map loads.
Frequently Asked Questions
What exactly is Isopolis?
It’s a website (sf.isopolis.city) that shows an interactive map of San Francisco’s neighborhoods, with a CARTO cartographic base and neighborhood boundaries taken from open DataSF datasets.
Does Isopolis have its own server?
No. The map loads the tiles and the GeoJSON directly from external services (CARTO and DataSF) and renders entirely in the user’s browser.
What does it mean that the bundle must be in the DOM before resolving relative paths?
It’s a reference to how ES modules behave: when a script uses relative paths or import maps, the browser needs to have inserted that script into the DOM before the module engine resolves its own paths.
Can I reproduce something similar for my own city?
Yes. Any city with an open data portal that publishes neighborhood boundaries in GeoJSON can be combined with OpenStreetMap or CARTO tiles using Leaflet, Mapbox GL JS, or MapLibre GL JS.
Does using CARTO’s tiles cost anything?
CARTO offers free basemaps for light use, but like any third-party tile service it has usage limits: for high traffic, it’s worth checking their terms or hosting your own tiles.
References
- sf.isopolis.city: Isopolis’s original site, the source for this article.
- OpenStreetMap: the collaborative open cartography project that feeds the map’s tiles.
- CARTO: the provider of the basemaps used by Isopolis.
- DataSF: the City of San Francisco’s open data portal.
- MapLibre GL JS: the open source alternative to Mapbox GL JS mentioned in the technical comparison.
- MDN: import maps: documentation on how the browser resolves module paths.
📱 Enjoying this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day. @programacion
0 Comments