Skip to content

Optimize Map Performance, Bounding, and Dive Trips Visualization#198

Merged
kargig merged 5 commits intomainfrom
feature/optimize-dive-trips-map-pagination
Apr 10, 2026
Merged

Optimize Map Performance, Bounding, and Dive Trips Visualization#198
kargig merged 5 commits intomainfrom
feature/optimize-dive-trips-map-pagination

Conversation

@kargig
Copy link
Copy Markdown
Owner

@kargig kargig commented Apr 10, 2026

Overview

This PR delivers sweeping performance optimizations and architectural improvements to the Map visualization across all entity types (dive-trips, diving-centers, dives, and dive-sites). It standardizes API responses, optimizes SQL queries with spatial functions, eliminates N+1 queries, defers expensive HTML sanitization to unblock the main thread, and implements strict geo-bounding filters to slash network payload sizes.

Changes Made

Backend

  • Pagination Standardization: Refactored the /api/v1/newsletters/trips endpoint to return a standardized paginated dictionary response (ParsedDiveTripListResponse), bringing it in line with other list APIs.
  • N+1 Query Elimination: Embedded latitude and longitude fields directly into the ParsedDiveTripResponse and nested ParsedDiveResponse schemas. The backend now calculates coordinate fallbacks (Diving Center vs. Dive Site), eliminating the need for the frontend to fetch thousands of external entities as a lookup dictionary.
  • Spatial Optimization: Replaced slow Python-side distance math with highly optimized native MySQL ST_Distance_Sphere sorting in the dive trips API.
  • Payload Reductions: Applied SQLAlchemy defer() to massive content and description columns in the newsletters and diving-centers list endpoints. Map payload sizes dropped by ~90%.
  • Serialization Speedup: Set ORJSONResponse as the default FastAPI response class globally for significantly faster JSON serialization.
  • Bounding Box Support: Added north, south, east, and west query parameters to the diving_centers and dives endpoints to allow database-level spatial filtering.
  • Bug Fix: Fixed an OperationalError 3065 sorting error caused by the execution order of DISTINCT and ORDER BY.

Frontend

  • Lazy Map Popups (Huge Perf Win): Implemented deferred DOMPurify.sanitize() and HTML string generation for Leaflet popups. Popups are now only generated and sanitized when a user explicitly clicks a marker, completely unblocking the main JavaScript thread and eliminating severe browser hangs.
  • Strict Bounds Requirements: Updated useViewportData to require bounds to be calculated by Leaflet before allowing API fetches. This prevents the map from blindly querying 1,000 random items globally on initial load.
  • Trips Exemption: Explicitly exempted dive-trips from the bounds-fetching requirement (since trips are globally filtered by date), which fixed a massive 6-second double-fetch race condition.
  • Multi-Site Trip Plotting: Rewrote LeafletMapView to plot an individual map marker for every dive site within a single dive trip. The parent diving center coordinates are only used as a fallback if no sites have coordinates.
  • Dynamic Marker Coloring: Trip markers are now dynamically color-coded based on their schedule status: Past (Gray), Today (Orange), Future (Green).
  • Popup Overhauls: Removed raw GPS coordinates from dive trip popups. Popups now prominently feature linked Dive Site names, formatted trip dates, and truncated descriptions.
  • Pagination UI: Added missing top pagination controls to the /dive-trips list page.

Breaking Changes

  • API Response Structure: The response format for /api/v1/newsletters/trips has been updated from a flat JSON array to a paginated dictionary structure ({ items: [...], total: X, page: Y, ... }). All frontend consumers have been updated to reflect this.

Testing

  • Performance Benchmarks: Verified using scripts/measure_performance2.js. Achieved massive gains:
    • -58.1% faster sequential load times for /map?type=dive-trips.
    • -87.7% reduction in payload size for /map?type=diving-centers (from ~340KB down to ~41KB).
    • Massive reduction in P95 spikes across all map views.
  • Backend Tests: All backend pytest suites (test_newsletters.py, test_sorting.py, test_dives.py, test_diving_centers.py) run successfully in the isolated GitHub Actions Docker environment.
  • Frontend Linter: Verified all modified React components pass cleanly via npx eslint --fix.
  • Manual Verification: Extensively tested with Chrome DevTools MCP to ensure zero console errors, responsive panning, accurate adjacent viewport prefetching, and proper popup rendering.

