📊 OHM Vector Tile - Admin Boundary Optimization Results
Posted by Rub21 on March 24, 2025 in English. Last updated on March 27, 2025.Vector tiles API: https://vtiles.openhistoricalmap.org/
One of the most frequently added data types in OpenHistoricalMap are boundaries. These are often represented as relations and ways, these relations are imported into the PostGIS tiler database. Many of these polygons are quite complex, sometimes covering entire countries or continents and for each relation/way, a polygons are generated.
Examples:
- Around 362 boundary relations represent British Empire on OHM over time.
- Around 1100 boundary relations, represent San José - California on OHM over time.
Previous Workflow for Vector Tiles
In the previous version, vector tiles were generated directly from raw boundary geometries. This method resulted in slower tile response times and significantly larger tile sizes, particularly when handling complex or high-resolution polygons.
We use PostGIS’s ST_AsMVTGeom
function to generate vector tiles. When working with ST_AsMVTGeom
and complex, global-scale geometries—especially those with thousands of vertices—the clipping process (cutting to the !BBOX!
) can introduce significant computational overhead. This becomes especially problematic at lower zoom levels, where a single geometry may span multiple tiles, triggering expensive intersection and transformation operations.
Over time, many users began noticing that tiles were loading too slowly, particularly in regions with dense boundary data. This performance bottleneck in the boundaries layer also negatively impacted the loading speed of other map layers, resulting in an overall degraded map experience.
Improving Performance with Complex Boundaries
📎 From: GitHub Issue #800
To address the previous version of the tiler, we conducted a detailed analysis of the boundary data and implemented a merging strategy.
We decided to use only a member according to their admin_level
and historical timeframes (start_date
and end_date
).
The dates were crucial for reducing duplication and redundancy. For example, we found many cases where the same geometry (way) was reused across multiple relations with different time spans:
- Relation 1:
start_date = 1990-12-31
,end_date = 2000-01-01
- Relation 2:
start_date = 2000-01-02
,end_date = 2009-12-31
- Relation 3:
start_date = 2010-01-02
,end_date = 2024-12-31
Instead of treating each as a separate geometry, we merged them into a simplified timeline-aware representation, significantly reducing the number of features that needed to be processed and served.
From the previous example, we end up with only 2 geometries:
- Merge Relation 1 and 2 (overlapping dates):
start_date = 1990-12-31
,end_date = 2009-12-31
- Keep Relation 3 (non-overlapping):
start_date = 2010-01-02
,end_date = 2024-12-31
Real-world Example
The way 198892846 originally belonged to 1,169 different relations.
After applying our optimization, it was merged into a single line geometry.
As a result, we introduced a column called merged_row_count
, which indicates how many relations have been consolidated into one line.
This optimization has greatly improved tile loading times and reduced the size of the vector tiles, offering a better user experience across the map.
📏 Vector Tile Size Statistics
Metric | 🔴 Before Optimization | 🟢 After Optimization | Improvement |
---|---|---|---|
Total tiles | 18 | 9 | 📦 Fewer tiles due to better data distribution |
Min tile size | 18.83 KB | 5.16 KB | đź”˝ Smaller minimal size |
Avg tile size | 1591.79 KB | 193.46 KB | âś… ~88% smaller on average |
Max tile size | 7854.09 KB | 445.94 KB | âś… ~94% smaller max tile |
land_ohm_lines - Coords per tile |
~663,300 | ~7,963 | âś… ~99% drop in geometry complexity |
land_ohm_lines - Avg tile size |
1530.21 KB | 120.53 KB | âś… Huge efficiency gain |
land_ohm_lines - Max tile size |
3617.17 KB | 1559.44 KB | âś… Reduced by more than half |
⚙️ Tiler Query Performance Statistics
Aspect | 🟥 Before Optimization (Slow Query) | 🟩 After Optimization (Fast Query) |
---|---|---|
Execution Time | 934.813 ms |
12.048 ms âś… (~77x faster) |
Rows Returned | 531 |
63 |
Access Type | Index Scan | Parallel Bitmap Heap Scan |
Index Used | idx_mview_admin_boundaries_lines_z10_12_geom |
mview_admin_boundaries_z10_12_geometry_idx |
Estimated Row Size | ~3836 bytes | ~95 bytes |
Columns Selected | 100+ (JSON tags, computed dates, multilingual names) | Minimal (geometry, IDs, dates) |
This was one of the most impactful performance improvements we’ve implemented in our vector tiles — but we’re still working on others, which I’ll continue sharing in upcoming posts.
Also related post here: https://forum.openhistoricalmap.org/t/better-performance-less-clutter-and-the-end-of-an-era/426
Discussion