Open-source project
aourednik/historical-basemaps avatar
aourednik/historical-basemaps

historical-basemaps gives you a country map for every year, not one for today

Collection of georeferenced boundaries of world countries and cultural regions for use in mapping historical data on global or continental scale

854 stars150 forksJavaScriptGPL-3.0

At a glance

What is it?
The historical-basemaps repository collects georeferenced boundaries of world countries and cultural regions as GeoJSON, one file per year, with an auto-generated index so applications can query which years exist and which polities appear in them. It is explicitly work in progress, its properties carry a border precision scale, and its README spends as much space on disputed territories as on the data format.
Who is it for?
Adopt historical-basemaps when you are mapping historical data at world or continental scale and you need a baseline of polities for a given year rather than a modern outline. Do not adopt it for academic work without checking the boundaries against other sources, because the README calls the project work in progress and asks for errors to be reported, and do not expect a place-name history, since the settlements file is a draft.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 38 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A base map for a year, not for a country

The project is described as a collection of georeferenced boundaries of world countries and cultural regions, for use in mapping historical data on global or continental scale. The README puts the aim plainly: providing ready-to-use base maps for mapping historical data.

That is a different artifact from the country outlines most mapping libraries ship. A modern dataset describes the world as it is now. This one describes the world as it was in a given year, and the differences are not cosmetic. The example index entry for 1492 lists Spain, Portugal and the Inca Empire, three entities that no modern boundary file would contain together, and the property schema includes a field for the colonial power exercising authority over a region and a field for the larger cultural area a region belongs to, which is a way of saying that the shape of a polity and the identity of its inhabitants do not have to coincide.

The framing quote at the top of the README is from Graeber and Wengrow's The Dawn of Everything, on kingdoms and empires being exceptional islands of political hierarchy surrounded by much larger territories whose inhabitants avoided fixed overarching systems of authority. It is a good description of why a cultural region layer is in the same dataset as the political one.

The scope is global and continental. The topics list mentions prehistory, borders, geojson, gis, svg and history, and the screenshots show a world map for 1880 rendered with Leaflet.

One thing to settle before reading further is the status. The README says the project is work in progress, asks you to verify the maps by comparison with other sources before using them in academic work, and asks you to report errors in the issues section. It also asks contributors to read CONTRIBUTING.md carefully, because some kinds of edits are not appropriate for the repository.

The index is the API for a dataset of files

All the maps are stored in the GeoJSON format, one file per feature layer, described as human and machine readable and easy to import in qGIS or use in D3, with Leaflet and OpenLayers integration also described as easy. The examples directory in the repository holds D3 and Leaflet integrations, including a rough.js variant that gives the maps a hand-drawn look, a fuzzy and exact borders example, a Leaflet example and a MapLibre example.

Loading the year files one at a time is the obvious problem that follows from that layout, and the answer is index.json.

The README describes it as a lightweight summary of the dataset. It lists the year and the corresponding.geojson filename, and the list of countries in each file, based on the NAME property. The point is that an application can query which years are available, or filter by country, without loading the full files.

The entry format is simple:

json
{
  "year": 1492,
  "filename": "world_1492.geojson",
  "countries": ["Spain", "Portugal", "Inca Empire"]
}

The file is auto-generated on every commit via GitHub Actions, which means it is never stale relative to the GeoJSON in the same commit. If you have modified the data locally and want the index to match, the README gives the command:

bash
node scripts/generateIndex.js

So the repository ships data, a machine readable index, and the script that produces the index. For a web application that lets a user scrub through centuries, that index is the part you actually query, and it is small enough to fetch on first paint.

The four properties carry the historical argument

Every polygon file has the same four fields, and each one is a decision about how to draw history.

NAME is the name of the country or region, described as ideal for text labels.

SUBJECTO is the name of the colonial power exercising authority on the country or region, and the name of the region otherwise. The README suggests it is ideal for choropleth colours, which is a neat solution to a problem that modern boundary files do not have: in a year where a large part of a continent was under one power, colouring by that power shows a political reality that colouring by the modern country name would erase.

PARTOF records when a region is part of a larger cultural area, with the example of Czechs as part of the Slavic tribes.

BORDERPRECISION is the one that matters for rendering, with ordinal values where 1 is approximate, 2 is moderately precise and 3 is determined by international law. The README says it is ideal for handling approximate borders with blur intensity or another visual effect, and the examples folder contains a D3 example for fuzzy and exact borders that puts the idea into practice.

Together those fields let a map distinguish a border that was surveyed from one that is a reconstruction. That is a meaningful distinction in a dataset that spans from prehistory to the modern era, and it is the mechanism by which the repository encodes epistemic uncertainty as data rather than as a caveat in the documentation.

The geocoding precision of multipolygons is adapted for mapping data at world and continent scale, which is a deliberate simplification rather than an accident of the data.

places.geojson and the trick of repeating a point

There is one points file, places.geojson, containing the locations of cities and other settlements, and the README is blunt about its state: for now it is only a draft and needs help for completion. Places throughout history can be added to this single file.

The fields are name as a string for the toponym, inhabitedSince as an integer for the year from which the place was inhabited, and inhabitedUntil as an integer for the year until which it was, or NULL if it still is.

The interesting part is the note attached to the name field, which explains a workaround for a data model that standard GIS does not have. It would be good to have the toponym as a nested JSON object, as suggested in a QGIS issue, for places whose toponym changed over time, but that is not yet handled by standard GIS. The workaround is to create distinct points with identical coordinates for each inhabitedSince to inhabitedUntil period.

