django-whosonfirst
A Django app for importing and managing Who's On First geographic data.
Who's On First (WOF) is an open, global gazetteer that provides stable place IDs, explicit hierarchies, and polygon geometries for geographic entities—from continents and countries down to cities and neighbourhoods.
Features
- Download WOF SQLite data bundles (global or per-country)
- Import places with full hierarchy (continent → country → region → locality → neighbourhood)
- Store geometries in PostGIS
- Map to GeoNames and Wikidata via concordances
- Extensible plugin system for custom processing
Requirements
- Python 3.10+
- Django 4.2+
- PostgreSQL with PostGIS extension
django.contrib.gisconfigured
Installation
pip install django-whosonfirst
Add to your INSTALLED_APPS:
INSTALLED_APPS = [
# ...
'django.contrib.gis',
'whosonfirst',
]
Run migrations:
python manage.py migrate whosonfirst
Quick Start
1. Download WOF Data
Download data for specific countries:
# Download US and Canada
python manage.py wof_download --countries=US,CA
# Download all countries (large!)
python manage.py wof_download
2. Import Data
# Import downloaded data
python manage.py wof_import
# Import specific countries
python manage.py wof_import --countries=US,CA
# Import specific placetypes
python manage.py wof_import --placetypes=country,region,locality
# Import without geometry (faster, smaller database)
python manage.py wof_import --no-geometry
3. Query Places
from whosonfirst.models import Place
# Get all countries
countries = Place.objects.filter(placetype='country')
# Get cities in the US
us_cities = Place.objects.filter(
placetype='locality',
country_code='US'
)
# Get a place by WOF ID
sf = Place.objects.get(wof_id=85922583) # San Francisco
# Get ancestors
sf.get_ancestors() # Returns California, US, North America
# Get descendants
ca = Place.objects.get(wof_id=85688637) # California
ca.get_descendants(placetype='locality') # All cities in CA
# Query by GeoNames ID
place = Place.objects.get(geonames_id=5391959)
# Query by Wikidata ID
place = Place.objects.get(wikidata_id='Q62')
Management Commands
wof_download
Download WOF SQLite data bundles.
# Download specific countries
python manage.py wof_download --countries=US,CA,MX
# Force re-download
python manage.py wof_download --countries=US --force
# List cached bundles
python manage.py wof_download --list
# Clear cache
python manage.py wof_download --clear
wof_import
Import WOF data into Django models.
# Import all downloaded data
python manage.py wof_import
# Import specific countries
python manage.py wof_import --countries=US
# Import specific placetypes
python manage.py wof_import --placetypes=country,region,locality,neighbourhood
# Flush existing data before import
python manage.py wof_import --flush=all
# Dry run (parse without saving)
python manage.py wof_import --dry-run
# Skip geometry import
python manage.py wof_import --no-geometry
# Skip alternative names
python manage.py wof_import --no-names
wof_update
Update data (download fresh and import changes).
python manage.py wof_update --countries=US
# Check for changes without importing
python manage.py wof_update --check-only
wof_build_index
Build derived indices and resolve relationships.
# Run all index operations
python manage.py wof_build_index --all
# Resolve parent relationships only
python manage.py wof_build_index --resolve-parents
Configuration
Configure via Django settings:
# Data directory for downloads (default: app's data/ directory)
WOF_DATA_DIR = '/path/to/wof/data'
# Placetypes to import by default
WOF_PLACETYPES = [
'continent',
'country',
'region',
'locality',
'neighbourhood',
]
# Country codes to import (empty = all)
WOF_COUNTRY_CODES = ['US', 'CA', 'MX']
# Whether to import geometry (default: True)
WOF_IMPORT_GEOMETRY = True
# Skip deprecated places (default: True)
WOF_SKIP_DEPRECATED = True
# Skip places marked as not current (default: False)
WOF_SKIP_NOT_CURRENT = False
# Languages to import for alternative names (ISO 639-3 codes)
# Default: ['eng'] (English only)
# Use empty list [] to import all languages
WOF_LANGUAGES = ['eng']
# HTTP download timeout in seconds (default: 300)
WOF_DOWNLOAD_TIMEOUT = 300
# Maximum download size in bytes (default: 10GB)
WOF_MAX_DOWNLOAD_SIZE = 10 * 1024 * 1024 * 1024
# Batch size for bulk operations (default: 1000)
WOF_BATCH_SIZE = 1000
# Plugin classes for custom processing
WOF_PLUGINS = ['myapp.plugins.MyWOFPlugin']
Placetypes
WOF uses a hierarchy of placetypes:
| Placetype | Description | Example |
|---|---|---|
| continent | Continents | North America |
| country | Countries | United States |
| dependency | Dependencies | Puerto Rico |
| region | First-level admin (states/provinces) | California |
| county | Counties | Los Angeles County |
| localadmin | Local administrative areas | City of Los Angeles |
| locality | Cities/towns | San Francisco |
| borough | Boroughs | Manhattan |
| neighbourhood | Neighbourhoods | Mission District |
| venue | Points of interest | Golden Gate Bridge |
Default import includes: continent, country, dependency, region, county, localadmin, locality
Models
Place
The main model storing all WOF places.
class Place(models.Model):
# Identity
wof_id = BigIntegerField(primary_key=True)
placetype = CharField(max_length=50)
name = CharField(max_length=255)
slug = SlugField(max_length=255)
country_code = CharField(max_length=2)
# Hierarchy
parent = ForeignKey('self', null=True)
parent_wof_id = BigIntegerField(null=True)
belongsto = JSONField(default=list) # Ancestor WOF IDs
hierarchy = JSONField(default=list)
# Geometry (PostGIS)
geom = MultiPolygonField(null=True, srid=4326)
centroid = PointField(null=True, srid=4326)
latitude = FloatField(null=True)
longitude = FloatField(null=True)
bbox = JSONField(null=True)
# Concordances
concordances = JSONField(default=dict)
geonames_id = BigIntegerField(null=True)
wikidata_id = CharField(max_length=20)
# Metadata
lastmodified = BigIntegerField(null=True)
is_current = BooleanField(default=True)
is_deprecated = BooleanField(default=False)
PlaceName
Alternative names in different languages.
class PlaceName(models.Model):
place = ForeignKey(Place)
name = CharField(max_length=255)
language = CharField(max_length=10) # ISO 639-3
name_type = CharField(max_length=20) # preferred, variant, etc.
is_preferred = BooleanField(default=False)
Plugins
Create custom plugins to hook into the import process:
# myapp/plugins.py
from whosonfirst.exceptions import HookException
class MyWOFPlugin:
def place_pre(self, command, item):
"""Called before parsing each place.
Args:
command: Management command instance
item: Raw data dict from parser
Raises:
HookException: Skip this item
"""
# Skip places without geometry
if not item.get('geometry'):
raise HookException('No geometry')
# Modify item in place
item['name'] = item['name'].title()
def place_post(self, command, obj, item):
"""Called after saving each place.
Args:
command: Management command instance
obj: Saved Place model instance
item: Raw data dict
"""
if obj.placetype == 'locality':
# Custom processing for cities
pass
Register in settings:
WOF_PLUGINS = ['myapp.plugins.MyWOFPlugin']
Concordances
WOF includes mappings to external datasets. Access via the concordances JSONField or dedicated fields:
place = Place.objects.get(name='San Francisco', placetype='locality')
# GeoNames ID
place.geonames_id # 5391959
# Wikidata QID
place.wikidata_id # 'Q62'
# All concordances
place.concordances
# {
# 'geonames_id': 5391959,
# 'wikidata_id': 'Q62',
# 'fips_code': '06075',
# 'iso_code': 'US-CA',
# ...
# }
Disclaimer
This project is not affiliated with, endorsed by, or associated with Who's On First, Mapzen, or Geocode Earth. It is an independent, third-party Django integration that consumes publicly available Who's On First data.
License
MIT License
WOF Data License
Who's On First data is licensed under various open licenses. See WOF Licenses for details.
When using WOF data, please provide appropriate attribution.
Links
Metadata
Release files for django-whosonfirst 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_whosonfirst-0.1.0.tar.gz | 35.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_whosonfirst-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.5 kB
Release files / django_whosonfirst-0.1.0.tar.gz
| Download URL | django_whosonfirst-0.1.0.tar.gz |
|---|---|
| Size | 35.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
869daa5cb8d61230eb1a227281d250230d350945b1a797df8fc14e0bf2ad239c
|
|
BLAKE2b-256 checksum How to use checksums |
5091b8dc76272009df975c4896ce485d6d939509e4c146a78325aceea31151e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 3, 2026.
Transparency logRelease files / django_whosonfirst-0.1.0-py3-none-any.whl
| Download URL | django_whosonfirst-0.1.0-py3-none-any.whl |
|---|---|
| Size | 45.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e03cd5f9462acf87886974429d5d30132331013f426f477e53543f6ff535e9c5
|
|
BLAKE2b-256 checksum How to use checksums |
ac886a9ca81d958deabb3b3c46ec8dafe16c82e3fd5ca558725a0aa7e44d1098
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 3, 2026.
Transparency log