Skip to main content

Fuel Economy Integration

Overview

VanRoute's fuel economy integration provides intelligent route optimization that considers real-world factors affecting fuel consumption when towing caravans. By analyzing terrain gradients, wind conditions, and vehicle specifications, the system can recommend more economical routes and show users actual dollar savings from choosing fuel-efficient paths.

Key Features

  • Gradient-aware routing - Accounts for elevation changes and hill climbing
  • Wind resistance modeling - Factors in headwinds, tailwinds, and crosswinds
  • Real-time fuel cost calculations - Shows dollar savings between route options
  • Vehicle profile customization - Supports different towing configurations
  • Weather-integrated planning - Uses forecast wind data for optimal departure times

Research Findings

Gradient Impact on Fuel Economy

Based on real-world data:

  • Hills reduce fuel economy by 10-20% when towing caravans
  • Example: 26 mpg flat terrain vs 22 mpg hilly terrain (15% reduction)
  • Drivers need to downshift on inclines, significantly increasing fuel consumption
  • Longer flat routes can be more economical than shorter hilly routes

Wind Resistance Impact

Based on empirical studies:

  • Every 10 mph of headwind reduces fuel efficiency by 13%
  • Real-world example: 18.9 mpg into headwind vs 21.3 mpg no wind
  • Another example: 29.0 mpg with headwind vs 33.1 mpg no wind
  • Tailwinds can nearly double fuel economy compared to headwinds
  • Wind speeds over 30 mph are unsafe for towing
  • Speed is critical: drag at 30 km/h is 1/4 of drag at 60 km/h

Mathematical Model

Base Fuel Consumption Formula

interface VehicleProfile {
baseConsumption: number // L/100km on flat terrain
weight: number // Total weight (kg): vehicle + caravan + cargo
frontalArea: number // m² (typically 8-12 m² for caravan combos)
dragCoefficient: number // Cd (typically 0.6-0.9 for caravans)
engineEfficiency: number // 0-1 scale (diesel typically 0.4, petrol 0.3)
}

interface SegmentConditions {
distance: number // km
averageGrade: number // % grade (positive = uphill, negative = downhill)
windSpeed: number // m/s
windDirection: number // degrees (0-360)
travelDirection: number // degrees (0-360)
}

interface FuelPrice {
unleaded: number // $/L
diesel: number // $/L
lpg: number // $/L
}

Gradient Penalty Calculation

function calculateGradientPenalty(grade: number): number {
// Grade is in percentage (e.g., 5 = 5% grade)
const baseGrade = Math.abs(grade)

if (baseGrade === 0) return 0
if (baseGrade <= 2) return 0.05 // 5% increase for gentle hills
if (baseGrade <= 4) return 0.10 // 10% increase for moderate hills
if (baseGrade <= 6) return 0.15 // 15% increase for steep hills
return 0.20 // 20% increase for very steep hills

// Note: Downhill grades provide minimal savings due to engine braking
// and safety considerations when towing
}

Wind Resistance Factor

function calculateWindFactor(
windSpeed: number, // m/s
windDirection: number, // degrees
travelDirection: number // degrees
): number {
// Calculate relative wind angle
const angleDiff = Math.abs(windDirection - travelDirection)
const normalizedAngle = Math.min(angleDiff, 360 - angleDiff)

// Calculate headwind component (positive) or tailwind (negative)
const headwindComponent = windSpeed * Math.cos(normalizedAngle * Math.PI / 180)

// Convert m/s to km/h
const windKmh = headwindComponent * 3.6

// Apply 13% increase per 10 km/h headwind (or decrease for tailwind)
const windFactor = (windKmh / 10) * 0.13

// Safety check: warn if crosswind too strong
const crosswindComponent = Math.abs(windSpeed * Math.sin(normalizedAngle * Math.PI / 180))
if (crosswindComponent * 3.6 > 30) {
// Return warning flag for unsafe towing conditions
return { factor: windFactor, unsafe: true }
}

return { factor: windFactor, unsafe: false }
}

Fuel Consumption Calculation

function calculateSegmentFuelConsumption(
vehicle: VehicleProfile,
segment: SegmentConditions
): number {
// Base consumption for distance
let consumption = vehicle.baseConsumption * (segment.distance / 100)

// Apply gradient penalty (only for uphills when towing)
if (segment.averageGrade > 0) {
const gradientPenalty = calculateGradientPenalty(segment.averageGrade)
consumption *= (1 + gradientPenalty)
}

// Apply wind resistance factor
const windResult = calculateWindFactor(
segment.windSpeed,
segment.windDirection,
segment.travelDirection
)
consumption *= (1 + windResult.factor)

return {
fuelLiters: consumption,
unsafe: windResult.unsafe
}
}

