An open-source interactive atlas of human history.
Live at openhistory.app
Built with MapLibre GL JS, React, TypeScript, FastAPI, and PostgreSQL. Polities and events sourced from Wikidata; territory boundaries from OpenHistoricalMap.
Territory boundaries (the shaded regions on the map) come from OpenHistoricalMap (OHM), a community-driven project that maps historical boundaries with day-level precision (CC0). A territory is linked to a polity when its OHM relation carries a wikidata=Q… tag pointing at the matching Wikidata entity — when that link exists, the boundary, the label, and the polity card all refer to the same entity, and colors propagate across the parent hierarchy.
Anyone can contribute boundary edits directly on the OHM website and they appear here automatically on the next tile refresh.
The ground truth for all events, locations, and political entities is Wikipedia and its structured data layer, Wikidata (CC BY-SA). The pipeline queries the Wikidata SPARQL API to fetch:
- Events — battles, elections, treaties, disasters, discoveries, etc., each with a date and location
- Locations — cities, regions, and countries referenced by events
- Polities — kingdoms, empires, republics, colonies, viceroyalties, indigenous nations, peoples, and other political entities. They may have founding/dissolution dates and a capital, and can be linked to a territory via the OHM
wikidatatag.
Polities know their parent state at each point in time (e.g. Saxony → German Confederation 1815–1866, Viceroyalty of Peru → Spanish Empire). This is derived from Wikidata signals (P150, P361, P131, P127, P17) with year-range intersection so links are temporally accurate. Multiple parents over time are supported.
The color cascade reflects this hierarchy: child polities render in the same color as their umbrella entity at the current timeline year. See docs/polity-parent-coloring.md for details.
When you correct a date or location for an event, location, or polity, that change is submitted directly to Wikidata — it improves the source data for everyone, not just OpenHistory. To make edits you need a free Wikimedia account. Click any feature on the map, then use the edit button in the info panel to log in and submit a correction.
A territory appears in grey when its OHM boundary isn't yet tagged with a Wikidata QID. The fix lives on OpenHistoricalMap itself — add a wikidata=Q… tag to the relation and OpenHistory will pick it up on the next tile refresh:
- Find the territory on openhistoricalmap.org and open it in the iD editor
- Look up the matching Wikidata entity (e.g. on wikidata.org) to get its Q-ID
- Add a
wikidatatag with that Q-ID to the OHM relation, and save
You'll need a free OpenHistoricalMap account (OSM-style login). An in-app one-click tagging flow is planned but not live yet.
Boundary shapes (where a territory's edges lie) are edited on OpenHistoricalMap itself — they're not stored in OpenHistory. Open the territory on openhistoricalmap.org in OHM's iD editor and adjust the polygon vertices. Edits propagate to OpenHistory on OHM's next tile refresh.
| Layer | Tech |
|---|---|
| Frontend | React 18, TypeScript, Vite, MapLibre GL JS |
| Backend | FastAPI (Python), PostgreSQL 16 (Railway-hosted) |
| Data | Wikidata SPARQL, Wikipedia REST API, OHM Overpass + vector tiles |
| Map | OpenFreeMap basemap tiles |
| Infrastructure | Railway (DB + production hosting) |
The core map, data pipeline, and deployment are working. Current focus is expanding data coverage, polishing the polity-cascade UX, and adding the in-app OHM tagging flow.
- Polity parent linkage — every polity knows its umbrella entity at the relevant year (German Confederation, Spanish Empire, etc.); child polygons inherit parent color
- Capital-city color grouping — Spain / Spanish Empire / Crown of Castile all share a color via shared Madrid capital
- OHM direct integration — vector tiles + OAuth scaffolding (one-click tagging in progress)
- Importance-driven label sizing — bigger labels for more globally significant polities
- Promoted orphan dated regions — Lower Canada, Congress Poland, Captaincy of Venezuela, etc. now first-class polities
- In-app one-click OHM tagging flow (OAuth is wired, the write path isn't live yet)
- Manual override registry for Wikidata curation gaps (e.g. Bavaria has no Wikidata link to the German Confederation)
- Natural language search over events/polities (likely via Wikidata Embeddings + pgvector)
- Polity succession chain (
preceded_by_qid/succeeded_by_qidare stored but not surfaced in the UI)
- Node.js 20+
- Python 3.10+
- PostgreSQL client (
psql)
OpenHistory uses a hosted PostgreSQL database on Railway. There is no local database — all scripts read DATABASE_URL from the environment.
cp .env.example .env
# Fill in DATABASE_URL with your Railway Postgres URL
# Add ANTHROPIC_API_KEY if you want to use LLM category assignment
source .envsource .env
for f in db/migrations/*.sql; do
echo "Applying $f..."
psql "$DATABASE_URL" -f "$f"
doneMigrations are in db/migrations/ and numbered sequentially. They are idempotent (IF NOT EXISTS, ON CONFLICT, etc.) so re-running is safe.
# Events
python3 -m pipeline.run_local --min-year 1770 --max-year 1820
# Polities
python3 -m pipeline.run_polities --min-year 1770 --max-year 1820
# Post-processing: cleanup, parent backfill, sitelinks, GeoJSON export
python3 -m pipeline.post_processcd frontend && npm install && npm run devOpen http://localhost:5173. The Vite dev server proxies /api to localhost:8000.
source .env && uvicorn server.main:app --reload --port 8000All historical data flows from Wikidata / OHM through the pipeline into Postgres, then exported to GeoJSON files consumed by the frontend.
| Class | Table | GeoJSON featureType |
Description |
|---|---|---|---|
| Historical event | events |
event |
Battles, treaties, disasters, etc. |
| Location | locations |
city / region |
Geographic anchors used to pin events |
| Polity | polities |
polity |
Time-bounded political entities (empires, kingdoms, colonies, etc.) |
| Territory polygon | — (served as OHM vector tiles) | — | Boundary shapes; linked to polities via OHM's wikidata tag |
Time-bounded political entities — historically specific ("French First Republic 1792–1804", not just "France"). Each row has:
wikidata_qid— Wikidata Q-identifier (unique)polity_type—empire/kingdom/principality/republic/confederation/sultanate/papacy/colony/people/otheryear_start/year_end— active periodcapital_name/capital_wikidata_qid— capital city (used for the color-cascade fallback)lng/lat— representative point (capital coordinates)preceded_by_qid/succeeded_by_qid— succession chainparents— JSONB array of{qid, yearStart, yearEnd, source}entries describing each parent at each time slice (seedocs/polity-parent-coloring.md)
| Column | Type | Description |
|---|---|---|
wikidata_qid |
TEXT UNIQUE | Wikidata Q-identifier |
title |
TEXT | Display title |
year_start / year_end |
INT | Year range (negative = BCE) |
date_is_fuzzy |
BOOL | Approximate date |
location_level |
TEXT | point, city, region, country, or NULL |
lng / lat |
FLOAT | Direct coords (point-level events only) |
location_wikidata_qid |
TEXT | Soft ref to locations.wikidata_qid |
categories |
TEXT[] | e.g. ['battle', 'war'] |
part_of_qids |
TEXT[] | Wikidata P361 (part-of) parent event QIDs |
sitelinks_count |
INT | Wikipedia language editions (importance signal for zoom-aware label sizing) |
- Point — event has its own P625 coordinates →
location_level = 'point' - P276 — event's P276 (location) QID resolves to a location record
- P17 fallback — event's P17 (country) QID →
location_level = 'country' - Unlocated — no location data → stored but not shown on map
docs/polity-parent-coloring.md— full reference for the polity parent-linkage and color cascade systemdocs/data-sources.md— data-source details (licensing, refresh cadence)docs/territory-linking.md— territory ↔ polity linking semantics
- Code — MIT
- Event / location / polity data — CC BY-SA (Wikidata / Wikipedia contributors)
- Territory boundaries — CC0 (OpenHistoricalMap contributors)
- Basemap tiles — © OpenFreeMap
Found a bug or have an idea? Open an issue or pull request on GitHub.
