Phase 2 Implementation Guide
This guide covers the implementation of Phase 2 data enrichment features: elevation data, weather forecasts, and mobile coverage integration.
Overview
Phase 2 adds advanced planning capabilities to Vanroute:
- Road Elevation & Grades - Calculate steepness, warn about challenging sections
- Weather Forecasts & Warnings - 7-day forecasts, severe weather alerts
- Mobile Coverage Maps - Identify coverage gaps by carrier and network type
These features enable the app to provide comprehensive route planning with terrain analysis, weather-based recommendations, and connectivity awareness.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Phase 2 Data Pipeline │
└─────────────────────────────────────────────────────────────┘
Elevation Data → Processing → road_elevations table → Mobile App
(One-time) (GDAL/WCS) (Grade warnings)
Weather Data → Parsing → weather_forecasts → Mobile App
(Every 6 hrs) (BOM) weather_warnings (Weather alerts)
Coverage Data → Import → mobile_coverage → Mobile App
(Annual) (KML) (Coverage gaps)
Database Setup
Run Migrations
Phase 2 requires two database migrations:
Migration 0008: Create Phase 2 Tables
- Creates
road_elevations,weather_forecasts,weather_warnings,mobile_coverage,elevation_tilestables - Adds spatial indexes for efficient queries
- Sets up automatic timestamp triggers
Migration 0009: Create Helper Functions
- Adds spatial query functions for mobile app
- Functions for elevation, weather, and coverage lookups
- Route analysis functions
Apply Migrations
Option A: Supabase SQL Editor (Recommended)
- Go to:
https://app.supabase.com/project/_/sql - Copy contents of
packages/database/supabase/migrations/0008_create_phase2_tables.sql - Paste and run in SQL Editor
- Repeat for
0009_create_phase2_helper_functions.sql
Option B: Supabase CLI
cd packages/database
npx supabase db push
Verify Setup
-- Check tables exist
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN (
'road_elevations',
'weather_forecasts',
'weather_warnings',
'mobile_coverage',
'elevation_tiles'
);
-- Check functions exist
SELECT routine_name FROM information_schema.routines
WHERE routine_schema = 'public'
AND routine_name LIKE '%_near_point%';
1. Elevation Data Implementation
Overview
Road elevation and grade data helps warn drivers about steep sections that may be challenging for caravans.
Data Source: Geoscience Australia Digital Elevation Models (DEMs)
Resolutions Available:
- 5m (LiDAR) - Urban and coastal areas (245,000 sq km coverage)
- 30m (SRTM) - National coverage, recommended for most roads
- 90m (SRTM) - Complete coverage, suitable for major highways
Implementation Options
Option A: WCS (Web Coverage Service) Integration
Query elevation on-demand from Geoscience Australia:
const WCS_URL = 'https://services.ga.gov.au/gis/services/DEM_SRTM_1Second_over_Bathymetry_Topography/MapServer/WCSServer';
async function getElevationAtPoint(lat: number, lon: number): Promise<number> {
const params = new URLSearchParams({
SERVICE: 'WCS',
VERSION: '2.0.1',
REQUEST: 'GetCoverage',
COVERAGEID: 'DEM_SRTM_1Second_over_Bathymetry_Topography',
SUBSET: `Lat(${lat})`,
SUBSET: `Long(${lon})`,
FORMAT: 'image/tiff'
});
const response = await fetch(`${WCS_URL}?${params}`);
// Parse GeoTIFF response to extract elevation value
// ... implementation
}
Pros:
- No storage required
- Always up-to-date
- Easy implementation
Cons:
- Requires internet connection
- Slower for bulk processing
- API rate limits
Option B: Local DEM Tile Processing
Download and process DEM tiles locally using GDAL:
# Install GDAL
brew install gdal # macOS
apt-get install gdal-bin # Ubuntu
# Install Node.js GDAL bindings
npm install gdal-async
import gdal from 'gdal-async';
async function processElevationTile(tilePath: string) {
const dataset = await gdal.openAsync(tilePath);
const band = await dataset.bands.getAsync(1);
// Sample elevation at road segment points
for (const segment of roadSegments) {
const elevation = await band.pixels.getAsync(x, y);
// Calculate grades...
}
}
Download DEM Tiles:
# National DEM tiles (30m SRTM)
wget https://elevation-direct-downloads.s3-ap-southeast-2.amazonaws.com/30m-dem/national_utm_mosaics/nationalz56_ag.zip
# Extract
unzip nationalz56_ag.zip
Pros:
- Fast bulk processing
- No API limits
- Offline capability
Cons:
- Large file sizes (2-5 GB)
- Storage requirements
- More complex implementation
Processing Script Usage
# Process entire state
npm run db:process-elevation -- --state NSW --resolution 30
# Process specific bounding box
npm run db:process-elevation -- --bounds -34,150,-33,151 --resolution 5
# Show help
npm run db:process-elevation -- --help
Grade Calculation
Grades are calculated as:
Grade % = (Rise / Horizontal Distance) × 100
Categories:
- Flat (0-3%): Easy for all vehicles
- Gentle (3-6%): No issues for caravans
- Moderate (6-10%): Caution with heavy loads
- Steep (10-15%): Challenging for caravans
- Very Steep (>15%): Avoid with caravans
Implementation Status
⚠️ Requires Production Implementation
The script framework is complete, but requires:
- WCS integration OR GDAL tile processing
- Elevation sampling along road segments
- Grade calculation between consecutive points
- Database import of calculated values
See packages/database/scripts/process-elevation-data.ts for framework.
2. Weather Data Implementation
Overview
Weather forecasts and warnings help drivers plan routes around adverse conditions.
Data Source: Bureau of Meteorology (BOM)
Coverage:
- 7-day forecasts for 32 major locations
- Severe weather warnings (national coverage)
- Road impact assessments
BOM Data Access
BOM provides data via FTP and RSS feeds (no official REST API):
FTP Access:
ftp://ftp.bom.gov.au/anon/gen/
Available Formats:
- XML forecast products
- JSON observations
- RSS warning feeds
- CAP (Common Alerting Protocol) warnings
Implementation
Forecast Parsing
import { parseStringPromise } from 'xml2js';
async function fetchBOMForecasts(locationCode: string): Promise<Forecast[]> {
// Connect to FTP
const ftpUrl = `ftp://ftp.bom.gov.au/anon/gen/fwo/${locationCode}.xml`;
const response = await fetch(ftpUrl);
const xml = await response.text();
// Parse XML
const parsed = await parseStringPromise(xml);
// Extract forecast data
const forecasts = parsed.product.forecast.map(f => ({
date: f.$.['forecast-period'],
min_temp: parseFloat(f.element.find(e => e.$.type === 'air_temperature_minimum')?.text),
max_temp: parseFloat(f.element.find(e => e.$.type === 'air_temperature_maximum')?.text),
// ... more fields
}));
return forecasts;
}
Warning Parsing
async function fetchBOMWarnings(): Promise<Warning[]> {
// Subscribe to RSS feeds
const rssFeeds = [
'http://www.bom.gov.au/fwo/IDZ00057.warnings_national.xml',
'http://www.bom.gov.au/fwo/IDN00100.weather.xml'
];
const warnings: Warning[] = [];
for (const feed of rssFeeds) {
const response = await fetch(feed);
const xml = await response.text();
const parsed = await parseStringPromise(xml);
// Extract warnings from RSS
for (const item of parsed.rss.channel[0].item) {
warnings.push({
title: item.title[0],
description: item.description[0],
issued: new Date(item.pubDate[0]),
// ... more fields
});
}
}
return warnings;
}
Automation
Weather data should be polled every 6 hours:
GitHub Actions:
# .github/workflows/poll-weather.yml
name: Poll Weather Data
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
workflow_dispatch:
jobs:
poll-weather:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm install
working-directory: ./packages/database
- run: npm run db:poll-weather
working-directory: ./packages/database
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_SERVICE_KEY: ${{ secrets.SUPABASE_SERVICE_KEY }}
Cron:
0 */6 * * * cd /path/to/vanroute/packages/database && npm run db:poll-weather >> /var/log/weather-poll.log 2>&1
Script Usage
# Poll BOM weather
npm run db:poll-weather
Implementation Status
⚠️ Sample Data Only
The script generates sample forecasts. For production:
- Implement FTP connection
- Parse XML forecast products
- Subscribe to RSS warning feeds
- Extract and store forecast data
See packages/database/scripts/poll-bom-weather.ts for framework.
3. Mobile Coverage Implementation
Overview
Mobile coverage maps help identify areas without connectivity, crucial for remote travel safety.
Data Source: ACCC Mobile Infrastructure Report
Coverage:
- Telstra (3G, 4G, 5G)
- Optus (3G, 4G, 5G)
- Vodafone (3G, 4G, 5G)
- TPG (4G, 5G)
Update Frequency: Annual
Data Download
- Visit: https://data.gov.au/data/dataset/accc-mobile-infrastructure-report-data-release
- Download KML files for each carrier and network type
- Example files:
coverage-map-telstra-4g-outdoor-2024.kmlcoverage-map-optus-5g-outdoor-2024.kml- etc.
KML Parsing
Install parser library:
npm install @tmcw/togeojson
Parse KML to GeoJSON:
import { kml } from '@tmcw/togeojson';
import { DOMParser } from 'xmldom';
async function parseKMLCoverage(kmlPath: string): Promise<CoverageArea> {
const kmlText = readFileSync(kmlPath, 'utf-8');
const kmlDom = new DOMParser().parseFromString(kmlText);
const geojson = kml(kmlDom);
// Extract metadata from filename
const fileName = kmlPath.split('/').pop();
const [, carrier, network, type, year] = fileName.match(
/coverage-map-(\w+)-(3g|4g|5g)-(outdoor|indoor)-(\d{4})/
);
return {
carrier: carrier.toLowerCase(),
network_type: network.toLowerCase(),
coverage_type: type.toLowerCase(),
geometry: geojson.features[0].geometry,
source_year: parseInt(year)
};
}
Import Script Usage
# Import directory of KML files
npm run db:import-coverage -- ~/Downloads/coverage/
# Import single file
npm run db:import-coverage -- coverage-telstra-4g-2024.kml
Implementation Status
⚠️ Requires KML Parser
The script framework exists, but requires:
- Install
@tmcw/togeojsonlibrary - Download ACCC KML files
- Parse KML coordinates to GeoJSON
- Import coverage polygons
See packages/database/scripts/import-mobile-coverage.ts for framework.
Mobile App Integration
Service Layer
Create packages/mobile/services/phase2Service.ts:
import { supabase } from './supabase';
// Check steep grades
export async function getSteepGradesNearLocation(
lat: number,
lon: number,
radiusKm: number = 50,
minGrade: number = 8.0
) {
const { data, error } = await supabase.rpc('steep_grades_near_point', {
lat,
lon,
radius_meters: radiusKm * 1000,
min_grade_percent: minGrade
});
if (error) throw error;
return data;
}
// Check weather forecast
export async function getWeatherForecast(
lat: number,
lon: number,
radiusKm: number = 100,
daysAhead: number = 7
) {
const { data, error } = await supabase.rpc('weather_forecast_near_point', {
lat,
lon,
radius_meters: radiusKm * 1000,
days_ahead: daysAhead
});
if (error) throw error;
return data;
}
// Check weather warnings
export async function getWeatherWarnings(
lat: number,
lon: number,
radiusKm: number = 100
) {
const { data, error } = await supabase.rpc('weather_warnings_for_area', {
lat,
lon,
radius_meters: radiusKm * 1000
});
if (error) throw error;
return data;
}
// Check mobile coverage
export async function checkCoverage(
lat: number,
lon: number,
carrier: string = 'telstra',
networkType: string = '4g'
) {
const { data, error } = await supabase.rpc('mobile_coverage_at_point', {
lat,
lon,
carrier,
network_type: networkType
});
if (error) throw error;
return data && data.length > 0;
}
// Get route conditions summary
export async function getRouteConditions(
lat: number,
lon: number,
radiusKm: number = 100
) {
const { data, error } = await supabase.rpc('route_conditions_summary', {
lat,
lon,
radius_meters: radiusKm * 1000
});
if (error) throw error;
return data;
}
UI Components
Grade Warning Component
import { getSteepGradesNearLocation } from '@/services/phase2Service';
export function GradeWarnings({ lat, lon }) {
const [grades, setGrades] = useState([]);
useEffect(() => {
getSteepGradesNearLocation(lat, lon, 50, 8.0).then(setGrades);
}, [lat, lon]);
return (
<View>
{grades.map(grade => (
<Alert
key={grade.id}
severity={grade.grade_category === 'very_steep' ? 'error' : 'warning'}
>
{grade.road_name || grade.road_number}: {grade.max_grade_percent}% grade
</Alert>
))}
</View>
);
}
Weather Warning Component
import { getWeatherWarnings } from '@/services/phase2Service';
export function WeatherWarnings({ lat, lon }) {
const [warnings, setWarnings] = useState([]);
useEffect(() => {
getWeatherWarnings(lat, lon, 100).then(setWarnings);
}, [lat, lon]);
return (
<View>
{warnings.map(warning => (
<Alert
key={warning.id}
severity={warning.severity === 'emergency' ? 'error' : 'warning'}
>
<Text>{warning.title}</Text>
<Text>{warning.description}</Text>
{warning.road_impact_description && (
<Text>Road Impact: {warning.road_impact_description}</Text>
)}
</Alert>
))}
</View>
);
}
Coverage Indicator Component
import { checkCoverage } from '@/services/phase2Service';
export function CoverageIndicator({ lat, lon }) {
const [has4G, setHas4G] = useState(false);
const [has5G, setHas5G] = useState(false);
useEffect(() => {
Promise.all([
checkCoverage(lat, lon, 'telstra', '4g'),
checkCoverage(lat, lon, 'telstra', '5g')
]).then(([g4, g5]) => {
setHas4G(g4);
setHas5G(g5);
});
}, [lat, lon]);
return (
<View>
{has5G && <Badge>5G</Badge>}
{has4G && !has5G && <Badge>4G</Badge>}
{!has4G && !has5G && <Badge color="error">No Coverage</Badge>}
</View>
);
}
Testing
Test Database Functions
-- Test steep grades
SELECT * FROM steep_grades_near_point(-33.8688, 151.2093, 50000, 8.0);
-- Test weather forecast
SELECT * FROM weather_forecast_near_point(-33.8688, 151.2093, 100000, 7);
-- Test weather warnings
SELECT * FROM weather_warnings_for_area(-33.8688, 151.2093, 100000);
-- Test coverage
SELECT * FROM mobile_coverage_at_point(-33.8688, 151.2093, 'telstra', '4g');
-- Test route summary
SELECT * FROM route_conditions_summary(-33.8688, 151.2093, 100000);
Test Scripts
# Weather polling (sample data)
npm run db:poll-weather
# Coverage import (requires downloaded files)
npm run db:import-coverage -- ~/Downloads/coverage/
# Elevation processing (framework only)
npm run db:process-elevation -- --help
Production Checklist
Before deploying Phase 2:
- Run database migrations (0008, 0009)
- Implement elevation processing (WCS or GDAL)
- Implement BOM weather parsing (FTP/RSS)
- Implement mobile coverage import (KML parser)
- Set up weather polling automation (GitHub Actions or cron)
- Test all helper functions with real data
- Create mobile app service layer
- Implement UI components for warnings
- Test with production data
- Set up monitoring and alerting
Performance Considerations
Database Optimization
All tables have appropriate indexes:
- Spatial indexes (GIST) on geometry columns
- B-tree indexes on frequently queried columns
- Partial indexes for filtered queries
Caching Strategy
Recommended cache durations:
- Elevation data: Indefinite (static)
- Weather forecasts: 1 hour
- Weather warnings: 15 minutes
- Mobile coverage: Indefinite (annual updates)
Query Optimization
Use appropriate radius parameters:
- Steep grades: 50km radius
- Weather forecasts: 100km radius
- Mobile coverage: Point-in-polygon (no radius)
Known Limitations
Elevation Data
- 5m LiDAR limited to urban/coastal areas
- Processing is computationally intensive
- Requires GDAL or WCS implementation
Weather Data
- No official BOM REST API
- Requires XML/RSS parsing
- Sample data only in current implementation
Mobile Coverage
- Based on theoretical coverage
- Actual coverage may vary
- Annual update cycle
- Requires KML parser library
Support
- Quick Reference: Phase 2 Quick Reference
- Completion Report: Phase 2 Completion Report
- Data Sources:
Status: ⚠️ Partial Implementation - Framework Complete, Production Data Integration Required Last Updated: October 2025 Next: Phase 3 - User Experience Enhancements