the_paragliding_app

Airspace Performance Optimization

Overview

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.

Performance Goals


Optimization Timeline

Completed Optimizations

Code locations are symbol names, not line numbers. They were line ranges until 2026-08-29, by which point every one of them was wrong — four pointed past the end of a file that had since shrunk to 1234 lines, and the two that were still in range named unrelated code. Cite something greppable.

Stage Optimization Implementation Details Performance Impact Code Location
1. SQL Filtering Move filtering to database Filter by type, class, altitude, and bounds in the SQL query instead of post-processing Reduces data loaded by ~70-90% AirspaceDiskCache.getGeometriesInBounds()
2. Pre-computed Altitudes Store altitude in feet lower_altitude_ft INTEGER column avoids runtime conversion Eliminates ~1ms per 100 airspaces airspace_disk_cache.dart, geometry table schema
3. Viewport Culling SQL spatial indices Compound index idx_geometry_spatial over the four bounds columns Fast spatial queries (~5ms for 1000s) idx_geometry_spatial in _onCreate
4. Direct Pipeline Skip GeoJSON parsing Build Clipper data straight from database BLOBs, no intermediate JSON or LatLng Saves ~20-30ms per load AirspaceDiskCache._createClipperData()
5. Altitude Sorting Pre-sort for clipping Sort airspaces by altitude once for optimal clipping order Enables early exit optimization _applyPolygonClippingOptimized()
6. Early Exit Stop at higher altitudes Break loop when lower altitude ≥ current altitude Reduces comparisons by ~40-60% _applyPolygonClippingOptimized()
7. Bounds Pre-check Inline bbox check Direct coordinate comparison before polygon operations Skips ~50-70% of polygon ops _applyPolygonClippingOptimized()
8. Altitude Array Cache-friendly layout Altitudes pre-extracted into a contiguous Int32List Better CPU cache locality ClippingBatch.altitudes
9. Binary Storage Compressed coordinates Superseded by #10 — see note below.
10. Int32 Coordinates Direct Clipper2 pipeline Int32 BLOBs (scaled by 10^7) with zero-copy Int32List.view into the Clipper data 25-40% faster clipping, 85% memory reduction coordinates_binary / polygon_offsets columns, _createClipperData()

#9 no longer describes the code. It recorded Float32 binary arrays with GZIP compression; #10 replaced that with uncompressed Int32 BLOBs, which is what the cache stores today (coordinates_binary BLOB -- Int32 array (scaled by 10^7)). There is no gzip in the disk cache. Both were listed as “completed” side by side, which reads as the two being layered rather than one having replaced the other.


Optimization Deep Dives

1. Int32 Coordinate Optimization (Major Improvement)

Problem

The original pipeline had excessive conversions and memory allocations:

Database (Float32) → Float32List → LatLng objects → Int64 → Clipper2

This created:

Solution

Direct Int32 pipeline with ClipperData wrapper:

Database (Int32) → Int32List.view → ClipperData → Point64 → Clipper2

Implementation Components

  1. ClipperData Class: Helper class for zero-copy Int32 to Clipper2 conversion
  2. Direct Pipeline: Database BLOBs → ClipperData → Clipper2 (no LatLng intermediary)
  3. Memory Alignment: Handled SQLite BLOB alignment issues with buffer copying
  4. Backward Compatibility: Maintains support for traditional LatLng paths for insertion

Performance Results

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

Test Conditions

2. SQL Spatial Index Optimization

Current Index Structure (Can Be Improved)

-- 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
);

Proposed Covering Index

-- 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:

3. Grid-Based Pre-filtering (Future Optimization)

Concept

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:


Current Performance Metrics

Clipping Operation Analysis

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

Timing Breakdown (100 airspaces)


Optimization Impact Summary

Before All Optimizations (Estimated)

After All Optimizations


Rejected Optimizations

R-tree Spatial Index

Reason: Too complex for current scale (<5000 polygons)

SIMD Operations

Reason: Limited Dart support, buggy in AOT compilation

WebAssembly Clipper

Reason: Not available in Flutter environment

GPU Acceleration

Reason: Overkill for current polygon counts


Benchmarking Methodology

Test Scenarios

  1. Dense Urban: 200+ overlapping airspaces (e.g., London, Paris)
  2. Sparse Rural: 10-20 airspaces with minimal overlap
  3. Pan Test: Rapid viewport changes
  4. Zoom Test: Multiple zoom levels

Metrics to Track

LoggingService.structured('CLIPPING_PERFORMANCE', {
  'polygons_input': count,
  'polygons_output': count,
  'clipping_time_ms': ms,
  'total_comparisons': count,
  'altitude_rejections': count,
  'bounds_rejections': count,
});

Performance Targets


Running Performance Tests

# Enable performance logging
LoggingService.enablePerformanceLogging = true;

# Look for structured logs
grep "CLIPPING_PERFORMANCE" dev_data/flutter.log
grep "AIRSPACE_CLIPPING" dev_data/flutter.log

Change Log

2024-01-20

2025-09-20

2025-01-12

2026-08-29


Last Updated: 2026-08-29 Next Review: After covering index implementation