This release is a pre-release and may not be stable for production use.
ChronoJSON
A time-aware extension of the GeoJSON specification.
GeoJSON answers where. ChronoJSON answers where — and when.
Documentation · Quick Start · Specification · Roadmap · Contributing
Table of Contents
- Overview
- Motivation
- Why ChronoJSON?
- Timeline Inheritance Model
- Features
- Installation
- Quick Start
- Data Model
- Milestone Types
- Metadata
- GeoJSON vs ChronoJSON
- Project Structure
- Architecture
- API Overview
- Specification Overview
- Roadmap
- Contributing
- License
- Author
- Acknowledgements
🌍 Overview
ChronoJSON is an open specification and reference Python implementation for representing time-aware geographic data. It extends the GeoJSON ecosystem with a temporal dimension, while remaining fully interoperable with existing GIS tooling.
It targets any domain where geography changes over time:
| Domain | Example use case |
|---|---|
| 🏛️ Historical GIS | Empire and border evolution |
| 📜 Digital Humanities | Annotated historical narratives |
| 🗺️ Historical Atlases | Interactive time-sliced maps |
| ⛏️ Archaeology | Site occupation timelines |
| 🏺 Cultural Heritage | Monument and site lifecycle tracking |
| 🧭 Historical Cartography | Map digitization over eras |
| 🎓 Education & Research | Teaching historical change spatially |
| 🎮 Strategy Games | Territory control over turns/time |
| 🛰️ UAV / Live Tracking | Flight paths, live vehicle location |
In one sentence: ChronoJSON stores change, not repetition.
💡 Motivation
GIS formats are excellent at describing space. They are not designed to describe change over time.
When a country's borders shift every few decades, the conventional workaround is to create a new, complete GeoJSON file for every date. This approach seems simple, but it does not scale:
- 📁 Duplicated geometries across every snapshot
- 🧾 Duplicated properties, repeated for every version
- 💾 Growing file sizes as history accumulates
- 🔧 Painful maintenance — one change means editing N files
ChronoJSON was designed to solve this at the format level, not with external tooling bolted on afterward.
❓ Why ChronoJSON?
- Single source of truth per feature. One entity, one file, full history.
- Inheritance, not duplication. Only changes are recorded; everything else is inherited automatically.
- GeoJSON-compatible core. Familiar
Feature/FeatureCollectionstructures, extended — not replaced. - Point-in-time queries. Ask "what did this feature look like in year X?" and get a resolved, immutable state.
- Tool-agnostic. Plays well with existing GIS and mapping ecosystems by design.
Why not plain GeoJSON?
GeoJSON has no concept of time. Any temporal modeling on top of it is a convention your team invents and maintains by hand — multiple files, naming schemes, manual diffing. ChronoJSON turns that convention into a first-class, validated data model.
⏳ Timeline Inheritance Model
Instead of repeating an entire feature for every date, ChronoJSON stores a base feature plus an ordered list of milestones. Any property or geometry not explicitly changed at a milestone is inherited from the previous state.
Example — Ottoman Beylik → Ottoman Empire
| Year | Event | Result |
|---|---|---|
| 1299 | creation |
Beylik founded, capital: Söğüt |
| 1326 | property_change |
Capital becomes Bursa |
| 1365 | property_change |
Capital becomes Edirne |
| 1453 | geometry_change + property_change |
Geometry updated, capital → Istanbul, name → "Ottoman Empire" |
| 1699 | geometry_change |
Territorial contraction after treaty |
| 1922 | deletion |
State dissolved |
timeline
title Ottoman Beylik → Ottoman Empire
1299 : Creation (Capital: Söğüt)
1326 : Capital → Bursa
1365 : Capital → Edirne
1453 : Geometry change, Capital → Istanbul, Name → Ottoman Empire
1699 : Geometry change
1922 : Deletion
Querying feature.at(1500) resolves every property and geometry by walking the timeline up to that date — no duplication, no manual bookkeeping.
✨ Features
Implemented
- ✅ GeoJSON-compatible format
- ✅
FeatureCollectionandFeatureobjects - ✅ Dataset-level metadata support
- ✅
ChronoandMilestoneobjects - ✅
TimeRangesupport - ✅ Multiple milestone types
- ✅ JSON loading and saving
- ✅ Timeline resolution engine
- ✅ Property inheritance
- ✅ Geometry inheritance
- ✅ Immutable resolved state
- ✅
Feature.at(year)point-in-time queries - ✅ Internal resolution cache
Planned — see Roadmap
📦 Installation
pip install chronojson
Requires Python 3.9+.
🚀 Quick Start
import chronojson
# Load a ChronoJSON dataset
collection = chronojson.load("ottoman.geojson")
# Pick a feature from the collection
feature = collection.features[0]
# Resolve its state at a specific point in time
state = feature.at(1500)
print(state.properties["capital"]) # Istanbul
print(state.properties["name"]) # Ottoman Empire
Every call to .at(year) returns an immutable, fully-resolved snapshot — geometry and properties included — computed by walking the feature's milestone timeline.
🧬 Data Model
classDiagram
class FeatureCollection {
+metadata: Metadata
+features: List~Feature~
}
class Feature {
+id: str
+base_properties: dict
+base_geometry: Geometry
+chrono: Chrono
+at(year) FeatureState
}
class Chrono {
+milestones: List~Milestone~
}
class Milestone {
+type: MilestoneType
+date: TimeRange
+properties: dict
+geometry: Geometry
}
class FeatureState {
+properties: dict
+geometry: Geometry
+year: int
}
FeatureCollection "1" --> "*" Feature
Feature "1" --> "1" Chrono
Chrono "1" --> "*" Milestone
Feature ..> FeatureState : resolves to
🏷️ Milestone Types
| Type | Status | Description |
|---|---|---|
creation |
✅ Stable | Marks the start of a feature's existence |
deletion |
✅ Stable | Marks the end of a feature's existence |
property_change |
✅ Stable | Updates one or more properties |
geometry_change |
✅ Stable | Replaces the feature's geometry |
event |
✅ Stable | Non-structural annotation (e.g. a battle, a treaty) |
merge |
🚧 Planned | Combines two or more features into one |
split |
🚧 Planned | Divides a feature into multiple features |
🗂️ Metadata
Every ChronoJSON dataset can declare descriptive metadata alongside its features:
{
"metadata": {
"title": "Ottoman Empire",
"description": "Territorial evolution of the Ottoman state, 1299–1922.",
"author": "Jane Doe",
"license": "CC-BY-4.0",
"created": "2024-01-01",
"updated": "2024-06-01",
"version": "1.0.0"
}
}
⚖️ GeoJSON vs ChronoJSON
| Aspect | GeoJSON | ChronoJSON |
|---|---|---|
| Spatial representation | ✅ Yes | ✅ Yes |
| Temporal representation | ❌ No | ✅ Yes |
| Change over time | Requires separate files | Native, via milestones |
| Duplication | High (one file per date) | Minimal (inheritance-based) |
| Point-in-time queries | Manual / external tooling | Feature.at(year) built-in |
| Backward compatibility | — | Fully compatible superset |
| File size at scale | Grows linearly with snapshots | Grows with actual changes only |
📁 Project Structure
chronojson/
├── __init__.py
├── schema.py # Core schema definitions
├── enums.py # Milestone types and enums
├── io.py # Load / save operations
├── timeline.py # Timeline resolution engine
├── validation.py # Schema and data validation
├── query.py # Query API (in progress)
├── geometry.py # Geometry inheritance logic
├── fuzzy.py # Fuzzy / uncertain date handling
├── exceptions.py # Custom exception types
│
├── converters/
│ ├── geojson.py # GeoJSON <-> ChronoJSON conversion
│ ├── csv.py # CSV import/export
│ └── pandas.py # pandas DataFrame integration
│
└── utils/
├── cache.py # Resolution caching
├── dates.py # Date/time parsing utilities
└── ids.py # Feature ID generation
🏗️ Architecture
flowchart LR
A[ChronoJSON File] --> B[io.py<br/>Load]
B --> C[schema.py<br/>Parse into Feature/Chrono objects]
C --> D[validation.py<br/>Validate structure]
C --> E[timeline.py<br/>Resolution Engine]
E --> F[geometry.py<br/>Geometry inheritance]
E --> G[Property inheritance]
F --> H[utils/cache.py]
G --> H
H --> I[Feature.at year]
I --> J[Resolved FeatureState]
🧩 API Overview
# Loading and saving
collection = chronojson.load("path/to/file.geojson")
chronojson.save(collection, "output.geojson")
# Accessing features
feature = collection.features[0]
# Resolving state at a point in time
state = feature.at(1453)
state.properties # dict of resolved properties
state.geometry # resolved geometry object
# Inspecting the timeline directly
for milestone in feature.chrono.milestones:
print(milestone.type, milestone.date)
📘 A full API reference will be published alongside the Query API release.
📐 Specification Overview
ChronoJSON is designed as an open specification, not just a Python library. The reference implementation in this repository is one conformant implementation; others (e.g. a future JavaScript SDK) are expected to follow the same document model:
FeatureCollection→Feature→Chrono→Milestone- All coordinate and geometry structures remain RFC 7946 (GeoJSON) compatible
- Temporal resolution is deterministic and order-dependent on milestone dates
A formal specification document is tracked under Roadmap.
🗺️ Roadmap
- Validation Engine (schema + semantic validation)
-
mergeandsplitmilestone support - Query API (filter features by time range, property, region)
- JavaScript SDK
- Complete formal specification document
- CLI tool
- GeoPandas integration
- Leaflet integration
- OpenLayers integration
- MapLibre integration
- Temporal GIS utility functions
🤝 Contributing
Contributions are welcome, whether you're fixing a bug, improving documentation, or proposing an addition to the specification.
How to contribute
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes and add tests where applicable
- Run the test suite:
pytest - Open a pull request with a clear description of the change
Please open an issue first for significant changes, especially anything touching the core specification, so it can be discussed before implementation work begins.
📄 License
This project is licensed under the MIT License. See LICENSE for details.
👤 Author
Maintained by Ahmet Koçyiğit. Feel free to open an issue or start a discussion for questions, ideas, or feedback.
🙏 Acknowledgements
- The GeoJSON specification, for the spatial foundation this project builds on
- The broader GIS and Digital Humanities communities whose use cases shaped this project's direction
ChronoJSON — because geography has a history.
Metadata
Release files for chronojson 0.1.0b2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| chronojson-0.1.0b2.tar.gz | 15.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| chronojson-0.1.0b2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.8 kB
Release files / chronojson-0.1.0b2.tar.gz
| Download URL | chronojson-0.1.0b2.tar.gz |
|---|---|
| Size | 15.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e668da26608587dc80965361a2ae5e317cda2dca0c0b1a52de7d615246fe745e
|
|
BLAKE2b-256 checksum How to use checksums |
48ffe75ec4a2ade690575c5f235210889bc67b7d0311c6dc868ef7d0b71406d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.0
|
Release files / chronojson-0.1.0b2-py3-none-any.whl
| Download URL | chronojson-0.1.0b2-py3-none-any.whl |
|---|---|
| Size | 11.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fe90bb764e4f9eefaf930bef89c527b27a20969a7f571ed2541a91595dc4fa35
|
|
BLAKE2b-256 checksum How to use checksums |
d5fe9e32c7a6cf16aeebd2e9d48a2bbc7a29951854d4a1ce2eb2e44d7fee3e36
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.0
|