Route-Level Fuel Calculation

interface Route {
segments: SegmentConditions[]
totalDistance: number
}

function calculateRouteFuelConsumption(
vehicle: VehicleProfile,
route: Route
): RouteConsumption {
let totalFuel = 0
let hasUnsafeConditions = false
const segmentDetails: SegmentFuelDetails[] = []

for (const segment of route.segments) {
const result = calculateSegmentFuelConsumption(vehicle, segment)
totalFuel += result.fuelLiters

if (result.unsafe) {
hasUnsafeConditions = true
}

segmentDetails.push({
distance: segment.distance,
fuel: result.fuelLiters,
grade: segment.averageGrade,
windSpeed: segment.windSpeed,
unsafe: result.unsafe
})
}

return {
totalFuelLiters: totalFuel,
averageConsumption: (totalFuel / route.totalDistance) * 100,
hasUnsafeConditions,
segments: segmentDetails
}
}

Dollar-Savings Calculation

interface RouteComparison {
route: Route
fuelConsumption: RouteConsumption
costDollars: number
}

function compareRoutes(
vehicle: VehicleProfile,
routes: Route[],
fuelPrice: FuelPrice,
fuelType: 'unleaded' | 'diesel' | 'lpg'
): RouteComparison[] {
const pricePerLiter = fuelPrice[fuelType]

const comparisons = routes.map(route => {
const consumption = calculateRouteFuelConsumption(vehicle, route)
const cost = consumption.totalFuelLiters * pricePerLiter

return {
route,
fuelConsumption: consumption,
costDollars: cost,
averageConsumptionPer100km: consumption.averageConsumption,
totalFuelLiters: consumption.totalFuelLiters
}
})

// Sort by cost (ascending)
comparisons.sort((a, b) => a.costDollars - b.costDollars)

// Calculate savings relative to most expensive route
const mostExpensive = comparisons[comparisons.length - 1]

return comparisons.map(comp => ({
...comp,
savingsVsMostExpensive: mostExpensive.costDollars - comp.costDollars,
savingsPercentage: ((mostExpensive.costDollars - comp.costDollars) / mostExpensive.costDollars) * 100
}))
}

Weather Data Integration

Open-Meteo API

We use Open-Meteo for wind forecast data:

Advantages:

  • Free and open-source
  • No API key required
  • Includes BOM ACCESS-G model for Australia
  • Hourly wind forecasts up to 7 days
  • JSON format, easy integration

API Endpoint:

https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&hourly=wind_speed_10m,wind_direction_10m

Sample Response:

{
"latitude": -37.8,
"longitude": 144.9,
"hourly": {
"time": ["2025-10-17T00:00", "2025-10-17T01:00", ...],
"wind_speed_10m": [5.2, 4.8, 4.5, ...],
"wind_direction_10m": [220, 215, 210, ...]
}
}

Weather Service Implementation

interface WindForecast {
timestamp: Date
windSpeed: number // m/s
windDirection: number // degrees
}

class WeatherService {
private readonly API_BASE = 'https://api.open-meteo.com/v1/forecast'

async getWindForecast(
latitude: number,
longitude: number,
startTime: Date,
endTime: Date
): Promise<WindForecast[]> {
const params = new URLSearchParams({
latitude: latitude.toString(),
longitude: longitude.toString(),
hourly: 'wind_speed_10m,wind_direction_10m',
start_date: startTime.toISOString().split('T')[0],
end_date: endTime.toISOString().split('T')[0],
timezone: 'Australia/Sydney'
})

const response = await fetch(`${this.API_BASE}?${params}`)
const data = await response.json()

return data.hourly.time.map((time: string, index: number) => ({
timestamp: new Date(time),
windSpeed: data.hourly.wind_speed_10m[index],
windDirection: data.hourly.wind_direction_10m[index]
}))
}

async getRouteWindConditions(
route: Route,
departureTime: Date
): Promise<SegmentConditions[]> {
const segments: SegmentConditions[] = []
let currentTime = departureTime

for (const waypoint of route.waypoints) {
// Estimate time to reach this waypoint (assuming 80 km/h average)
const hoursToWaypoint = waypoint.distanceFromStart / 80
const waypointTime = new Date(
currentTime.getTime() + hoursToWaypoint * 3600000
)

// Get wind forecast at waypoint location and time
const windData = await this.getWindForecast(
waypoint.latitude,
waypoint.longitude,
waypointTime,
waypointTime
)

segments.push({
distance: waypoint.segmentDistance,
averageGrade: waypoint.grade,
windSpeed: windData[0].windSpeed,
windDirection: windData[0].windDirection,
travelDirection: waypoint.bearing
})
}

return segments
}
}

