Xatra (pronounced kṣatra), a tool for making historical maps – the data I have is currently focused on India (including Central and Southeast Asia), but I welcome expansions to the library. Installation:
pip install xatra
For a quick start, see examples.py e.g.
import xatra.maps.nations as nations
import xatra.maps.colonies as colonies
# any optional parameters may be set either while initializing a
# FlagMap object or in plot()
nations.INDIC.plot(path_out="examples/nations/INDIC.html")
nations.SILKRD.plot(path_out="examples/nations/SILKRD.html")
nations.SEA_GREATER.plot(path_out="examples/nations/SEA_GREATER.html")
nations.INDOSPHERE.plot(path_out="examples/nations/INDOSPHERE.html")
colonies.EARLY_SUVARNABHUMI.plot(path_out="examples/colonies/EARLY_SUVARNABHUMI.html")
colonies.SEA_ROUTES.plot(path_out="examples/colonies/SEA_ROUTES.html")
The outputs are in
Note: view this documentation,
Key ideas:
xatra.FlagMap
: Any historical visualization you’ll make is an instance of xatra.FlagMap
, which is fundamentally specified by a list of xatra.Flag
s (plus some GeoJSON data)
xatra.Flag
: A xatra.Flag
is a declaration that a particular polity (Flag.name
) ruled over some particular set of features (Flag.matcher
), optionally for some particular specific period of history (Flag.period
).
name : str
– the name of the historical polity.period : (num, num)
(only for dynamic maps) – period for which the flag is valid, e.g. (-322, 500)
= “322 BC to 500 AD” – inclusive of starting year but exclusive of ending year.matcher : xatra.Matcher
– a xatra.Matcher
object is basically a function that returns True or False for a given GeoJSON feature (dict or GeoPandas Row), i.e. the characteristic function for the set of features claimed by the flag.from xatra.data import Loka, Varuna
from xatra.maps import Flag, FlagMap
from xatra.matchers import *
flags_sample = [
Flag(name="Arjunayana", period=[-60, 80], matcher=KURU, ref="Majumdar p 29"),
Flag(name="Kuru", period=[-1100, -900], matcher=KURU),
Flag(
name="Maurya",
period=[-260, -180],
matcher=(SUBCONTINENT_PROPER | country("Afghanistan") | BALOCH)
- (BACTRIA | MARGIANA | MERU | KALINGA | TAMIL_PROPER),
),
Flag(name="Maurya", period=[-260, -180], matcher=KALINGA),
]
SampleMap = FlagMap(
flags=flags_sample,
loka=Loka.INDIC,
varuna=Varuna.INDIAN_SUBCONTINENT,
custom_html="Sample map for demonstration",
)
sample.plot(path_out="examples/sample.html")
xatra.Matcher
: A basically comprehensive list of matcher
s is given in xatra.matchers
. Useful functions:
country("India")
, province("Maharashtra")
, district("Nanded")
, taluk("Hadgaon")
etc. However you should usually use GADM Unique IDs to avoid name clashes – use examples/rdviz/INDIAN_SUBCONTINENT.html, examples/rdviz/SILKRD.html, examples/rdviz/SEA.html to easily find GIDs. Also note that for some countries like Pakistan and Nepal, “districts” are actually level-3 divisions and thus accessed by taluk()
.|
, &
, -
. Important to note that -
takes precedence over the other operators in Python, so use brackets when needed.COLA
, KOSALA
, RS_DOAB
, GY_DOAB
, BACTRIA
. These are quite comprehensive, you probably won’t have to define your own. See examples/matchers/INDIC.html, examples/matchers/SILKRD.html, examples/matchers/SEA.html, examples/matchers/INDOSPHERE.html for a visual overview of what I have.xatra.DataCollection
: The raw GeoJSON data we plot our flag lists on is stored in the xatra/data/
directory and accessed through the xatra.DataCollection
class and its load()
method in xatra.data
. Useful DataCollections are in xatra.data.Loka
and xatra.data.Varuna
. See examples/rdviz/INDIAN_SUBCONTINENT.html, examples/rdviz/SILKRD.html, examples/rdviz/SEA.html, examples/rdviz/WORLD.html to visualize the GeoJSON data. The only thing worth noting about DataCollection
s is that they can contain “breaks” DataItem(type="break", id="IND.20.20_1", level=3)
, which specify which features in the rawdata should be broken into even finer administrative detail – e.g. in the data right now, Nanded district in Maharashtra is broken into its taluks (because the ancient tracts of Asmaka and Mulaka contained different taluks of the district), and the districts of Xinjiang province are broken into their taluks (because the districts are too big and coarse, and contained multiple ancient Tarim city-states).
xatra.data
contains the Raw GeoJSONs for areas of interest to us and the class and method for loading them (DataCollection.load()
). It also contains DataCollection.download()
, which is used when preparing the package.xatra.maps.FlagMap
contains the xatra.Flag
and xatra.FlagMap
classes. Also see therein the optional arguments that can be passed while initializing a FlagMap
or to plot()
(which overrides the optional arguments set during initialization).xatra.maps
is otherwise a directory of useful and interesting example Maps. This is where I would like PRs, more than anywhere else.xatra.matchers.Matcher
is the xatra.Matcher
class, and xatra.matchers.matcherlib
is a collection of matcher
functions you can use in building your own Maps.If you’re interested in contributing to this open-source project, please do so! I’m an amateur at coding, and this is just a side-project of mine, and I’m quite busy with real work to do everything I wish I could.
Ideally if you’d like to contribute, create an issue and assign it to yourself, then start a pull request. I will review and approve pull requests daily. Here’s a to-do list of priorities you could work on:
matplotlib
implementation for dynamic maps, or work with Leaflet.js directly, idk.plot_flags()
(which plots the merged geometry for each flag instead of plotting each district and colouring them to look the same) – and use it to calculate flag centroids tooplot_flags()
to plot every intersection of flags so that more specific tooltips can be displayed.data.Pura
)plot_flags
faster by directly calculating flag_gdf
instead of from loka_with_features
plot_flags
Or descs below via kwargs in Flag
, Label