The Optimal Strategy: Numeric UTC with Client-Side Offsets
The best trade-off between dynamic user selection and render performance is to store timestamps as numeric UTC integers (Unix epoch) within vector tile properties and apply a single, global offset variable on the client side. This avoids the need to rebuild tiles on the server while bypassing the CPU overhead of per-feature Date object instantiation during the render loop.
Implementation Logic
Instead of converting every feature's timestamp to a local time zone, treat the time zone selection as a mathematical shift applied to the filter criteria rather than the data itself.
- Data Storage: Store timestamps in your vector tiles as integers (milliseconds or seconds since epoch). Avoid ISO 8601 strings, as string parsing in a render loop significantly degrades frame rates.
- Filter Calculation: When a user selects a time zone, calculate the current offset for that zone in milliseconds.
- Application: Apply the offset to the filter value passed to
setFilter(). For example, if the user wants to see data for "12:00 PM EST," calculate the UTC equivalent and filter the numeric properties against that single UTC value.
Minimizing Historic Offset Inconsistencies
Relying on the native Date object is risky for historical data because different browser engines may use different versions of the IANA Time Zone Database. To ensure deterministic behavior across all clients:
- Use a Standardized Library: Implement a library like Luxon or date-fns-tz. These libraries provide a consistent way to handle IANA zones and offsets independently of the browser's native
Intl implementation.
- Avoid Native Date Instantiation: Do not call
new Date() inside the map.on('render', ...) loop. Perform all time-zone calculations once per UI change and store the resulting numeric UTC timestamp in a variable.
Verification and Performance
To verify this approach, you can monitor the frame rate (FPS) using the browser's performance profiling tools. Compare the cost of setFilter(['>=', 'timestamp', utcValue]) against a custom filter that invokes new Date() for every feature. The numeric comparison is handled by the Mapbox GL JS engine (often via GPU/WebGL), whereas Date objects force a move back to the main CPU thread.
Diagnostic Detail Needed: Are you utilizing Mapbox's built-in setFilter for visibility, or are you manually iterating through features to update styles/properties via a custom render loop?