Related Issues

  • Resolves multiple frontend rendering bottlenecks, N+1 query inefficiencies, and pagination bugs.

Additional Notes

  • Deployment Considerations: This PR relies on MySQL spatial functions (ST_Distance_Sphere). Ensure the production database environment correctly supports these spatial operations. The global shift to ORJSONResponse is fully backwards compatible but offers significant throughput gains.
  • All identified frontend CPU blocking issues related to the wind=true overlay and thousands of type=dives markers have been resolved by the deferred popup rendering optimization.

kargig added 2 commits April 10, 2026 14:44
- Refactor `/newsletters/trips` API to use a
paginated response (`ParsedDiveTripListResponse`)
for consistency with other endpoints.
- Add `latitude` and `longitude` to the trip
response schema, eliminating the need for N+1
queries to resolve map marker coordinates on the
frontend.
- Short-circuit map data fetching in
`useViewportData.js` when no trips match the
current filters, saving bandwidth.
- Add top pagination controls to the Dive Trips
list page.
- Fix state initialization in `IndependentMapView`
to prevent duplicate API requests on mount.
- Fix MySQL sorting error caused by `DISTINCT`
and `ORDER BY` execution order.
- Add `latitude` and `longitude` to nested
`ParsedDiveResponse` schema to support multi-site
trip mapping.
- Update trip serialization to assign fallback
coordinates from the diving center to the parent
trip object.
- Modify `LeafletMapView` to plot individual
markers for every dive site within a trip, using
the diving center coordinates only as a fallback.
- Implement dynamic marker color-coding for dive
trips based on schedule status (past, today,
future).
- Overhaul map popups for dive trips: replace raw
coordinates with linked Dive Site names, formatted
trip details, and brief descriptions.
- Add dive trips map URL to performance measuring
scripts.
@kargig kargig force-pushed the feature/optimize-dive-trips-map-pagination branch from 56c9b33 to 4da7169 Compare April 10, 2026 15:15
kargig added 3 commits April 10, 2026 19:12
- Replace slow Python-side distance sorting with
native MySQL `ST_Distance_Sphere` in the dive
trips API, massively speeding up spatial queries.
- Defer loading of the heavy `content` column in
the `newsletters` list endpoint to reduce memory
and bandwidth overhead.
- Set `ORJSONResponse` as the default FastAPI
response class globally for much faster JSON
serialization.
- Fix a frontend caching regression in
`useViewportData` by omitting map bounds from
the React Query cache key for dive trips, stopping
redundant API requests when panning.
- Sync the debounced viewport immediately on
the initial load in `useViewportData` to bypass
the 1.5-second debounce delay.
- Require `bounds` to be calculated before
allowing API fetches in `useViewportData` to
prevent querying 1000 random items globally.
- Calculate and set initial map bounds quickly
after the Leaflet instance loads in
`IndependentMapView` to ensure data fetching
triggers without requiring user interaction.
- Implement deferred DOMPurify HTML sanitization
and deferred HTML string generation for Leaflet
popups (`LeafletMapView`). Popups are now only
generated when explicitly clicked, massively
unblocking the main thread and eliminating a 10s
hang when loading thousands of points.
- Implement explicit map bounding box filtering
for Diving Centers and Dives in both the
frontend `useViewportData` hook and the backend
SQLAlchemy endpoints (`diving_centers.py`,
`dives_crud.py`), mirroring the logic for Dive
Sites.
- Add `defer` to the massive `description` column
in `diving_centers.py` for map payloads,
dropping bandwidth usage by 90%.
- Exempt dive trips from the `useViewportData`
initial load bounds requirement, fixing a flicker
and a massive 6-second double-fetch React race
condition regression.
@kargig kargig changed the title Improve Dive Trips Map Visualization and Pagination Optimization Optimize Map Performance, Bounding, and Dive Trips Visualization Apr 10, 2026
@kargig kargig merged commit 2a554f1 into main Apr 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant