MapLibre GL Integration
Relevant source files
The following files were used as context for generating this wiki page:
The P4N Map project integrates MapLibre GL (version ^5.6.0) as its core mapping engine to provide high-performance, GPU-rendered spatial visualizations. Unlike standard DOM-based mapping libraries, this implementation relies on GeoJSON sources and MapLibre's native symbol/circle layers to ensure that thousands of markers remain synchronized with the basemap without lag or positional drift.
The integration serves as the primary interface for visualizing multi-stop van trips, route corridors, Park4Night spots, and OpenStreetMap (OSM) amenities. It utilizes free OpenStreetMap raster tiles, avoiding reliance on proprietary Mapbox or Google Maps keys.
Sources: package.json:17, README.md:20-22, src/components/MapView.tsx:3-9
Core Architecture
The mapping system is built around a centralized MapView component that manages a maplibregl.Map instance. Data is pushed to the map through a series of GeoJSON sources, which are updated using optimized setData calls to minimize re-renders.
Data Flow and Synchronization
The Planner component acts as the orchestrator, passing filtered and scored data down to MapView through React props. The map maintains internal state using useRef to track the map instance and its readiness, ensuring that operations like fitBounds or flyTo only occur after the map has fully loaded.
The diagram above shows how trip data flows from the planning logic into the MapLibre rendering engine.
Sources: src/components/MapView.tsx:55-125, src/components/Planner.tsx:755-766
Visual Layers and Styling
The map utilizes several distinct layers to represent different data types, organized from bottom to top to ensure proper visibility and interactivity.
| Layer ID | Source ID | Type | Description |
|---|---|---|---|
corridor | route | line | A wide, blurred green band representing the allowed detour distance from the road. |
route-line | route | line | The primary trip polyline calculated via OSRM. |
band-line | band | line | A bright yellow overlay highlighting the current "look-ahead" window. |
p4n-dots-lyr | p4n-dots | circle | Small dots for OSM amenities (fuel, water, etc.). |
p4n-places-lyr | p4n-places | symbol | GPU-rendered pins for Park4Night spots with custom icons. |
p4n-stops-lyr | p4n-stops | symbol | Lettered badges (A, B, C...) for trip waypoints. |
Sources: src/components/MapView.tsx:43-52, src/components/MapView.tsx:313-348, src/components/MapView.tsx:376-410
Dynamic Pin Generation
To maintain high performance and visual clarity, pins are drawn dynamically onto an HTML5 Canvas at 2x scale and registered as MapLibre images. This allows the app to support various states (e.g., Nature, Selected, Category) without loading external assets for every variant.
Interaction and Logic
Map interactions are handled through native MapLibre event listeners rather than React event handlers on DOM elements. This allows for precise "hit-testing" on GPU-rendered features.
Hover and Tooltips
The map implements a high-contrast, dark-themed hover card using maplibregl.Popup. When the user moves the cursor over a pin or amenity dot, the map identifies the feature and displays metadata including:
- Place name and rating.
- Distance from the road (
kmFromRoad). - Progress along the trip (
progressKm).
Sources: src/components/MapView.tsx:162-184, src/components/MapView.tsx:254-272
Navigation and Camera Control
The map state is updated automatically based on user actions:
- Selection: When a place is selected in the sidebar,
map.flyTocenters the map on the coordinate. - Geolocation: The "Find Me" feature uses the browser's Geolocation API to update a custom DOM marker and fly the camera to the user's position.
- Fitting Bounds: The
fitToContentfunction calculates a bounding box encompassing all route points, stops, and results to ensure the entire trip is visible.
Sources: src/components/MapView.tsx:130-160, src/components/Planner.tsx:327-353
Corridor Visualization Logic
A unique aspect of the integration is the visual corridor band. The width of the corridor layer is dynamically interpolated based on the map's zoom level to represent actual ground distance (kilometres) rather than fixed pixels.
// src/components/MapView.tsx:416-425
const K = corridorKm * 2 * 0.01807; // Constant for km-to-pixel ratio
map.setPaintProperty("corridor", "line-width", [
"interpolate",
["exponential", 2],
["zoom"],
4,
Math.max(6, K * 16),
20,
K * 1_048_576,
]);
This ensures the green "safety band" accurately reflects the maxKmFromRoad setting as the user zooms in and out.
Sources: src/components/MapView.tsx:408-428
Conclusion
The MapLibre GL integration provides the project with a robust, low-latency spatial foundation. By moving away from heavy DOM markers to GPU-accelerated layers and dynamic canvas-based icons, the system maintains high performance even when displaying complex multi-stop routes and dense result sets from Park4Night and OpenStreetMap.