In other words, a city that changed its name in 1900 becomes two points at the same location, one for each name, each with its own date range, and a visualisation filters them by the time field to show the name that was current in the year you are looking at. It is a workaround, and the README says so, but it is the kind of decision that keeps the data loadable in tools that already exist rather than waiting for a better format.

The places image in the repository shows the current state of the draft, which is a useful reference when you decide how much effort to put into filling it in.

SVG is an export, and contributors are told not to edit it

The SVG directory is the result of converting the GeoJSON files with the R script geojson2svg.Rmd. By default the script uses a Natural Earth projection, and the README explains that you can change it by using another PROJ string than natearth2 in the st_transform call, which is a small but real convenience for anyone who wants a different look for print.

Then comes the instruction that matters for contributors: please treat these SVG files only as a starting point for a vector drawing software, for the needs of GIS-unsavvy users, and if you contribute to the repository, please edit directly, and only, the GeoJSON files.

That is a deliberate single source of truth, and it has a consequence for anyone who works this way. If you find an error in a boundary, the fix goes into the GeoJSON and the SVG is regenerated. Editing the SVG by hand produces a change that the next conversion will overwrite, and it leaves the two representations inconsistent in the meantime.

The same logic explains why the SVG is a convenience rather than a product. It exists so that someone who does not use GIS software can open a map in a vector drawing program and get a picture. The data is the repository, and the pictures are the rendering of it.

The conceptual limits, stated before the data

Most of the README after the format sections is a list of warnings, and they are worth reading in order.

Historical boundaries are even more disputed than contemporary ones. The concept of territory and national boundary becomes meaningful, in Europe, only since the Peace of Westphalia in 1648, which is the repository's way of saying that drawing a crisp line around a Bronze Age polity in central Europe is anachronistic. Areas of civilizations actually overlap, especially in ancient history. And overlaying these ancient vector maps on contemporary physical maps can be misleading, because rivers, lakes and shorelines change enormously over millennia, with the Aral Sea since the 1980s given as the example.

The repository then deals with the geometric consequence head on. Overlapping areas are usually treated as topological errors in traditional GIS, but some overlaps make sense here. The pragmatic bottom line given is that vector GIS has points, polygons and lines. When redrawing, transparent layers with opacity below 100% and blur effects are a convenient way to handle the fuzzy and overlapping borders of pre-modern societies.

That is a design decision dressed as a rendering technique, and it is the most transferable idea in the project. Rather than force the data into a shape the format was not built for, the dataset keeps the overlaps and pushes the ambiguity into the visual layer, where a reader can see that a border is uncertain.

The visual distinction between precise and approximate borders can also be achieved on the same map, and the D3 example referenced from the README shows how.

What has been built on top, and what to verify first

The README lists several applications that use the dataset, and the list is non-exhaustive. Point in History is a web app for visualising the history of any point on the planet, by Hans Hack. A web app on ourednik.info, in alpha release, lets you explore the dataset in alternative map projections. Historic Borders, a web app with a timeline, was the first implementation and is currently unavailable.

Atlas Pi is the most ambitious of the list and the one worth reading about, because it changes what you need the repository for. It is described as a public REST and MCP API of historical geography, with 1000+ polities, GeoJSON borders, events, dynastic chains and cited sources, spanning 4500 BCE to 2024, and with per-record confidence scores.

That is a different shape of artefact. The repository gives you geometry: polygons, their names, the authority over them, the cultural area they belong to, and a precision ordinal. Atlas Pi gives you a queryable service with sources and confidence attached to records, and an MCP interface so a model can ask it questions.

For a team that wants to draw a map, the repository is the dependency. For a team that wants to answer questions about historical geography, including the sources behind the answer, that API is the different approach, and the trade is that you give up the ability to fix the geometry yourself.

Whichever route you take, the README's instruction stands: verify the maps by comparison with other sources before using them in academic work.

Editorial conclusion

Adopt historical-basemaps when you are mapping historical data at world or continental scale and you need a baseline of polities for a given year rather than a modern outline. Do not adopt it for academic work without checking the boundaries against other sources, because the README calls the project work in progress and asks for errors to be reported, and do not expect a place-name history, since the settlements file is a draft. Before you build on it, read the properties of a file in qGIS, note that the year, filename and country list you need are all in index.json, and keep your own copy of the GeoJSON rather than editing the generated SVG, which the README reserves for non-GIS users.

Frequently asked questions

How do I find out which years are available in historical-basemaps?

The index.json file lists each year with its .geojson filename and the list of countries in it, based on the NAME property, and it is auto-generated on every commit through GitHub Actions. You can regenerate it locally with node scripts/generateIndex.js.

What does the BORDERPRECISION property in historical-basemaps mean?

It carries ordinal values where 1 is approximate, 2 is moderately precise and 3 is determined by international law. The README suggests using it for blur intensity or other visual effects to signal how reliable a border is.

Can I fix an error by editing the SVG files in historical-basemaps?

No. The SVG files are generated from the GeoJSON by the R script geojson2svg.Rmd and are described as a starting point for GIS-unsavvy users, and the README asks contributors to edit only the GeoJSON files directly.

How does historical-basemaps handle places whose name changed over time?

The places.geojson file is a draft, and it handles renaming with a workaround: create distinct points with identical coordinates for each inhabitedSince to inhabitedUntil period, then filter by the time fields in your visualisation software.

Official sources

  1. aourednik/historical-basemaps on GitHub
  2. Issues
  3. License: GPL-3.0
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/aourednik-historical-basemaps.svg)](https://hysenlabs.com/projects/aourednik-historical-basemaps)