This document tracks the performance optimization journey of the airspace rendering pipeline in The Paragliding App. The pipeline processes aviation airspace data from OpenAIP, stores it in SQLite, and renders it on maps with polygon clipping to eliminate overlaps.
| Stage | Optimization | Implementation Details | Performance Impact | Code Location |
|---|---|---|---|---|
| 1. SQL Filtering | Move filtering to database | Filter by type, class, altitude, and viewport in SQL query instead of post-processing | Reduces data loaded by ~70-90% | airspace_geojson_service.dart:445-450 |
| 2. Pre-computed Altitudes | Store altitude in feet | Added lower_altitude_ft INTEGER column to avoid runtime conversion |
Eliminates ~1ms per 100 airspaces | airspace_disk_cache.dart:107-108 |
| 3. Viewport Culling | SQL spatial indices | Compound index idx_geometry_spatial for bounding box queries |
Fast spatial queries (~5ms for 1000s) | airspace_disk_cache.dart:185-189 |
| 4. Direct Pipeline | Skip GeoJSON parsing | Load from cache directly without intermediate JSON | Saves ~20-30ms per load | _loadAirspacePolygonsFromCacheDirect() |
| 5. Altitude Sorting | Pre-sort for clipping | Sort airspaces by altitude once for optimal clipping order | Enables early exit optimization | airspace_geojson_service.dart:1473-1479 |
| 6. Early Exit | Stop at higher altitudes | Break loop when lower altitude ≥ current altitude | Reduces comparisons by ~40-60% | airspace_geojson_service.dart:1538-1545 |
| 7. Bounds Pre-check | Inline bbox check | Direct coordinate comparison before polygon operations | Skips ~50-70% of polygon ops | airspace_geojson_service.dart:1550-1557 |
| 8. Altitude Array | Cache-friendly layout | Pre-extract altitudes into contiguous Int32List |
Better CPU cache locality | airspace_geojson_service.dart:1486-1490 |
| 9. Binary Storage | Compressed coordinates | Float32 binary arrays with GZIP compression | 75% storage reduction vs JSON | airspace_disk_cache.dart:227-287 |
| 10. Int32 Coordinates | Direct Clipper2 pipeline | Int32 storage with zero-copy ClipperData | 25-40% faster clipping, 85% memory reduction | Multiple files (see below) |
The original pipeline had excessive conversions and memory allocations:
Database (Float32) → Float32List → LatLng objects → Int64 → Clipper2
This created:
Direct Int32 pipeline with ClipperData wrapper:
Database (Int32) → Int32List.view → ClipperData → Point64 → Clipper2
| Metric | Before | After | Improvement |
|---|---|---|---|
| Clipping Time (1344 polygons) | ~2000-2500ms | 1508-1727ms | 25-40% faster |
| Memory Allocations | 50,000+ LatLng objects | 0 intermediate objects | 100% reduction |
| Conversion Overhead | ~35-45ms per 1000 | ~2-5ms per 1000 | 85% reduction |
| Memory per Coordinate | ~56 bytes | ~8 bytes | 85% reduction |
-- Multiple separate indices (suboptimal)
CREATE INDEX idx_geometry_spatial ON airspace_geometry(
bounds_west, bounds_east, bounds_south, bounds_north
);
CREATE INDEX idx_geometry_spatial_altitude ON airspace_geometry(
lower_altitude_ft, bounds_west, bounds_east, bounds_south, bounds_north
);
-- Create optimized covering index for the most common query pattern
CREATE INDEX idx_geometry_spatial_covering ON airspace_geometry(
bounds_west,
bounds_east,
bounds_south,
bounds_north,
lower_altitude_ft,
type_code,
id,
coordinates_binary,
polygon_offsets
);
Expected Impact:
Add grid cell column for coarse filtering before exact spatial query:
-- Add grid cell column (10x10 degree grid)
ALTER TABLE airspace_geometry ADD COLUMN grid_cell INTEGER;
-- Update grid cells
UPDATE airspace_geometry
SET grid_cell =
(CAST((bounds_west + 180) / 10 AS INTEGER)) * 100 +
(CAST((bounds_south + 90) / 10 AS INTEGER));
-- Create grid index
CREATE INDEX idx_geometry_grid ON airspace_geometry(
grid_cell, bounds_west, bounds_east
);
-- Optimized query with grid filtering
SELECT * FROM airspace_geometry
WHERE grid_cell IN (?, ?, ?, ?) -- Pre-computed grid cells
AND bounds_west <= ? AND bounds_east >= ?
AND bounds_south <= ? AND bounds_north >= ?
AND lower_altitude_ft <= ?
ORDER BY lower_altitude_ft ASC;
Expected Impact:
Based on production logging from CLIPPING_DETAILED_PERFORMANCE:
| Metric | Baseline (O(n²)) | Current | Improvement |
|---|---|---|---|
| Theoretical comparisons | n*(n-1)/2 | - | - |
| Actual comparisons | 100% of theoretical | 30-40% of theoretical | 60-70% reduction |
| Altitude rejections | 0 | 40-60% of comparisons | Early exit working |
| Bounds rejections | 0 | 50-70% of remaining | Spatial filtering effective |
| Empty clipping lists | 0 | ~20% of airspaces | Skip unnecessary ops |
| Actual clipping operations | 100% | ~20% of airspaces | 80% reduction |
Reason: Too complex for current scale (<5000 polygons)
Reason: Limited Dart support, buggy in AOT compilation
Reason: Not available in Flutter environment
Reason: Overkill for current polygon counts
LoggingService.structured('CLIPPING_PERFORMANCE', {
'polygons_input': count,
'polygons_output': count,
'clipping_time_ms': ms,
'total_comparisons': count,
'altitude_rejections': count,
'bounds_rejections': count,
});
# Enable performance logging
LoggingService.enablePerformanceLogging = true;
# Look for structured logs
flutter_controller_enhanced logs 50 | grep "CLIPPING_PERFORMANCE"
flutter_controller_enhanced logs 50 | grep "AIRSPACE_CLIPPING"
Last Updated: 2025-01-12 Next Review: After covering index implementation