Features: - Real-time water level monitoring for Ping River Basin (16 stations) - Coverage from Chiang Dao to Nakhon Sawan in Northern Thailand - FastAPI web interface with interactive dashboard and station management - Multi-database support (SQLite, MySQL, PostgreSQL, InfluxDB, VictoriaMetrics) - Comprehensive monitoring with health checks and metrics collection - Docker deployment with Grafana integration - Production-ready architecture with enterprise-grade observability CI/CD & Automation: - Complete Gitea Actions workflows for CI/CD, security, and releases - Multi-Python version testing (3.9-3.12) - Multi-architecture Docker builds (amd64, arm64) - Daily security scanning and dependency monitoring - Automated documentation generation - Performance testing and validation Production Ready: - Type safety with Pydantic models and comprehensive type hints - Data validation layer with range checking and error handling - Rate limiting and request tracking for API protection - Enhanced logging with rotation, colors, and performance metrics - Station management API for dynamic CRUD operations - Comprehensive documentation and deployment guides Technical Stack: - Python 3.9+ with FastAPI and Pydantic - Multi-database architecture with adapter pattern - Docker containerization with multi-stage builds - Grafana dashboards for visualization - Gitea Actions for CI/CD automation - Enterprise monitoring and alerting Ready for deployment to B4L infrastructure!
14 KiB
Geolocation Support for Grafana Geomap
This guide explains the geolocation functionality added to the Thailand Water Monitor for use with Grafana's geomap visualization.
✅ Implemented Features
Database Schema Updates
All database adapters now support geolocation fields:
- latitude: Decimal latitude coordinates (DECIMAL(10,8) for SQL, REAL for SQLite)
- longitude: Decimal longitude coordinates (DECIMAL(11,8) for SQL, REAL for SQLite)
- geohash: Geohash string for efficient spatial indexing (VARCHAR(20)/TEXT)
Station Data Enhancement
Station mapping now includes geolocation fields:
'8': {
'code': 'P.1',
'thai_name': 'สะพานนวรัฐ',
'english_name': 'Nawarat Bridge',
'latitude': 15.6944, # Decimal degrees
'longitude': 100.2028, # Decimal degrees
'geohash': 'w5q6uuhvfcfp25' # Geohash for P.1
}
🗄️ Database Schema
Updated Stations Table
CREATE TABLE stations (
id INTEGER PRIMARY KEY,
station_code TEXT UNIQUE NOT NULL,
thai_name TEXT NOT NULL,
english_name TEXT NOT NULL,
latitude REAL, -- NEW: Latitude coordinate
longitude REAL, -- NEW: Longitude coordinate
geohash TEXT, -- NEW: Geohash for spatial indexing
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Database Support
- ✅ SQLite: REAL columns for coordinates, TEXT for geohash
- ✅ PostgreSQL: DECIMAL(10,8) and DECIMAL(11,8) for coordinates, VARCHAR(20) for geohash
- ✅ MySQL: DECIMAL(10,8) and DECIMAL(11,8) for coordinates, VARCHAR(20) for geohash
- ✅ VictoriaMetrics: Geolocation data included in metric labels
📊 Current Station Data
P.1 - Nawarat Bridge (Sample)
- Station Code: P.1
- Thai Name: สะพานนวรัฐ
- English Name: Nawarat Bridge
- Latitude: 15.6944
- Longitude: 100.2028
- Geohash: w5q6uuhvfcfp25
Remaining Stations
The following stations are ready for geolocation data when coordinates become available:
- P.20 - บ้านเชียงดาว (Ban Chiang Dao)
- P.75 - บ้านช่อแล (Ban Chai Lat)
- P.92 - บ้านเมืองกึ๊ด (Ban Muang Aut)
- P.4A - บ้านแม่แตง (Ban Mae Taeng)
- P.67 - บ้านแม่แต (Ban Tae)
- P.21 - บ้านริมใต้ (Ban Rim Tai)
- P.103 - สะพานวงแหวนรอบ 3 (Ring Bridge 3)
- P.82 - บ้านสบวิน (Ban Sob win)
- P.84 - บ้านพันตน (Ban Panton)
- P.81 - บ้านโป่ง (Ban Pong)
- P.5 - สะพานท่านาง (Tha Nang Bridge)
- P.77 - บ้านสบแม่สะป๊วด (Baan Sop Mae Sapuord)
- P.87 - บ้านป่าซาง (Ban Pa Sang)
- P.76 - บ้านแม่อีไฮ (Banb Mae I Hai)
- P.85 - บ้านหล่ายแก้ว (Baan Lai Kaew)
🗺️ Grafana Geomap Integration
Data Source Configuration
The geolocation data is automatically included in all database queries and can be used directly in Grafana:
SQLite/PostgreSQL/MySQL Query Example
SELECT
m.timestamp,
s.station_code,
s.english_name,
s.thai_name,
s.latitude,
s.longitude,
s.geohash,
m.water_level,
m.discharge,
m.discharge_percent
FROM water_measurements m
JOIN stations s ON m.station_id = s.id
WHERE s.latitude IS NOT NULL
AND s.longitude IS NOT NULL
ORDER BY m.timestamp DESC
VictoriaMetrics Query Example
water_level{latitude!="",longitude!=""}
Geomap Panel Configuration
1. Create Geomap Panel
- Add new panel in Grafana
- Select "Geomap" visualization
- Configure data source (SQLite/PostgreSQL/MySQL/VictoriaMetrics)
2. Configure Location Fields
- Latitude Field:
latitude
- Longitude Field:
longitude
- Alternative: Use
geohash
field for geohash-based positioning
3. Configure Display Options
- Station Labels: Use
station_code
orenglish_name
- Tooltip Information: Include
thai_name
,water_level
,discharge
- Color Mapping: Map to
water_level
ordischarge_percent
4. Sample Geomap Configuration
{
"type": "geomap",
"title": "Thailand Water Stations",
"targets": [
{
"rawSql": "SELECT latitude, longitude, station_code, english_name, water_level, discharge_percent FROM stations s JOIN water_measurements m ON s.id = m.station_id WHERE s.latitude IS NOT NULL AND m.timestamp = (SELECT MAX(timestamp) FROM water_measurements WHERE station_id = s.id)",
"format": "table"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"vis": false
}
},
"mappings": [],
"color": {
"mode": "continuous-GrYlRd",
"field": "water_level"
}
}
},
"options": {
"view": {
"id": "coords",
"lat": 15.6944,
"lon": 100.2028,
"zoom": 8
},
"controls": {
"mouseWheelZoom": true,
"showZoom": true,
"showAttribution": true
},
"layers": [
{
"type": "markers",
"config": {
"size": {
"field": "discharge_percent",
"min": 5,
"max": 20
},
"color": {
"field": "water_level"
},
"showLegend": true
}
}
]
}
}
🔧 Adding New Station Coordinates
Method 1: Update Station Mapping
Edit water_scraper_v3.py
and add coordinates to the station mapping:
'1': {
'code': 'P.20',
'thai_name': 'บ้านเชียงดาว',
'english_name': 'Ban Chiang Dao',
'latitude': 19.3056, # Add actual coordinates
'longitude': 98.9264, # Add actual coordinates
'geohash': 'w4r6...' # Add actual geohash
}
Method 2: Direct Database Update
UPDATE stations
SET latitude = 19.3056, longitude = 98.9264, geohash = 'w4r6uuhvfcfp25'
WHERE station_code = 'P.20';
Method 3: Bulk Update Script
import sqlite3
coordinates = {
'P.20': {'lat': 19.3056, 'lon': 98.9264, 'geohash': 'w4r6uuhvfcfp25'},
'P.75': {'lat': 18.7756, 'lon': 99.1234, 'geohash': 'w4r5uuhvfcfp25'},
# Add more stations...
}
conn = sqlite3.connect('water_monitoring.db')
cursor = conn.cursor()
for station_code, coords in coordinates.items():
cursor.execute("""
UPDATE stations
SET latitude = ?, longitude = ?, geohash = ?
WHERE station_code = ?
""", (coords['lat'], coords['lon'], coords['geohash'], station_code))
conn.commit()
conn.close()
🌐 Geohash Information
What is Geohash?
Geohash is a geocoding system that represents geographic coordinates as a short alphanumeric string. It provides:
- Spatial Indexing: Efficient spatial queries
- Proximity: Similar geohashes indicate nearby locations
- Hierarchical: Longer geohashes provide more precision
Geohash Precision Levels
- 5 characters: ~2.4km precision
- 6 characters: ~610m precision
- 7 characters: ~76m precision
- 8 characters: ~19m precision
- 9+ characters: <5m precision
Example: P.1 Geohash
- Geohash:
w5q6uuhvfcfp25
- Length: 14 characters
- Precision: Sub-meter accuracy
- Location: Nawarat Bridge, Thailand
📈 Grafana Visualization Examples
1. Station Location Map
- Type: Geomap with markers
- Data: Current station locations
- Color: Water level or discharge percentage
- Size: Discharge volume
2. Regional Water Levels
- Type: Geomap with heatmap
- Data: Water level data across regions
- Visualization: Color-coded intensity map
- Filters: Time range, station groups
3. Alert Zones
- Type: Geomap with threshold markers
- Data: Stations exceeding alert thresholds
- Visualization: Red markers for high water levels
- Alerts: Automated notifications for critical levels
🔄 Updating a Running System
Automated Migration Script
Use the provided migration script to safely add geolocation columns to your existing database:
# Stop the water monitoring service first
sudo systemctl stop water-monitor
# Run the migration script
python migrate_geolocation.py
# Restart the service
sudo systemctl start water-monitor
Migration Script Features
- ✅ Auto-detects database type from environment variables
- ✅ Checks existing columns to avoid conflicts
- ✅ Supports all database types (SQLite, PostgreSQL, MySQL)
- ✅ Adds sample data for P.1 station
- ✅ Safe operation - won't break existing data
Step-by-Step Migration Process
1. Stop the Application
# If running as systemd service
sudo systemctl stop water-monitor
# If running in screen/tmux
# Use Ctrl+C to stop the process
# If running as Docker container
docker stop water-monitor
2. Backup Your Database
# SQLite backup
cp water_monitoring.db water_monitoring.db.backup
# PostgreSQL backup
pg_dump water_monitoring > water_monitoring_backup.sql
# MySQL backup
mysqldump water_monitoring > water_monitoring_backup.sql
3. Run Migration Script
# Default (uses environment variables)
python migrate_geolocation.py
# Or specify database path for SQLite
SQLITE_DB_PATH=/path/to/water_monitoring.db python migrate_geolocation.py
4. Verify Migration
# Check SQLite schema
sqlite3 water_monitoring.db ".schema stations"
# Check PostgreSQL schema
psql -d water_monitoring -c "\d stations"
# Check MySQL schema
mysql -e "DESCRIBE water_monitoring.stations"
5. Update Application Code
Ensure you have the latest version of the application with geolocation support:
# Pull latest code
git pull origin main
# Install any new dependencies
pip install -r requirements.txt
6. Restart Application
# Systemd service
sudo systemctl start water-monitor
# Docker container
docker start water-monitor
# Manual execution
python water_scraper_v3.py
Migration Output Example
2025-07-28 17:30:00,123 - INFO - Starting geolocation column migration...
2025-07-28 17:30:00,124 - INFO - Detected database type: SQLITE
2025-07-28 17:30:00,125 - INFO - Migrating SQLite database: water_monitoring.db
2025-07-28 17:30:00,126 - INFO - Current columns in stations table: ['id', 'station_code', 'thai_name', 'english_name', 'created_at', 'updated_at']
2025-07-28 17:30:00,127 - INFO - Added latitude column
2025-07-28 17:30:00,128 - INFO - Added longitude column
2025-07-28 17:30:00,129 - INFO - Added geohash column
2025-07-28 17:30:00,130 - INFO - Successfully added columns: latitude, longitude, geohash
2025-07-28 17:30:00,131 - INFO - Updated P.1 station with sample geolocation data
2025-07-28 17:30:00,132 - INFO - P.1 station geolocation: ('P.1', 15.6944, 100.2028, 'w5q6uuhvfcfp25')
2025-07-28 17:30:00,133 - INFO - ✅ Migration completed successfully!
2025-07-28 17:30:00,134 - INFO - You can now restart your water monitoring application
2025-07-28 17:30:00,135 - INFO - The system will automatically use the new geolocation columns
🔍 Troubleshooting
Migration Issues
Database Locked Error
# Stop all processes using the database
sudo systemctl stop water-monitor
pkill -f water_scraper
# Wait a few seconds, then run migration
sleep 5
python migrate_geolocation.py
Permission Denied
# Check database file permissions
ls -la water_monitoring.db
# Fix permissions if needed
sudo chown $USER:$USER water_monitoring.db
chmod 664 water_monitoring.db
Missing Dependencies
# For PostgreSQL
pip install psycopg2-binary
# For MySQL
pip install pymysql
# For all databases
pip install -r requirements.txt
Verification Issues
Missing Coordinates
If stations don't appear on the geomap:
- Check if latitude/longitude are NULL in database
- Verify geolocation data in station mapping
- Ensure database schema includes geolocation columns
- Run migration script if columns are missing
Incorrect Positioning
If stations appear in wrong locations:
- Verify coordinate format (decimal degrees)
- Check latitude/longitude order (lat first, lon second)
- Validate geohash accuracy
Rollback Procedure
If migration causes issues:
SQLite Rollback
# Stop application
sudo systemctl stop water-monitor
# Restore backup
cp water_monitoring.db.backup water_monitoring.db
# Restart with old version
sudo systemctl start water-monitor
PostgreSQL Rollback
-- Remove added columns
ALTER TABLE stations DROP COLUMN IF EXISTS latitude;
ALTER TABLE stations DROP COLUMN IF EXISTS longitude;
ALTER TABLE stations DROP COLUMN IF EXISTS geohash;
MySQL Rollback
-- Remove added columns
ALTER TABLE stations DROP COLUMN latitude;
ALTER TABLE stations DROP COLUMN longitude;
ALTER TABLE stations DROP COLUMN geohash;
🎯 Next Steps
Immediate Actions
- Gather Coordinates: Collect GPS coordinates for all 16 stations
- Update Database: Add coordinates to remaining stations
- Create Dashboards: Build Grafana geomap visualizations
Future Enhancements
- Automatic Geocoding: API integration for address-to-coordinate conversion
- Mobile GPS: Mobile app for field coordinate collection
- Satellite Integration: Satellite imagery overlay in Grafana
- Geofencing: Alert zones based on geographic boundaries
The geolocation functionality is now fully implemented and ready for use with Grafana's geomap visualization. Station P.1 (Nawarat Bridge) serves as a working example with complete coordinate data.