# How TREK Renders Interactive Maps with Leaflet and Mapbox GL (3D Buildings & Terrain)

> Discover how TREK renders interactive maps using Leaflet and Mapbox GL, featuring dynamic switching between raster tiles and 3D buildings and terrain. Explore the dual-provider architecture.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: how-to-guide
- Published: 2026-06-26

---

**The TREK application uses a dual-provider architecture that lets users choose between classic Leaflet raster tiles or modern Mapbox GL vector rendering with 3D terrain and buildings, switching dynamically via [`MapSettingsTab.tsx`](https://github.com/mauriceboe/TREK/blob/main/MapSettingsTab.tsx).**

TREK implements a flexible mapping system that supports both lightweight raster tiles and GPU-accelerated vector graphics. The repository's **interactive maps with Leaflet and Mapbox GL** are implemented through separate React components that share a common settings interface. Users can toggle between providers at runtime without page reloads, enabling 3D building visualization when hardware and tokens permit.

## Leaflet Rendering with react-leaflet

The Leaflet stack provides a lightweight, raster-based mapping experience ideal for low-end devices or offline scenarios.

### MapView Component Architecture

The Leaflet implementation lives in [`client/src/components/Map/MapView.tsx`](https://github.com/mauriceboe/TREK/blob/main/client/src/components/Map/MapView.tsx). It leverages `react-leaflet` to wrap Leaflet primitives—such as `MapContainer`, `TileLayer`, `Marker`, and `Polyline`—in React-friendly components. The tile source defaults to Carto light basemaps but can be swapped via user settings.

A set of controller components manages map state: `MapController`, `BoundsController`, `SelectionController`, and `ViewportController` handle centering, bounds fitting, and viewport-change events for POI-explore pills.

### Custom Markers and Clustering

Marker customization uses Leaflet's `DivIcon` via the `createPlaceIcon` function to render photo-circle or category-icon markers. For performance with large datasets, `react-leaflet-cluster` groups markers using the `clusterIconCreateFunction` helper, displaying cluster counts without overwhelming the DOM.

### Location and Reservation Layers

The `LeafletLocationLayer` component draws the user's GPS dot and accuracy circle using the shared `useGeolocation` hook. For trip planning, the `ReservationOverlay` component renders flight- and train-style arcs on top of the basemap.

```tsx
// Render a Leaflet map with custom markers
import { MapView } from '@/components/Map/MapView';

export default function TripMap() {
  return (
    <MapView
      places={myPlaces}
      center={[48.8566, 2.3522]}
      zoom={12}
      tileUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
    />
  );
}

```

## Mapbox GL Rendering with 3D Terrain and Buildings

The Mapbox GL stack delivers high-resolution vector tiles, GPU-accelerated rendering, and true 3D visualization.

### Direct API Integration

The Mapbox implementation is found in [`client/src/components/Map/MapViewGL.tsx`](https://github.com/mauriceboe/TREK/blob/main/client/src/components/Map/MapViewGL.tsx). Unlike the Leaflet wrapper, this component talks directly to the `mapbox-gl` API, creating a map instance inside a container div referenced by `containerRef`. The Mapbox style URL and access token are read from the `useSettingsStore` hook (`settings.mapbox_access_token`), and changing these values rebuilds the map via a `useEffect` watching `[mapboxStyle, mapboxToken, mapbox3d]`.

### Enabling 3D Terrain and Buildings

When the selected style supports terrain (indicated by `wantsTerrain`), the `addTerrainAndSky` helper from [`client/src/components/Map/mapboxSetup.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/components/Map/mapboxSetup.ts) injects a DEM source and sky layer. The `addCustom3dBuildings` function adds a `fill-extrusion` layer that renders vector-tile building data with realistic heights, adjusting colors based on the dark mode flag.

```tsx
// Render a Mapbox GL map with 3D terrain and buildings
import { MapViewGL } from '@/components/Map/MapViewGL';

export default function TripMapGL() {
  return (
    <MapViewGL
      places={myPlaces}
      center={[48.8566, 2.3522]}
      zoom={12}
    />
  );
}

```

### Altitude-Aware Markers

Because Mapbox GL places HTML markers at sea level by default, the `syncMarkerAltitudes` routine (lines 50-70 of [`MapViewGL.tsx`](https://github.com/mauriceboe/TREK/blob/main/MapViewGL.tsx)) periodically queries `map.queryTerrainElevation` and updates each marker's `LngLat` to `[lng, lat, elevation]`. This prevents the "drift" bug where markers appear to float above or sink below the terrain when the camera pitches.

Custom marker elements are built by `createMarkerElement` (lines 60-134), mimicking the Leaflet photo-circle style with order-badge support, and attached via `new mapboxgl.Marker({ element, anchor: 'center' })`.

### Route Layers and Event Handling

Two GeoJSON sources (`trip-route` and `trip-gpx`) are added on load and rendered with a "casing-plus-core" line style that reproduces the Apple Maps look—dark blue casing with a bright blue core. The `ReservationMapboxOverlay` (in [`client/src/components/Map/reservationsMapbox.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/components/Map/reservationsMapbox.ts)) mirrors the Leaflet overlay, drawing arcs and endpoint badges.

Event handling wires directly to the Mapbox instance. The "add-place" action is explicitly bound to the middle mouse button to avoid conflicts with Mapbox's built-in rotate/pitch gestures.

## Switching Between Map Providers

The `MapSettingsTab` component (in [`client/src/components/Settings/MapSettingsTab.tsx`](https://github.com/mauriceboe/TREK/blob/main/client/src/components/Settings/MapSettingsTab.tsx)) stores the provider choice in `settings.map_provider`. A wrapper component conditionally renders either stack:

```tsx
// Provider switch UI
import { useSettingsStore } from '@/store/settingsStore';
import { MapView } from '@/components/Map/MapView';
import { MapViewGL } from '@/components/Map/MapViewGL';

export default function MapWrapper(props) {
  const provider = useSettingsStore(s => s.settings.map_provider ?? 'leaflet');

  return provider === 'leaflet' ? (
    <MapView {...props} />
  ) : (
    <MapViewGL {...props} />
  );
}

```

This architecture allows the entire map experience to be toggled at runtime without reloading the page, falling back to Leaflet when Mapbox tokens are unavailable.

## Summary

- TREK abstracts its map layer behind a **dual-provider architecture** supporting both Leaflet and Mapbox GL.
- **Leaflet** ([`MapView.tsx`](https://github.com/mauriceboe/TREK/blob/main/MapView.tsx)) provides lightweight raster tiles with clustering via `react-leaflet` and `react-leaflet-cluster`.
- **Mapbox GL** ([`MapViewGL.tsx`](https://github.com/mauriceboe/TREK/blob/main/MapViewGL.tsx)) enables 3D terrain and buildings using `addTerrainAndSky` and `addCustom3dBuildings` from [`mapboxSetup.ts`](https://github.com/mauriceboe/TREK/blob/main/mapboxSetup.ts).
- The `syncMarkerAltitudes` function prevents marker drift by querying DEM elevation data and updating marker positions to `[lng, lat, elevation]`.
- Users switch providers at runtime through [`MapSettingsTab.tsx`](https://github.com/mauriceboe/TREK/blob/main/MapSettingsTab.tsx), which updates `settings.map_provider` and conditionally renders the appropriate component.

## Frequently Asked Questions

### How does TREK switch between Leaflet and Mapbox GL?

The [`MapSettingsTab.tsx`](https://github.com/mauriceboe/TREK/blob/main/MapSettingsTab.tsx) component stores the provider selection in `settings.map_provider`. A wrapper component reads this value from the `useSettingsStore` hook and conditionally renders either `MapView` for Leaflet or `MapViewGL` for Mapbox GL, allowing runtime switching without page reloads.

### Why do Mapbox markers need altitude synchronization?

Mapbox GL places HTML markers at sea level by default, causing them to float or sink when the camera tilts over 3D terrain. The `syncMarkerAltitudes` function queries `map.queryTerrainElevation` for each marker's coordinates and updates their `LngLat` to include the terrain height, keeping them anchored to the surface during pitch changes.

### What enables 3D buildings and terrain in the Mapbox view?

The `addTerrainAndSky` helper adds a DEM source and sky layer from Mapbox's terrain service, while `addCustom3dBuildings` injects a `fill-extrusion` layer that renders building heights from vector tiles. These utilities are located in [`client/src/components/Map/mapboxSetup.ts`](https://github.com/mauriceboe/TREK/blob/main/client/src/components/Map/mapboxSetup.ts) and are called when the selected style requests terrain support.

### Can I use custom markers with both map providers?

Yes. Leaflet uses `DivIcon` via `createPlaceIcon` for photo-circle markers, while Mapbox GL uses `createMarkerElement` to build HTML elements attached with `new mapboxgl.Marker()`. Both approaches support custom styling, order badges, and category colors, with Mapbox additionally requiring altitude synchronization to align with terrain.