Switch to dark mode inside tunnels

In this example, we illustrate how to modify the map display (between light and dark modes) based on the location within a specific route.

To mimic the movement of a vehicle, we employ an animated point along the route. In this scenario, we simulate the vehicle entering a tunnel, prompting a switch to the dark map style. Subsequently, when the vehicle exits the tunnel, we revert back to the light map style. You can download the vehicle image, and obtain the route geoJSON file.

NPM module setup


npm install --save @maptiler/sdk @turf/turf
import { Map, MapStyle, config } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import * as turf from '@turf/turf';

config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
let activeMode = "light";
let route;
// Used to increment the value of the point measurement against the route.
let counter = 0;
const style = getStyleByMode(activeMode);
const map = new Map({
  container: 'map',
  style: MapStyle.STREETS.LIGHT,
  center: [6.7979, 45.8515],
  zoom: 10,
  hash: true
});

function getStyleByMode(mode) {
  return (mode == 'dark') ? MapStyle.STREETS.DARK : MapStyle.STREETS.LIGHT;
}

function switchMode(mode) {
  stop();
  activeMode = mode;
  const style = getStyleByMode(mode);
  map.setStyle(style);
}

function tunnelEntrance(counter) {
  return counter == 779;
}

function tunnelExit(counter) {
  return counter == 1131;
}

let playing = true;

const origin = [6.856344820704434, 45.90164121615423];
// A single point that animates along the route.
// Coordinates are initially set to origin.
const point = {
  'type': 'FeatureCollection',
  'features': [
    {
      'type': 'Feature',
      'properties': {},
      'geometry': {
        'type': 'Point',
        'coordinates': origin
      }
    }
  ]
};

// Number of steps to use in the animation, more steps means
// a smoother animation, but too many steps will result in a
// low frame rate
const steps = 1500;

//load the route GeoJSON form MapTiler Cloud
maptilersdk.data.get('YOUR_MAPTILER_DATASET_ID_HERE')
.then((data) => {
  route = data;

  const options = {tolerance: 0.0001, highQuality: true};
  route = turf.simplify(route, options);

  // Calculate the distance in kilometers between route start/end point.
  const lineDistance = turf.length(route.features[0], 'kilometers');

  const arc = [];

  // Draw an arc between the `origin` & `destination` of the two points
  for (let i = 0; i < lineDistance; i += lineDistance / steps) {
      const segment = turf.along(route.features[0], i, 'kilometers');
      arc.push(segment.geometry.coordinates);
  }

  // Update the route with calculated arc coordinates
  route.features[0].geometry.coordinates = arc;

  map.on('load', function () {

    loadAnimationLayers();

    counter = 0;
    // Start the animation.
    animate(counter);
  });

  map.on('styledata', () => {
    const waiting = () => {
      if (!map.isStyleLoaded()) {
        setTimeout(waiting, 100);
      } else {
        loadAnimationLayers();
      }
    };
    waiting();
  });

});

async function loadAnimationLayers() {
  if (!map.hasImage('cat')) {
    const image = await map.loadImage('../navigation.png');
    map.addImage('cat', image.data);
  }

  // Add a source and layer displaying a point which will be animated in a circle.
  if (!map.getSource('route')) {
    map.addSource('route', {
      'type': 'geojson',
      'data': route
    });

    map.addSource('point', {
      'type': 'geojson',
      'data': point
    });
  }

  if (!map.getLayer('route')) {
    map.addLayer({
      'id': 'route',
      'source': 'route',
      'type': 'line',
      'paint': {
          'line-width': 2,
          'line-color': '#007cbf'
      }
    });

    map.addLayer({
      'id': 'point',
      'source': 'point',
      'type': 'symbol',
      'layout': {
        'icon-image': 'cat',
        'icon-size': 0.05,
        'icon-rotate': ['get', 'bearing'],
        'icon-rotation-alignment': 'map',
        'icon-overlap': 'always',
        'icon-ignore-placement': true
      }
    });

    playing = true;
    // Restart the animation.
    animate(counter);
  }
}

function stop() {
  playing = false;
}

function animate() {
  if (!playing) return;
  // Update point geometry to a new position based on counter denoting
  // the index to access the arc.
  point.features[0].geometry.coordinates =
    route.features[0].geometry.coordinates[counter];

  // Calculate the bearing to ensure the icon is rotated to match the route arc
  // The bearing is calculate between the current point and the next point, except
  // at the end of the arc use the previous point and the current point
  try {
    point.features[0].properties.bearing = turf.bearing(
      turf.point(
        route.features[0].geometry.coordinates[
          counter >= steps ? counter - 1 : counter
        ]
      ),
      turf.point(
        route.features[0].geometry.coordinates[
          counter >= steps ? counter : counter + 1
        ]
      )
    );
  } catch (error) {

  }

  // Update the source with this new data.
  map.getSource('point').setData(point);

  // Request the next frame of animation so long the end has not been reached.
  if (counter < steps - 1) {
    requestAnimationFrame(animate);
  }

  counter = counter + 1;

  if (tunnelEntrance(counter)) {
    switchMode('dark');
  } else if (tunnelExit(counter)) {
    switchMode('light');
  }
}

document.getElementById('replay').addEventListener('click', function () {
  // Set the coordinates of the original point back to origin
  point.features[0].geometry.coordinates = origin;

  // Update the source layer
  map.getSource('point').setData(point);

  // Reset the counter
  counter = 0;

  // Restart the animation.
  animate(counter);
});
Change between light and dark mode based on the time of day

Change to dark mode at sunset

Examples

Toggle light and dark map modes based on time.

Set dark mode based on system settings

Set dark mode based on system settings

Examples

Switch map light/dark mode based on system color settings.

Built-in map styles

Built-in map styles

Tutorials

Easily create beautiful maps using reliable built-in styles.

Change map styles

Change map styles

Tutorials

Switch between built-in and custom map styles dynamically.

Was this helpful?