National Disasters Tracker
National Disasters Canada (NDC) Tracker is a distributed system for reporting and monitoring environmental disasters across geographical regions. It implements a registry-based architecture where a central Hub coordinates multiple independent regional Subhubs—each responsible for a specific geographic zone defined by coordinates and a coverage radius. The frontend integrates LeafletJS for interactive map visualization, allowing users to report, edit, and geographically pin disasters in real-time, while the backend uses geofencing algorithms to automatically route requests to the correct regional node.
Architecture & How It Works
The system is split into three tiers: a Central Hub (hub.py) acting as a Service Registry and Discovery API, multiple Regional Subhubs (subhub.py) managing CRUD operations for their geographic zone, and a Next.js frontendwith LeafletJS map rendering. When a subhub starts, it self-registers with the hub via a POST request. The frontend queries the hub for active subhubs, then communicates directly with the relevant subhub for disaster data—creating a truly distributed system where data is “owned” by the node covering that area.
1. Service Registry & Self-Registration
The Central Hub acts as a Service Registry. When each regional subhub starts, it automatically sends a POST request to the hub with its metadata—IP address, port, geographic coordinates, and coverage radius. The hub stores this in an in-memory registry and exposes a /get_subhubs discovery endpoint for the frontend.
# Subhub self-registration on startup
def register_with_hub(hub_host, hub_port, metadata):
"""Called once when subhub.py initializes"""
url = "http://" + hub_host + ":" + str(hub_port) + "/register"
requests.post(url, json={
"id": metadata["id"],
"name": metadata["name"], # e.g. "Hamilton"
"ip": metadata["ip"],
"port": metadata["port"],
"latitude": metadata["latitude"], # 43.26
"longitude": metadata["longitude"], # -79.77
"radius_km": metadata["radius_km"] # 50
})2. Geospatial Matching (Geofencing)
The Hub implements an is_point_in_circle geofencing algorithm to match user coordinates to the nearest registered subhub. This determines which regional node should handle a disaster report:
- ●Haversine Distance: Calculates the great-circle distance between the user's coordinates and each subhub's center point using the Haversine formula.
- ●Radius Check: If the computed distance is less than the subhub's
radius_km, the point falls within that subhub's coverage zone. - ●Automatic Routing: The frontend uses this to route disaster CRUD operations directly to the correct subhub API, bypassing the hub for data operations.is_point_in_circle(user_lat, user_lon, hub_lat, hub_lon, radius_km) => boolean
3. Factory Pattern & CSV Persistence
Each subhub uses a DisasterFactory to instantiate typed disaster objects—Natural, Biological, or Man-made—ensuring consistent data structures across the distributed system. Instead of a centralized database, each subhub persists its disaster data locally to CSV files in backend/[subhub_name]/data.csv. This distributed state model means each regional node is fully self-contained—it can operate independently even if the hub goes offline, as long as the frontend already has the subhub's address cached from the initial discovery call.
Tech Stack & Tools Used
Frontend & Visualization
- Next.js (App Router): React-based framework providing SSR, file-system routing, and optimized builds.
- LeafletJS: Interactive map library rendering OpenStreetMap tiles for geographic disaster pinning.
- TailwindCSS & Shadcn/UI: Utility-first styling with accessible, pre-built component primitives.
- TypeScript: End-to-end type safety across the frontend application.
Backend & Infrastructure
- Python & Flask: Lightweight API framework running both the central hub and regional subhub instances.
- Flask-CORS & Requests: Cross-origin handling and inter-service HTTP communication for hub-subhub registration.
- CSV Persistence: File-based data layer using Python's
csvmodule for local disaster record storage. - Docker & GitHub Actions: Containerization and CI/CD pipeline for distributed deployment and testing.
Architecture Diagram
📐Distributed Hub-Subhub Architecture
[ ARCHITECTURE OVERVIEW ]
┌─────────────────────────────────────────────────────────────┐
│ FRONTEND LAYER │
│ Next.js (App Router) │
│ │
│ ┌───────────────────┐ ┌───────────────────┐ │
│ │ Dashboard Page │ │ Report Form │ │
│ │ LeafletJS Map │ │ Disaster CRUD │ │
│ │ OpenStreetMap │ │ Type Selector │ │
│ └────────┬──────────┘ └────────┬──────────┘ │
│ │ GET /get_subhubs │ POST /disasters │
│ │ (Discovery) │ PUT /disasters/:id │
│ │ │ DELETE /disasters/:id │
│ ┌────────┴────────────────────────┴──────────┐ │
│ │ TailwindCSS + Shadcn/UI + TypeScript │ │
│ └────────────────────┬───────────────────────┘ │
└────────────────────────┼────────────────────────────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────────────────────┐
│ CENTRAL HUB │ │ REGIONAL SUBHUBS │
│ hub.py │ │ subhub.py (× N instances) │
│ Port: 3000 │ │ Ports: 3001, 3002, ... │
│ │ │ │
│ ┌────────────┐ │ │ ┌────────────────────────────┐ │
│ │ Service │ │ │ │ On Startup: │ │
│ │ Registry │ │ │ │ POST /register → Hub │ │
│ │ │ │ │ │ { ip, port, coords, │ │
│ │ subhubs[] │ │ │ │ radius_km, name } │ │
│ │ = [{...}] │ │ │ └────────────────────────────┘ │
│ └────────────┘ │ │ │
│ │ │ ┌────────────────────────────┐ │
│ ┌────────────┐ │ │ │ DisasterFactory │ │
│ │ Geofencing │ │ │ │ │ │
│ │ Algorithm │ │ │ │ create("Natural") │ │
│ │ │ │ │ │ create("Biological") │ │
│ │ is_point_ │ │ │ │ create("Man-made") │ │
│ │ in_circle │ │ │ └────────────────────────────┘ │
│ │ (lat,lon, │ │ │ │
│ │ radius) │ │ │ ┌────────────────────────────┐ │
│ └────────────┘ │ │ │ CSV Persistence Layer │ │
│ │ │ │ backend/[name]/data.csv │ │
│ GET /get_ │ │ │ Read/Write on CRUD ops │ │
│ subhubs │ │ └────────────────────────────┘ │
└──────────────────┘ └──────────────────────────────────┘
Registration & Discovery Flow:
[Subhub Starts] → POST /register → Hub
│
▼
Hub stores: {
id, name, ip, port,
latitude, longitude, radius_km
}
│
▼
[Frontend Loads] → GET /get_subhubs → Hub
│
▼
Hub returns: [all registered subhubs]
│
▼
[User Clicks Map] → is_point_in_circle(lat, lon)
│
├─ Match found → Route to Subhub API
└─ No match → "No coverage" message
Disaster CRUD Flow:
[User Reports Disaster] → POST /disasters → Subhub
│
├─ DisasterFactory.create(type)
├─ Validate & store in memory
└─ Sync to CSV: backend/[name]/data.csv
│
▼
{ disaster_id, type, severity, coords, timestamp }Local Execution & Testing
This distributed system requires at least three terminal sessions: one for the Central Hub, one for each Regional Subhub, and one for the Next.js frontend. The hub must be started first since subhubs register with it on startup:
# Install Backend Dependenciescd backendpip install -r requirements.txt# Terminal 1: Start the Central Hubpython hub.py --host 0.0.0.0 --port 3000# Terminal 2: Start a Regional Subhub (e.g. Hamilton)python subhub.py --hub-host 127.0.0.1 --hub-port 3000 --ip 127.0.0.1 --host 0.0.0.0 --port 3001 --id 1 --name "Hamilton" --longitude 43.26 --latitude -79.77 --radius_km 50# Terminal 3: Start the Frontendcd frontendnpm installnpm run devTesting Strategy
- ●API Validation: Test subhub registration by calling
/get_subhubson the Hub to verify the registry is populated correctly. - ●Map Verification: Use the LeafletJS map to report a disaster and verify a CSV file is generated in
backend/[subhub_name]/. - ●Container Testing: Run
docker build -t ndc-hub .to containerize services for distributed testing environments.