An interactive map of the People's Solar Energy Fund and its 39 member organizations across the United States, Puerto Rico, and Hawaiʻi.
A single-page static site. No backend, no build step, no API keys at runtime — every coordinate, distance, and state polygon is bundled as static JSON / GeoJSON. Drop the folder on any static host and it works.
- Background
- Install
- Usage
- Updating member data
- Project structure
- Architecture
- Deployment
- Maintainers
- Contributing
- License
PSEF is a national loan fund for community-owned solar, founded in 2018. Its 39 member organizations are local cooperatives, community-development corporations, tribal energy authorities, and national alliances — geographically scattered from Hawaiʻi to Puerto Rico.
This map gives the network a visible shape. PSEF sits at the center as the network hub (sun); each member is a region-colored dot, connected to PSEF by a line annotated with the distance between them. Clicking any state surfaces every PSEF member that operates there; clicking a member opens its address, distance from PSEF, and a link to its site.
The map is meant to be low-maintenance: any volunteer can update the network by editing one Python coordinate table and re-running a small build pipeline. There is no CMS, no database, no service to keep alive.
No dependencies to install. The frontend pulls MapLibre GL JS and the Satoshi font from public CDNs at runtime; everything else is local.
git clone <repo-url>
cd network-mapThe build pipeline (used only when member data changes — see Updating member data) needs Python 3.8+ and uses only the standard library.
Run any static HTTP server from this directory and open the page:
python3 -m http.server 8000
# open http://localhost:8000Any static server works (npx serve, caddy file-server, etc.). You must serve over HTTP — opening index.html via file:// will fail because fetch blocks file:// requests in modern browsers.
| Interaction | Result |
|---|---|
| Drag the map | Pans in any direction |
| Scroll wheel / two-finger trackpad | Pans (no accidental zoom) |
Ctrl / ⌘ + scroll |
Zooms |
+ / − buttons (top-left) |
Zoom |
| Click the amber sun | Popup with PSEF HQ address + link |
| Click a colored dot | Popup with member address, distance to PSEF, link to site |
| Click an empty area inside a state | Popup listing every PSEF member in that state |
| Click the 39 member orgs card | Opens a left panel with every member grouped by region |
| Click the states / founded / regions cards | Opens an info modal |
| Click the ⤢ button | Resets view to fit all members |
When the network changes (a new member, a moved HQ, a confirmed pinpoint city for an approximate row):
- Edit
data/members_raw.mdto keep the human-readable list current. - Mirror the change in the
MEMBERStable at the top ofscripts/build_data.py. - Rebuild and validate:
python3 scripts/build_data.py # rewrites data/members.json python3 scripts/validate.py # confirms every dot is inside its declared state
- Reload the page. No JS changes needed.
To refresh the state polygons themselves (rare):
python3 scripts/fetch_osm_states_geojson.py # downloads fresh polygons from OSM
python3 scripts/enrich_states_geojson.py # idempotent — adds STUSPS / NAME
python3 scripts/patch_oahu_polygon.py # idempotent — Wai'anae fix if needed
python3 scripts/validate.pyAll scripts are idempotent.
network-map/
├── index.html page shell
├── app.js map rendering + interactions (MapLibre GL JS)
├── styles.css dark theme, marker shapes, popup styling
├── assets/ PSEF logo + favicon
├── data/
│ ├── members_raw.md source-of-truth member list (human-readable)
│ ├── members.json built artifact: PSEF + 39 members, geocoded, distances baked in
│ └── us-states.geojson 52 features (50 states + DC + PR), sourced from OpenStreetMap
└── scripts/ Python build pipeline (no external deps)
The frontend is vanilla JS on MapLibre GL JS 5.24.0 — flat 2D Mercator, locked north-up, with continuous horizontal wrap so the user can drag east past the date line indefinitely. The basemap is CartoDB dark_all raster tiles. State polygons come from OpenStreetMap (matching the basemap source so the overlay borders sit on top of the tile-drawn ones).
For deep technical detail — data schema, build-pipeline internals, layer-by-layer breakdown of the map, and design-decision rationale — see ARCHITECTURE.md.
The site is static, so any free static-hosting service can serve it as-is. DEPLOYMENT.md walks through three options PSEF can choose from — GitHub Pages, Cloudflare Pages, and Netlify — with step-by-step setup, custom-domain instructions, and a comparison table.
- Manisha — project lead
Issues and PRs welcome. To propose a change to the network:
- For data changes (new member, corrected address, confirmed coordinates), edit
data/members_raw.mdand theMEMBERStable inscripts/build_data.py, then runbuild_data.py+validate.pyand include the regenerateddata/members.jsonin your PR. - For UI / interaction changes, edit
app.js/styles.cssand reload — no build step.
If you find a member dot that lands in the wrong state, that's almost always a coordinate error in MEMBERS — validate.py will tell you which row.
Not yet decided. Until a license is added, default copyright applies (all rights reserved); contact the maintainer before redistributing.