By Abdirahman Ahmed (Maano)
"Faafinta aqoonta, iyo xogta Soomaliyeed ee furan."
(Open Somali Data Initiative)
The first open geographic API for Somalia — mapping regions, districts, roads, ports, and postal codes.
Free and open for developers, researchers, and Somali communities everywhere.
An open-source, production-ready API providing Somali geographic and infrastructure data. The first comprehensive open geographic API for Somalia, combining administrative boundaries, roads, transport infrastructure, and postal codes.
✅ PRODUCTION-READY with 26,343 real Somalia-only geographic items loaded:
- ✅ 36 Regions (GADM - Administrative Boundaries)
- ✅ 148 Districts (GADM - Administrative Boundaries)
- ✅ 26,046 Roads (OpenStreetMap - Major cities road network)
- ✅ 23 Airports (OpenStreetMap - Somalia only, verified and cleaned)
- ✅ 8 Ports (Major Somali ports: Mogadishu, Berbera, Bosaso, Kismayo, Merca, Hobyo, Garacad, Las Khorey)
- ✅ 17 Checkpoints (OpenStreetMap - Somalia only, filtered)
✅ All data filtered to Somalia bounding box - No non-Somalia locations included.
This API serves Somali geographic and governance data in clean, structured, and open formats. It works like OpenStreetMap API, GADM boundaries API, and Google PlusCodes resolver - but focused 100% on Somali data.
- 🗺️ Administrative Boundaries: 36 regions, 148 districts with full GeoJSON geometries
- 🧭 Postal Codes: Google Open Location Code (PlusCodes) with Somali region prefixes
- 🛣️ Road Infrastructure: 26,046+ roads from OpenStreetMap (major cities)
✈️ Transport: 23 verified airports, 8 major Somali ports, 17 checkpoints (all Somalia only)- 🔍 Search: Fuzzy place name search with aliases
- 📊 Open Data: All data served as GeoJSON with ODbL licensing
- Python 3.11+
- SQLite (default) or PostgreSQL/PostGIS (future)
- Clone the repository:
git clone https://github.com/AbdirahmanNomad/somali-geo-api.git
cd somali-geo-api- Install dependencies:
cd backend
pip install -r requirements.txt
# or with uv
uv sync- Set up environment variables:
cp .env.example .env
# Edit .env with your settings- Download and load real data:
# Download real data from GADM and OpenStreetMap
python scripts/download_and_load_real_data.py
# Or download OSM data separately
python scripts/download_osm_complete.py
# Load all data into database
python scripts/load_geodata.py- Run the API:
uvicorn app.main:app --reloadThe API will be available at http://localhost:8000
- A minimal static viewer is included to test and visualize endpoints quickly.
- Location:
frontend/simple
Run locally:
cd frontend/simple
python3 -m http.server 8080
# open http://127.0.0.1:8080Notes:
- CORS is already configured to allow http://localhost:8080 during local development.
- Use the endpoint dropdown, filters, and the Location Codes panel to test the API.
- Viewer features: region/type filters, pagination (skip/limit), marker clustering for transport layers.
- In local environment, the backend auto-runs a light data cleanup on startup:
- Assigns
regionfor checkpoints, airports, and ports via point-in-polygon against region polygons - Removes transport points outside Somalia polygons
- Assigns
- This keeps results Somalia-only and labeled, without PostGIS. For production-scale spatial queries, migrate to PostGIS.
After loading data, verify what's loaded:
cd backend
python -c "
from app.core.db import engine
from app import models
from sqlmodel import Session, select
with Session(engine) as db:
print(f'Regions: {len(db.exec(select(models.Region)).all())}')
print(f'Districts: {len(db.exec(select(models.District)).all())}')
print(f'Roads: {len(db.exec(select(models.Road)).all())}')
print(f'Airports: {len(db.exec(select(models.Airport)).all())}')
print(f'Ports: {len(db.exec(select(models.Port)).all())}')
print(f'Checkpoints: {len(db.exec(select(models.Checkpoint)).all())}')
"- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- OpenAPI Schema: http://localhost:8000/api/v1/openapi.json
# Regions
curl "http://localhost:8000/api/v1/regions/export" -o regions.geojson
# Districts
curl "http://localhost:8000/api/v1/districts/export" -o districts.geojson
# Roads
curl "http://localhost:8000/api/v1/roads/export" -o roads.geojsonThis project runs on SQLite by default. To enable spatial queries (nearby, within polygon), migrate to PostGIS:
- Provision PostgreSQL with PostGIS extension.
- Set environment variables in
.env:DATABASE_URL=postgresql+psycopg://USER:PASSWORD@HOST:PORT/DBNAME
- Run Alembic migrations (or re-run loaders to populate PostGIS).
- Replace Haversine approximations with ST_DWithin/ST_Contains queries in
/roads/nearbyand future polygon endpoints.
- In-memory caching is enabled for hot endpoints scaffold (can be swapped to Redis).
- Redis plan:
- Add
REDIS_URL=redis://localhost:6379/0to.env - Replace in-memory cache with Redis client in
app/core/cache.py.
- Add
- Secrets: never use defaults in production.
- Set
SECRET_KEY,FIRST_SUPERUSER,FIRST_SUPERUSER_PASSWORDin.env.
- Set
- CI matrix:
- Align Node to v25 and pin Python (e.g., 3.11.x) in workflows.
- Build backend and frontend; run endpoint tests.
- Rate limiting: optional middleware scaffold added; enable via dependency if needed.
- Endpoint tests: add coverage for regions/districts/roads/transport/locationcode.
- Loader tests: verify counts and Somalia-only filters (bbox/polygon).
- Manual checks:
/api/v1/regions/export→ 36 features/api/v1/districts/export→ 148 features/api/v1/roads/export→ 26,046 features/api/v1/roads/nearby?lat=2.0469&lon=45.3182&radius_km=5→ should return results
curl "http://localhost:8000/api/v1/roads?format=geojson&limit=10"# Roads within 5km of Mogadishu Port
curl "http://localhost:8000/api/v1/roads/nearby?lat=2.0469&lon=45.3182&radius_km=5"
# Districts (places) within 10km of Mogadishu Port
curl "http://localhost:8000/api/v1/places/nearby?lat=2.0469&lon=45.3182&radius_km=10"GET /api/v1/regions
GET /api/v1/regions/{id}
GET /api/v1/districts?region={name}
GET /api/v1/districts/{id}Example: Get all regions
curl http://localhost:8000/api/v1/regionsExample Response:
{
"data": [
{
"id": 1,
"name": "Banaadir",
"code": "SOM-BAN",
"population": null,
"area_km2": null,
"geometry": {
"type": "MultiPolygon",
"coordinates": [...]
}
}
],
"count": 36
}Example: Get districts in a region
curl "http://localhost:8000/api/v1/districts?region=Banadir"Example District Response:
{
"data": [
{
"id": 1,
"name": "Mogadishu",
"code": "SOM-BNR-MGD",
"region_name": "Banadir",
"population": null,
"aliases": ["Xamar", "Hamari"],
"centroid": {"lat": 2.0, "lon": 45.3},
"geometry": {...}
}
],
"count": 5
}GET /api/v1/locationcode/generate?lat={lat}&lon={lon}
GET /api/v1/locationcode/resolve?code={code}Generate Example:
curl "http://localhost:8000/api/v1/locationcode/generate?lat=2.0343&lon=45.3201"Response:
{
"code": "8FJ53+PM",
"latitude_center": 2.0343,
"longitude_center": 45.3201,
"region_code": "SOM-BNR"
}Resolve Example:
curl "http://localhost:8000/api/v1/locationcode/resolve?code=8FJ53+PM"GET /api/v1/roads
GET /api/v1/roads/{id}
GET /api/v1/transport/airports
GET /api/v1/transport/airports/{id}
GET /api/v1/transport/ports
GET /api/v1/transport/ports/{id}
GET /api/v1/transport/checkpoints
GET /api/v1/transport/checkpoints/{id}Example: Get roads
curl "http://localhost:8000/api/v1/roads?limit=10"Example Response:
{
"data": [
{
"id": 1,
"name": "KM4",
"type": "primary",
"length_km": null,
"condition": null,
"surface": null,
"geometry": [[45.3108, 2.0314], [45.3109, 2.0315], ...]
}
],
"count": 26046
}Example: Get airports
# Get all airports
curl "http://localhost:8000/api/v1/transport/airports?limit=5"
# Filter by type (international or domestic)
curl "http://localhost:8000/api/v1/transport/airports?type=international"
curl "http://localhost:8000/api/v1/transport/airports?type=domestic"Example: Get roads with filtering
# Get all roads
curl "http://localhost:8000/api/v1/roads?limit=10"
# Filter by type (primary or secondary)
curl "http://localhost:8000/api/v1/roads?type=primary"
curl "http://localhost:8000/api/v1/roads?type=secondary"GET /api/v1/places/search?name={name}&limit={limit}Example:
curl "http://localhost:8000/api/v1/places/search?name=mogadishu&limit=5"Response:
{
"data": [
{
"id": "SOM-BNR-MGD",
"name": "Mogadishu",
"region": "Banadir",
"type": "district",
"aliases": ["Xamar", "Hamari"],
"centroid": {"lat": 2.0, "lon": 45.3},
"population": null
}
],
"count": 1
}backend/
├── app/
│ ├── api/v1/endpoints/ # API endpoints
│ │ ├── regions.py
│ │ ├── districts.py
│ │ ├── roads.py
│ │ ├── location_codes.py
│ │ ├── places.py
│ │ └── transport.py
│ ├── core/ # Configuration
│ ├── models/ # Database models
│ ├── utils/ # Utilities (OLC helper)
│ └── data/ # GeoJSON data files
├── scripts/
│ └── load_geodata.py # Data loading script
└── tests/ # API tests
| Data Type | Count | Source | Format | License |
|---|---|---|---|---|
| Administrative Boundaries | 36 regions, 148 districts | GADM | GeoJSON | Free |
| Roads | 26,046 roads | OpenStreetMap | GeoJSON | ODbL |
| Airports | 59 airports (Somalia only, verified) | OpenStreetMap | GeoJSON | ODbL |
| Ports | 8 major Somali ports (Mogadishu, Berbera, Bosaso, Kismayo, Merca, Hobyo, Garacad, Las Khorey) | Manual data entry | GeoJSON | ODbL |
| Checkpoints | 46 checkpoints (Somalia only) | OpenStreetMap | GeoJSON | ODbL |
| Location Codes | Algorithm-based | Google Open Location Code | API | Apache 2.0 |
Note: All transport data (airports, ports, checkpoints) is geographically filtered to only include locations within Somalia's bounding box.
scripts/download_and_load_real_data.py- Download GADM and geoBoundaries datascripts/download_osm_complete.py- Download all OpenStreetMap datascripts/download_roads_alternative.py- Alternative roads download (city-based)scripts/load_geodata.py- Load all GeoJSON files into database
- Framework: FastAPI (Python 3.11+)
- Database: SQLite (current), PostGIS (future upgrade)
- ORM: SQLModel + SQLAlchemy
- Data Format: GeoJSON (standard)
- Location Codes: Google Open Location Code library
- Deployment: Docker, Render/Railway/Fly.io
- Data Processing: Python with requests, geopandas (optional)
✅ Regions: 36 (GADM - Administrative Boundaries)
✅ Districts: 148 (GADM - Administrative Boundaries)
✅ Roads: 26,046 (OpenStreetMap - Major Cities Road Network)
✅ Airports: 59 (OpenStreetMap - Somalia Only, Verified ✓)
✅ Ports: 8 (Major Somali Ports: Mogadishu, Berbera, Bosaso, Kismayo, Merca, Hobyo, Garacad, Las Khorey)
✅ Checkpoints: 46 (OpenStreetMap - Somalia Only, Filtered ✓)
─────────────────────────────────────────────────────────────
📊 TOTAL: 26,278 Real Somalia Geographic Items
✅ All data is filtered to Somalia bounding box:
- Latitude: 1.5° to 11.5° (South to North) - excludes border areas
- Longitude: 41.0° to 51.5° (West to East)
All data is real, verified, Somalia-only, and sourced from authoritative providers (GADM, OpenStreetMap).
# Build and run
docker-compose up --build- Set environment variables
- Run data loader:
python scripts/load_geodata.py - Start server:
uvicorn app.main:app --host 0.0.0.0 --port 8000
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
- Submit GeoJSON files for new regions/districts
- Report data inaccuracies
- Add missing place name aliases
- Contribute transport infrastructure data
- Improve road condition data
- Add population statistics
Recent Improvements Completed:
- ✅ Geographic filtering - All transport data filtered to Somalia only
- ✅ Airport/road type filtering - Filter airports by
?type=internationalor?type=domestic - ✅ Enhanced error messages - 404 errors now show available IDs
- ✅ Database indexes - All key fields indexed for performance
Planned Enhancements:
- Spatial queries (nearby roads, places within bounding box)
- Population data integration
- PostGIS migration for advanced spatial queries
- Redis caching layer
- API rate limiting
- Automated data refresh pipeline
- ✅ Data Quality: All transport data now filtered to Somalia bounding box - FIXED
- Road Coverage: Roads downloaded from major cities (Mogadishu, Hargeisa, Bosaso, Kismayo); full country coverage may be incomplete
- Population Data: Not all regions/districts have population statistics
- Spatial Queries: Currently using SQLite; PostGIS upgrade planned for advanced spatial queries
- Transport Data: OSM may have limited coverage for Somalia; some airports/ports/checkpoints may be missing from OSM
- PostGIS migration for spatial queries
- Redis caching layer
- Nearby/within-bbox spatial endpoints
- Population data integration
- Road condition updates
- Automated data refresh pipeline
- API rate limiting
- Enhanced search with fuzzy matching
Code: MIT License Data: ODbL (Open Data Commons Open Database License)
Data sources include:
- © OpenStreetMap contributors
- © GADM
- © OCHA Somalia
- © FAO Somalia
- FastAPI community for the excellent framework
- Google for Open Location Code library
- OpenStreetMap contributors
- Humanitarian data providers (OCHA, HDX)
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: API Docs
Built with ❤️ for Somalia's open data community