FloodWatch API Documentation¶
Overview¶
FloodWatch uses two main API services for geospatial data: 1. FastAPI Service - High-performance forecast data (GeoJSON) 2. TiPg - Vector tiles from PostGIS tables
FastAPI Service¶
Base URL: http://localhost:9050
Technology: FastAPI with asyncpg for high-performance PostgreSQL queries
Response Format: GeoJSON with optimized JSON serialization (orjson)
Features¶
- Connection pooling (5-20 connections)
- In-memory caching (15-minute TTL for dates)
- Response time logging
- Country-based filtering
- Automatic fallback to latest data
Endpoints¶
1. Service Info¶
Returns service status and available endpoints.
Response:
{
"service": "FloodWatch Forecast API",
"status": "operational",
"endpoints": {
"dates": "/api/fast/merged-forecast/dates/",
"forecast": "/api/fast/merged-forecast/{date}/",
"latest": "/api/fast/merged-forecast/latest/",
"ensemble": "/api/fast/ensemble-control-points"
}
}
2. Health Check¶
Database health check.
Response:
3. Available Forecast Dates¶
Returns list of all available forecast dates with metadata.
Features: - Cached (15 minutes TTL) - Validates dates before returning - Filters out invalid/overflow dates
Response:
{
"dates": [
{
"date": "2025-01-15",
"date_string": "20250115",
"feature_count": 15234,
"file_count": 45,
"created_at": "2025-01-15T12:30:00"
}
],
"count": 120,
"source": "database"
}
Response Headers:
- X-Cache-Hit: true or false
- X-Response-Time: Response time in milliseconds
4. Get Forecast by Date¶
Returns deterministic forecast data for a specific date.
Parameters:
- forecast_date (path): Date in YYYY-MM-DD format
- country (query, optional): Filter features by country name
Example:
# Get forecast for specific date
curl http://localhost:9050/api/fast/merged-forecast/2025-01-15/
# Filter by country
curl http://localhost:9050/api/fast/merged-forecast/2025-01-15/?country=Kenya
Response: GeoJSON FeatureCollection
Response Headers:
- X-Forecast-Date: Actual date of returned data
- X-Feature-Count: Number of features in response
- X-Original-Count: Number of features before filtering
- X-Response-Time: Response time in milliseconds
- X-Fallback: true if fallback to latest was used
- X-Fallback-Date: Date of fallback data (if applicable)
- Cache-Control: public, max-age=3600
5. Get Latest Forecast¶
Returns the most recent deterministic forecast data.
Parameters:
- country (query, optional): Filter features by country name
Example:
# Get latest forecast
curl http://localhost:9050/api/fast/merged-forecast/latest/
# Filter by country
curl http://localhost:9050/api/fast/merged-forecast/latest/?country=Uganda
Response: GeoJSON FeatureCollection
Response Headers:
- X-Forecast-Date: Date of the latest forecast
- X-Feature-Count: Number of features in response
- X-Original-Count: Number of features before filtering
- X-Response-Time: Response time in milliseconds
- Cache-Control: public, max-age=1800
6. Ensemble Control Points¶
Returns ensemble forecast data with control points.
Example:
Response: GeoJSON FeatureCollection with ensemble forecast points
Response Headers:
- X-Feature-Count: Total number of control points
- X-Features-With-Data: Number of points with forecast data
- X-Forecast-Date: Date of the forecast
- X-Response-Time: Response time in milliseconds
- Cache-Control: public, max-age=3600
Performance¶
Typical response times: - Dates endpoint (cached): < 5ms - Dates endpoint (uncached): < 100ms - Forecast endpoints: 20-100ms depending on data size - Country filtering: Minimal overhead (client-side filtering)
Error Handling¶
All errors return standard HTTP status codes:
- 400: Invalid date format
- 404: No data found
- 500: Server error
Error Response:
TiPg - Vector Tiles Service¶
Base URL: http://localhost:8083
Technology: TiPg (OGC Vector Tiles from PostGIS)
Tile Format: Mapbox Vector Tiles (MVT)
Features¶
- OGC-compliant vector tiles
- Optimized for dense river networks
- High-resolution tiles (4096x4096)
- Supports zoom levels 0-22
- Large tile buffer (256px) to prevent clipping
- Up to 50,000 features per tile
Configuration¶
The service is optimized with these settings:
TIPG_TILE_RESOLUTION: 4096 # High resolution
TIPG_TILE_BUFFER: 256 # Prevents clipping at tile edges
TIPG_MAX_FEATURES_PER_TILE: 50000 # For dense networks
TIPG_DEFAULT_MINZOOM: 0 # Available from zoom 0
TIPG_DEFAULT_MAXZOOM: 22 # Available up to zoom 22
TIPG_DB_SCHEMAS: ["pgstac"] # Serves tables from pgstac schema
Endpoints¶
1. API Documentation¶
Interactive OpenAPI documentation (Swagger UI).
Browser Access: http://localhost:8083/docs
2. Service Landing Page¶
OGC API landing page with links to all available resources.
3. Collections List¶
Lists all available vector tile collections (PostGIS tables/views).
Response:
{
"collections": [
{
"id": "river_networks",
"title": "River Networks",
"extent": {
"spatial": {...},
"temporal": {...}
},
"links": [...]
}
]
}
4. Collection Metadata¶
Get detailed metadata for a specific collection.
Example:
5. Vector Tiles¶
Returns vector tiles in Mapbox Vector Tiles (MVT) format.
Parameters:
- collectionId: Table/view name from pgstac schema
- tileMatrixSetId: Usually WebMercatorQuad
- z: Zoom level (0-22)
- x: Tile column
- y: Tile row
Example:
# Get tile at zoom 10, x=512, y=384
curl http://localhost:8083/collections/river_networks/tiles/WebMercatorQuad/10/512/384
6. TileJSON¶
Returns TileJSON metadata for use with mapping libraries.
Example:
Response:
{
"tilejson": "3.0.0",
"name": "river_networks",
"tiles": [
"http://localhost:8083/collections/river_networks/tiles/WebMercatorQuad/{z}/{x}/{y}"
],
"minzoom": 0,
"maxzoom": 22,
"bounds": [-180, -90, 180, 90]
}
7. Health Check¶
Service health check.
Usage in Web Maps¶
MapLibre GL JS Example¶
map.addSource('rivers', {
type: 'vector',
tiles: [
'http://localhost:8083/collections/river_networks/tiles/WebMercatorQuad/{z}/{x}/{y}'
],
minzoom: 0,
maxzoom: 22
});
map.addLayer({
id: 'rivers-layer',
type: 'line',
source: 'rivers',
'source-layer': 'default', // TiPg uses 'default' as layer name
paint: {
'line-color': '#0080ff',
'line-width': 2
}
});
Leaflet Example¶
// Using Leaflet.VectorGrid plugin
const vectorTileOptions = {
rendererFactory: L.canvas.tile,
vectorTileLayerStyles: {
default: {
color: '#0080ff',
weight: 2
}
}
};
const riverLayer = L.vectorGrid.protobuf(
'http://localhost:8083/collections/river_networks/tiles/WebMercatorQuad/{z}/{x}/{y}',
vectorTileOptions
).addTo(map);
OpenLayers Example¶
import MVT from 'ol/format/MVT';
import VectorTileLayer from 'ol/layer/VectorTile';
import VectorTileSource from 'ol/source/VectorTile';
const vectorLayer = new VectorTileLayer({
source: new VectorTileSource({
format: new MVT(),
url: 'http://localhost:8083/collections/river_networks/tiles/WebMercatorQuad/{z}/{x}/{y}'
}),
style: {
'stroke-color': '#0080ff',
'stroke-width': 2
}
});
Performance Considerations¶
- Tile Caching: Consider implementing a CDN or tile cache (MapCache) in front of TiPg for production
- Zoom Level Strategy:
- Use simplified geometries at lower zoom levels
- Full detail at higher zoom levels
- Feature Filtering: Use SQL views to pre-filter data by relevance
- Index Optimization: Ensure PostGIS spatial indexes exist on all geometry columns
Query Available Collections¶
# List all collections
curl http://localhost:8083/collections | jq '.collections[].id'
# Get collection details
curl http://localhost:8083/collections/your_table_name | jq
Migrating from MapServer to TiPg¶
Why TiPg?¶
- Modern Protocol: OGC API compliance instead of legacy WMS/WFS
- Better Performance: Vector tiles are more efficient than WMS/WFS for vector data
- Client-Side Styling: Tiles can be styled dynamically in the browser
- Smaller Payloads: Only download tiles in viewport
- Better Caching: Tile-based caching is more effective
- Zoom-Dependent Rendering: Automatic level-of-detail
Migration Steps¶
- Identify Vector Layers: Determine which MapServer layers serve vector data
- Update Frontend: Replace WMS/WFS layers with vector tile layers
- Style Translation: Convert MapServer styles to client-side styles
- Testing: Verify performance and rendering quality
- Deprecation: Remove MapServer layers once TiPg is stable
Current Status¶
- MapServer (port 8095): Still active for backward compatibility
- MapCache (port 8096): Still active for raster tile caching
- TiPg (port 8083): Ready for vector tile serving
Environment Variables¶
FastAPI Service¶
DB_HOST=postgis # Database host
DB_PORT=5432 # Database port
DB_NAME=floodwatch # Database name
DB_USER=postgres # Database user
DB_PASSWORD=<set-in-env> # Database password
TiPg Service¶
POSTGRES_HOST=postgis # Database host
POSTGRES_PORT=5432 # Database port
POSTGRES_DBNAME=floodwatch # Database name
POSTGRES_USER=postgres # Database user
POSTGRES_PASS=<set-in-env> # Database password
TIPG_DB_SCHEMAS=["pgstac"] # Schemas to serve
TIPG_TILE_RESOLUTION=4096 # Tile resolution
TIPG_TILE_BUFFER=256 # Tile buffer size
TIPG_MAX_FEATURES_PER_TILE=50000 # Max features per tile
Testing the APIs¶
FastAPI Tests¶
# Health check
curl http://localhost:9050/health
# Get available dates
curl http://localhost:9050/api/fast/merged-forecast/dates/
# Get latest forecast
curl http://localhost:9050/api/fast/merged-forecast/latest/
# Get forecast by date
curl http://localhost:9050/api/fast/merged-forecast/2025-01-15/
# Filter by country
curl "http://localhost:9050/api/fast/merged-forecast/latest/?country=Kenya"
# Get ensemble data
curl http://localhost:9050/api/fast/ensemble-control-points
TiPg Tests¶
# Health check
curl http://localhost:8083/healthz
# List collections
curl http://localhost:8083/collections
# Get TileJSON
curl http://localhost:8083/collections/your_table/tiles/WebMercatorQuad/tilejson.json
# Get a tile (returns binary MVT data)
curl http://localhost:8083/collections/your_table/tiles/WebMercatorQuad/5/16/12
Accessing APIs from Jupyter Notebooks¶
Installation¶
First, install required Python packages:
FastAPI - Forecast Data¶
1. Get Available Dates¶
import requests
import pandas as pd
# Base URL
FASTAPI_URL = "http://localhost:9050"
# Get available dates
response = requests.get(f"{FASTAPI_URL}/api/fast/merged-forecast/dates/")
dates_data = response.json()
# Convert to DataFrame
dates_df = pd.DataFrame(dates_data['dates'])
print(f"Total dates available: {dates_data['count']}")
print(dates_df.head())
# Check if cached
print(f"Cache hit: {response.headers.get('X-Cache-Hit')}")
print(f"Response time: {response.headers.get('X-Response-Time')}ms")
2. Load Forecast Data as GeoDataFrame¶
import geopandas as gpd
from shapely.geometry import shape
# Get latest forecast
response = requests.get(f"{FASTAPI_URL}/api/fast/merged-forecast/latest/")
forecast_data = response.json()
# Convert GeoJSON to GeoDataFrame
gdf = gpd.GeoDataFrame.from_features(forecast_data['features'])
print(f"Total features: {len(gdf)}")
print(f"Forecast date: {response.headers.get('X-Forecast-Date')}")
print(gdf.head())
3. Filter by Country¶
# Get forecast for specific country
country = "Kenya"
response = requests.get(
f"{FASTAPI_URL}/api/fast/merged-forecast/latest/",
params={"country": country}
)
kenya_data = response.json()
# Convert to GeoDataFrame
kenya_gdf = gpd.GeoDataFrame.from_features(kenya_data['features'])
print(f"Features in {country}: {len(kenya_gdf)}")
print(f"Original count: {response.headers.get('X-Original-Count')}")
4. Analyze Forecast Data¶
import matplotlib.pyplot as plt
# Get latest forecast
response = requests.get(f"{FASTAPI_URL}/api/fast/merged-forecast/latest/")
gdf = gpd.GeoDataFrame.from_features(response.json()['features'])
# Basic statistics
print("Forecast Statistics:")
print(gdf.describe())
# Plot if you have numeric columns
if 'flood_risk' in gdf.columns:
gdf['flood_risk'].hist(bins=20)
plt.title('Flood Risk Distribution')
plt.xlabel('Risk Level')
plt.ylabel('Frequency')
plt.show()
5. Time Series Analysis¶
import pandas as pd
from datetime import datetime, timedelta
# Get multiple dates
dates_response = requests.get(f"{FASTAPI_URL}/api/fast/merged-forecast/dates/")
dates = dates_response.json()['dates']
# Load data for last 7 days
forecasts = []
for date_info in dates[-7:]:
date_str = date_info['date']
response = requests.get(f"{FASTAPI_URL}/api/fast/merged-forecast/{date_str}/")
gdf = gpd.GeoDataFrame.from_features(response.json()['features'])
gdf['forecast_date'] = date_str
forecasts.append(gdf)
# Combine all forecasts
all_forecasts = gpd.GeoDataFrame(pd.concat(forecasts, ignore_index=True))
print(f"Total records: {len(all_forecasts)}")
6. Interactive Map with Folium¶
import folium
from folium import GeoJson
# Get latest forecast
response = requests.get(f"{FASTAPI_URL}/api/fast/merged-forecast/latest/")
forecast_data = response.json()
# Create map centered on data
gdf = gpd.GeoDataFrame.from_features(forecast_data['features'])
center = [gdf.geometry.centroid.y.mean(), gdf.geometry.centroid.x.mean()]
m = folium.Map(location=center, zoom_start=6)
# Add forecast data
GeoJson(
forecast_data,
name='Forecast Data',
tooltip=folium.GeoJsonTooltip(fields=['properties'], aliases=['Properties'])
).add_to(m)
m.save('forecast_map.html')
print("Map saved to forecast_map.html")
7. Ensemble Control Points¶
# Get ensemble forecast data
response = requests.get(f"{FASTAPI_URL}/api/fast/ensemble-control-points")
ensemble_data = response.json()
# Convert to GeoDataFrame
ensemble_gdf = gpd.GeoDataFrame.from_features(ensemble_data['features'])
print(f"Control points: {response.headers.get('X-Feature-Count')}")
print(f"Points with data: {response.headers.get('X-Features-With-Data')}")
print(ensemble_gdf.head())
TiPg - Vector Tiles¶
1. List Available Collections¶
import requests
TIPG_URL = "http://localhost:8083"
# Get all collections
response = requests.get(f"{TIPG_URL}/collections")
collections = response.json()
# List collection IDs
for collection in collections['collections']:
print(f"Collection: {collection['id']}")
print(f" Title: {collection.get('title', 'N/A')}")
if 'extent' in collection:
spatial = collection['extent'].get('spatial', {})
if 'bbox' in spatial and spatial['bbox']:
print(f" Bounds: {spatial['bbox'][0]}")
print()
2. Get Collection Metadata¶
# Get specific collection details
collection_id = "pgstac.Impact_admin0" # Example collection
response = requests.get(f"{TIPG_URL}/collections/{collection_id}")
metadata = response.json()
print(f"Collection: {metadata['id']}")
print(f"Description: {metadata.get('description', 'N/A')}")
print(f"Extent: {metadata.get('extent', {})}")
3. Get TileJSON Metadata¶
# Get TileJSON for a collection
collection_id = "pgstac.Impact_admin0"
tile_matrix = "WebMercatorQuad"
response = requests.get(
f"{TIPG_URL}/collections/{collection_id}/tiles/{tile_matrix}/tilejson.json"
)
tilejson = response.json()
print(f"Name: {tilejson['name']}")
print(f"Min Zoom: {tilejson['minzoom']}")
print(f"Max Zoom: {tilejson['maxzoom']}")
print(f"Bounds: {tilejson['bounds']}")
print(f"Tile URL: {tilejson['tiles'][0]}")
4. Access Vector Tile Data¶
import requests
from io import BytesIO
# Note: Vector tiles are binary MVT format
# For analysis, you typically use them in web maps
# But you can download and inspect them
collection_id = "pgstac.Impact_rivers"
z, x, y = 5, 16, 12 # Example tile coordinates
tile_url = f"{TIPG_URL}/collections/{collection_id}/tiles/WebMercatorQuad/{z}/{x}/{y}"
response = requests.get(tile_url)
print(f"Tile size: {len(response.content)} bytes")
print(f"Content-Type: {response.headers.get('Content-Type')}")
# Vector tiles are meant for web rendering, not direct analysis
# For data analysis, use FastAPI endpoints instead
5. Query Features Using Items Endpoint¶
Some TiPg deployments support querying features as GeoJSON:
# Check if items endpoint is available
collection_id = "pgstac.Impact_admin0"
try:
# Try to get features as GeoJSON (if supported)
response = requests.get(
f"{TIPG_URL}/collections/{collection_id}/items",
params={
'limit': 100, # Limit number of features
'bbox': '32,-5,42,5' # Bounding box filter (minx,miny,maxx,maxy)
}
)
if response.status_code == 200:
features = response.json()
gdf = gpd.GeoDataFrame.from_features(features['features'])
print(f"Retrieved {len(gdf)} features")
print(gdf.head())
else:
print(f"Items endpoint not available (status {response.status_code})")
print("Use vector tiles for visualization instead")
except Exception as e:
print(f"Error accessing items: {e}")
print("TiPg is optimized for vector tiles, use them in web maps")
6. Display Vector Tiles in Folium¶
import folium
# Create map
m = folium.Map(location=[0, 35], zoom_start=6)
# Add vector tile layer using TileLayer
# Note: Folium doesn't natively support MVT, use raster tiles or MapLibre
collection_id = "pgstac.Impact_admin0"
tile_url = f"{TIPG_URL}/collections/{collection_id}/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}"
# For MVT support, use MapLibre GL JS in HTML
html = f"""
<!DOCTYPE html>
<html>
<head>
<script src='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js'></script>
<link href='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css' rel='stylesheet' />
<style>
body {{ margin: 0; padding: 0; }}
#map {{ position: absolute; top: 0; bottom: 0; width: 100%; }}
</style>
</head>
<body>
<div id='map'></div>
<script>
var map = new maplibregl.Map({{
container: 'map',
style: {{
'version': 8,
'sources': {{
'osm': {{
'type': 'raster',
'tiles': ['https://tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png'],
'tileSize': 256
}},
'vector-tiles': {{
'type': 'vector',
'tiles': ['{tile_url}'],
'minzoom': 0,
'maxzoom': 22
}}
}},
'layers': [
{{
'id': 'osm',
'type': 'raster',
'source': 'osm'
}},
{{
'id': 'vector-layer',
'type': 'line',
'source': 'vector-tiles',
'source-layer': 'default',
'paint': {{
'line-color': '#000000',
'line-width': 2
}}
}}
]
}},
center: [35, 0],
zoom: 6
}});
</script>
</body>
</html>
"""
with open('vector_tiles_map.html', 'w') as f:
f.write(html)
print("Vector tiles map saved to vector_tiles_map.html")
Combined Workflow Example¶
import requests
import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt
# Setup
FASTAPI_URL = "http://localhost:9050"
TIPG_URL = "http://localhost:8083"
# 1. Get latest forecast from FastAPI
print("1. Loading forecast data...")
forecast_response = requests.get(f"{FASTAPI_URL}/api/fast/merged-forecast/latest/")
forecast_gdf = gpd.GeoDataFrame.from_features(forecast_response.json()['features'])
forecast_date = forecast_response.headers.get('X-Forecast-Date')
print(f" Loaded {len(forecast_gdf)} forecast points for {forecast_date}")
# 2. Get available TiPg collections
print("\n2. Checking available vector collections...")
collections_response = requests.get(f"{TIPG_URL}/collections")
collections = [c['id'] for c in collections_response.json()['collections']]
print(f" Available collections: {collections[:5]}...")
# 3. Analyze forecast data
print("\n3. Analyzing forecast data...")
if len(forecast_gdf) > 0:
print(f" Bounds: {forecast_gdf.total_bounds}")
print(f" CRS: {forecast_gdf.crs}")
print(f" Columns: {list(forecast_gdf.columns)}")
# 4. Summary
print(f"\n4. Summary:")
print(f" - Forecast date: {forecast_date}")
print(f" - Number of points: {len(forecast_gdf)}")
print(f" - Vector collections: {len(collections)}")
print(f" - Ready for analysis!")
Best Practices¶
- Use FastAPI for Data Analysis: FastAPI returns GeoJSON which is easy to work with in Python
- Use TiPg for Visualization: Vector tiles are optimized for web map rendering
- Cache Responses: Store frequently-used data locally to reduce API calls
- Handle Errors: Always check response status codes
- Respect Rate Limits: In production, implement request throttling
- Use Async for Multiple Requests: Consider
aiohttpfor concurrent requests
Error Handling Example¶
import requests
def get_forecast_safe(date=None):
"""Safely retrieve forecast data with error handling"""
try:
url = f"{FASTAPI_URL}/api/fast/merged-forecast/latest/"
if date:
url = f"{FASTAPI_URL}/api/fast/merged-forecast/{date}/"
response = requests.get(url, timeout=30)
response.raise_for_status()
data = response.json()
gdf = gpd.GeoDataFrame.from_features(data['features'])
return {
'success': True,
'data': gdf,
'date': response.headers.get('X-Forecast-Date'),
'count': len(gdf)
}
except requests.exceptions.Timeout:
return {'success': False, 'error': 'Request timeout'}
except requests.exceptions.HTTPError as e:
return {'success': False, 'error': f'HTTP error: {e}'}
except Exception as e:
return {'success': False, 'error': f'Unexpected error: {e}'}
# Usage
result = get_forecast_safe()
if result['success']:
print(f"Loaded {result['count']} features for {result['date']}")
else:
print(f"Error: {result['error']}")
Production Considerations¶
FastAPI¶
- Increase connection pool size for high traffic
- Add Redis for distributed caching
- Enable HTTP/2 for better performance
- Add rate limiting
- Implement authentication if needed
TiPg¶
- Add tile caching layer (Varnish, CloudFlare, etc.)
- Set up CDN for global distribution
- Monitor tile generation performance
- Optimize database indexes
- Consider materialized views for complex queries
Support¶
For issues or questions:
- FastAPI service logs: docker logs floodwatch_fastapi
- TiPg service logs: docker logs floodwatch_tipg
- Database logs: docker logs floodwatch_pgstac