Fuel Price Data

Data Sources

  1. Fuel Price Australia API (Commercial)

    • Real-time fuel prices by location
    • Updated daily
    • Covers major fuel brands
  2. State Government APIs

    • NSW FuelCheck API
    • QLD Fair Trading API
    • VIC FuelWatch
  3. Web Scraping (Backup)

    • MotorMouth.com.au
    • PetrolSpy

Database Schema

-- Fuel prices table
CREATE TABLE fuel_prices (
id BIGSERIAL PRIMARY KEY,
station_id BIGINT REFERENCES fuel_stations(id),
fuel_type VARCHAR(20) NOT NULL, -- 'unleaded', 'diesel', 'e10', 'premium', 'lpg'
price_cents INTEGER NOT NULL, -- Price in cents per liter
recorded_at TIMESTAMPTZ NOT NULL,
source VARCHAR(50) NOT NULL, -- 'fuelcheck', 'user_report', 'scrape'
created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_fuel_prices_station_type_time
ON fuel_prices(station_id, fuel_type, recorded_at DESC);

-- Average fuel prices by region (for route calculation)
CREATE TABLE regional_fuel_averages (
id BIGSERIAL PRIMARY KEY,
region_name VARCHAR(100) NOT NULL,
state VARCHAR(3) NOT NULL,
fuel_type VARCHAR(20) NOT NULL,
average_price_cents INTEGER NOT NULL,
sample_size INTEGER NOT NULL,
calculated_at TIMESTAMPTZ DEFAULT NOW(),
valid_until TIMESTAMPTZ NOT NULL
);

-- Helper function to get current fuel price
CREATE OR REPLACE FUNCTION get_fuel_price(
p_latitude DOUBLE PRECISION,
p_longitude DOUBLE PRECISION,
p_fuel_type VARCHAR(20)
) RETURNS INTEGER AS $$
DECLARE
v_price INTEGER;
BEGIN
-- Try to find recent price within 50km
SELECT price_cents INTO v_price
FROM fuel_prices fp
JOIN fuel_stations fs ON fp.station_id = fs.id
WHERE fp.fuel_type = p_fuel_type
AND fp.recorded_at > NOW() - INTERVAL '7 days'
AND ST_DWithin(
fs.geom::geography,
ST_SetSRID(ST_MakePoint(p_longitude, p_latitude), 4326)::geography,
50000 -- 50km radius
)
ORDER BY
ST_Distance(
fs.geom::geography,
ST_SetSRID(ST_MakePoint(p_longitude, p_latitude), 4326)::geography
) ASC,
fp.recorded_at DESC
LIMIT 1;

-- Fall back to state average if no local price found
IF v_price IS NULL THEN
SELECT average_price_cents INTO v_price
FROM regional_fuel_averages
WHERE fuel_type = p_fuel_type
AND valid_until > NOW()
ORDER BY calculated_at DESC
LIMIT 1;
END IF;

RETURN v_price;
END;
$$ LANGUAGE plpgsql;

User Interface Design

Route Comparison View

┌─────────────────────────────────────────────────────────┐
│ Route Options: Sydney to Melbourne │
├─────────────────────────────────────────────────────────┤
│ │
│ 🔵 RECOMMENDED: Most Economical │
│ Via Hume Highway │
│ 📍 877 km ⏱️ 9h 15m ⛽ 89.2 L │
│ 💰 $164.30 (Diesel @ $1.84/L) │
│ 💚 SAVE $23.40 compared to Princes Highway │
│ 🏔️ Moderate hills (avg 2.1% grade) │
│ 🌤️ Light headwind (12 km/h) - Good conditions │
│ │
├─────────────────────────────────────────────────────────┤
│ │
│ ⚪ ALTERNATE: Scenic Route │
│ Via Princes Highway (Coastal) │
│ 📍 1,042 km ⏱️ 11h 30m ⛽ 102.1 L │
│ 💰 $187.70 (Diesel @ $1.84/L) │
│ 🏔️ Steep hills in Gippsland (avg 4.3% grade) │
│ 🌤️ Strong coastal winds expected (20-25 km/h) │
│ │
├─────────────────────────────────────────────────────────┤
│ │
│ ⚠️ ALTERNATE: Fastest Route │
│ Via Alpine Highway │
│ 📍 795 km ⏱️ 8h 45m ⛽ 98.4 L │
│ 💰 $181.00 (Diesel @ $1.84/L) │
│ ⛰️ Very steep grades (avg 6.2% grade) │
│ ⚠️ NOT RECOMMENDED: Excessive fuel consumption │
│ │
└─────────────────────────────────────────────────────────┘

Trip Settings Panel

┌─────────────────────────────────────────────────────────┐
│ Vehicle & Fuel Settings │
├─────────────────────────────────────────────────────────┤
│ │
│ Vehicle Profile: │
│ [v] Toyota LandCruiser 200 + 24ft Caravan │
│ │
│ Base Fuel Consumption: │
│ [___18___] L/100km (unladen: 12 L/100km) │
│ │
│ Fuel Type: │
│ (•) Diesel ( ) Unleaded ( ) Premium ( ) LPG │
│ │
│ Current Fuel Price: │
│ $[__1.84__]/L [Auto-detect from location] │
│ │
│ Route Preferences: │
│ [x] Prioritize fuel economy │
│ [x] Show dollar savings │
│ [x] Factor in wind conditions │
│ [x] Avoid steep grades when possible │
│ [ ] Fastest route regardless of cost │
│ │
└─────────────────────────────────────────────────────────┘

Real-Time Trip Monitor

┌─────────────────────────────────────────────────────────┐
│ Trip Progress: Sydney → Melbourne │
├─────────────────────────────────────────────────────────┤
│ │
│ Progress: 342 km / 877 km (39%) │
│ [████████████░░░░░░░░░░░░░░░░░░] │
│ │
│ Fuel Consumption: │
│ Actual: 18.4 L/100km │
│ Expected: 17.8 L/100km │
│ Variance: +3.4% (within normal range) │
│ │
│ Used: 63.7 L Remaining: ~25.5 L needed │
│ 💰 Spent: $117.20 Projected total: $164.90 │
│ │
│ Next fuel stop in 87 km │
│ 🌤️ Headwind increasing - expect 5% higher consumption │
│ │
└─────────────────────────────────────────────────────────┘

Integration with Routing System

Modified Routing Service

interface RoutingOptions {
origin: Coordinate
destination: Coordinate
vehicleProfile: VehicleProfile
fuelType: 'unleaded' | 'diesel' | 'lpg'
departureTime: Date
preferences: {
optimizeForFuel: boolean
avoidSteepGrades: boolean
maxGradePercent?: number
considerWind: boolean
maxCrosswing?: number // km/h
}
}

class EnhancedRoutingService {
private weatherService: WeatherService
private fuelPriceService: FuelPriceService

async calculateRoutes(options: RoutingOptions): Promise<RouteComparison[]> {
// 1. Get base routes from graph (considering restrictions)
const baseRoutes = await this.getBaseRoutes(
options.origin,
options.destination,
options.vehicleProfile.height,
options.vehicleProfile.weight
)

// 2. Enrich with elevation data
const routesWithElevation = await this.addElevationData(baseRoutes)

// 3. Get wind forecasts if enabled
let routesWithWeather = routesWithElevation
if (options.preferences.considerWind) {
routesWithWeather = await Promise.all(
routesWithElevation.map(async route => {
const windConditions = await this.weatherService.getRouteWindConditions(
route,
options.departureTime
)
return { ...route, windConditions }
})
)
}

// 4. Calculate fuel consumption and costs
const avgFuelPrice = await this.fuelPriceService.getAveragePrice(
options.origin,
options.fuelType
)

const routeComparisons = compareRoutes(
options.vehicleProfile,
routesWithWeather,
{ [options.fuelType]: avgFuelPrice / 100 },
options.fuelType
)

// 5. Apply preferences and sort
if (options.preferences.optimizeForFuel) {
routeComparisons.sort((a, b) => a.costDollars - b.costDollars)
}

// 6. Filter unsafe routes
return routeComparisons.filter(route => {
if (route.fuelConsumption.hasUnsafeConditions) return false
if (options.preferences.maxGradePercent) {
const maxGrade = Math.max(...route.fuelConsumption.segments.map(s => s.grade))
if (maxGrade > options.preferences.maxGradePercent) return false
}
return true
})
}
}

Implementation Roadmap

Phase 1: Foundation (2-3 weeks)

Week 1:

  • Design and implement fuel economy calculation service
  • Add gradient penalty calculations using existing elevation data
  • Create vehicle profile database schema and API
  • Basic unit tests for consumption calculations

Week 2:

  • Integrate Open-Meteo weather API
  • Implement wind resistance factor calculations
  • Add weather data caching layer
  • Test with real route scenarios

Week 3:

  • Implement fuel price database schema
  • Create fuel price ingestion service (starting with state APIs)
  • Build regional fuel price averaging system
  • Add price update scheduling

Phase 2: Route Integration (2 weeks)

Week 1:

  • Extend routing service with fuel economy calculations
  • Add route comparison and ranking logic
  • Implement dollar-savings calculations
  • Add unsafe condition detection

Week 2:

  • Create route comparison API endpoints
  • Add vehicle profile CRUD endpoints
  • Implement trip monitoring endpoints
  • Performance optimization and caching

Phase 3: User Interface (2-3 weeks)

Week 1:

  • Design and implement vehicle profile setup UI
  • Build route comparison cards with savings display
  • Add fuel settings panel
  • Implement route detail views with gradient/wind visualizations

Week 2:

  • Create real-time trip monitoring UI
  • Add fuel consumption tracking
  • Implement savings progress display
  • Mobile responsive design refinement

Week 3:

  • User testing and feedback
  • UI/UX refinements
  • Accessibility improvements
  • Performance optimization

Phase 4: Advanced Features (2-3 weeks)

Week 1:

  • Departure time optimization (find best wind conditions)
  • Multi-day trip fuel planning
  • Fuel station recommendation based on prices
  • Alternative fuel route suggestions

Week 2:

  • Historical fuel consumption tracking
  • Personalized consumption models (learning from actual usage)
  • Seasonal fuel economy adjustments
  • Community fuel price reporting

Week 3:

  • Advanced analytics dashboard
  • Fuel savings leaderboard
  • Export trip fuel reports
  • Integration with vehicle telematics (if available)

Success Metrics

Technical Metrics

  • Fuel economy calculation accuracy within ±10% of real-world consumption
  • Wind forecast accuracy within ±15% (limited by weather API)
  • Route calculation time under 2 seconds for typical trips
  • 99.9% uptime for weather and fuel price services

Business Metrics

  • User engagement: % of users who use fuel economy features
  • Average savings: Median dollar savings per trip
  • Route selection: % of users choosing economical over fastest route
  • Retention: Impact on user retention and subscription conversion

User Satisfaction

  • Fuel savings accuracy rating
  • Feature usefulness rating
  • Likelihood to recommend based on fuel savings feature
  • Support tickets related to fuel economy calculations

Cost Analysis

API Costs

  • Open-Meteo: Free (no API key required)
  • Fuel Price APIs: $50-200/month depending on provider and volume
  • Database storage: ~$10/month for fuel price historical data

Development Costs

  • Phase 1-2: 4-5 weeks (core functionality)
  • Phase 3: 2-3 weeks (user interface)
  • Phase 4: 2-3 weeks (advanced features)
  • Total: 8-11 weeks for complete implementation

Infrastructure Costs

  • Compute: Minimal additional load on existing routing infrastructure
  • Storage: ~500 MB/year for fuel price history
  • Bandwidth: Negligible increase (weather API responses are small)

Future Enhancements

  1. Electric Vehicle Support

    • Battery consumption modeling based on terrain
    • Charging station integration
    • Range anxiety mitigation
  2. Machine Learning Integration

    • Personalized consumption models
    • Driver behavior analysis
    • Predictive fuel price modeling
  3. Carbon Footprint Tracking

    • CO₂ emissions calculation
    • Carbon offset suggestions
    • Environmental impact visualization
  4. Advanced Weather Integration

    • Temperature effects on fuel economy
    • Rain and road conditions
    • Traffic pattern prediction
  5. Fleet Management Features

    • Multi-vehicle fuel tracking
    • Company-wide fuel savings reports
    • Driver performance comparison

References