Back to all examples

Places Customization Playground

Customize place markers with icons, fonts, and colors

import type { Place } from '@tomtom-org/maps-sdk/core';
import { TomTomConfig } from '@tomtom-org/maps-sdk/core';
import type { MapFont, PlaceIconConfig, PlacesTheme } from '@tomtom-org/maps-sdk/map';
import { PlacesModule, TomTomMap } from '@tomtom-org/maps-sdk/map';
import { search } from '@tomtom-org/maps-sdk/services';
import type { DataDrivenPropertyValueSpecification } from 'maplibre-gl';
import tomtomLogo from './tomtomLogo.png';
import './style.css';
import { API_KEY } from './config';

// (Set your own API key when working in your own environment)
TomTomConfig.instance.put({ apiKey: API_KEY, language: 'en-US' });

(async () => {
    const map = new TomTomMap({
        container: 'sdk-map',
        center: [4.90435, 52.36876],
        zoom: 10,
    });
    const places = await PlacesModule.get(map);

    const fontSelectors: NodeListOf<HTMLInputElement> = document.querySelectorAll('.sdk-example-font-selector');
    const contentSelectors: NodeListOf<HTMLInputElement> = document.querySelectorAll('.sdk-example-content-selector');
    const colorSelectors: NodeListOf<HTMLDivElement> = document.querySelectorAll('.sdk-example-color-selector');
    for (const element of colorSelectors) {
        element.style.backgroundColor = element.dataset.value ?? '';
    }

    const customIconsConfig: PlaceIconConfig = {
        categoryIcons: [
            { id: 'ELECTRIC_VEHICLE_STATION', image: tomtomLogo, pixelRatio: 1 },
            { id: 'CAFE_PUB', image: 'https://dummyimage.com/30x20/4137ce/fff', pixelRatio: 1 },
        ],
    };

    const multiLineLabel: DataDrivenPropertyValueSpecification<string> = [
        'format',
        ['get', 'title'],
        { 'font-scale': 0.9 },
        '\n',
        {},
        ['get', 'phone'], // comes as extra feature property, see applyExtraFeatureProps call below
        { 'font-scale': 0.8, 'text-font': ['literal', ['Noto-Regular']], 'text-color': '#3125d1' },
        '\n',
        {},
        ['get', 'staticProp'], // comes as extra feature property, see applyExtraFeatureProps call below
        { 'font-scale': 0.7, 'text-font': ['literal', ['Noto-Bold']], 'text-color': '#ce258d' },
    ];

    const updatePlaces = async () => {
        await places.show(
            await search({
                query: '',
                poiCategories: ['ELECTRIC_VEHICLE_STATION', 'CAFE_PUB'],
                boundingBox: map.getBBox(),
                limit: 100,
            }),
        );
    };

    const listenToUIEvents = () => {
        const iconStyleSelector = document.getElementById('sdk-example-icon-style-selector') as HTMLSelectElement;
        for (const element of colorSelectors) {
            element.addEventListener('click', () => {
                for (const element1 of colorSelectors) {
                    element1.classList.remove('active');
                }
                element.classList.add('active');
                places.applyTextConfig({ ...places.getConfig()?.text, color: element.dataset.value });
            });
        }

        for (const element of fontSelectors) {
            element.addEventListener('change', () => {
                places.applyTextConfig({
                    ...places.getConfig()?.text,
                    font: [element.value as MapFont],
                });
            });
        }

        for (const element of contentSelectors) {
            element.addEventListener('change', () => {
                element.value !== 'default' &&
                    places.applyExtraFeatureProps({
                        phone: (place: Place) => `Phone: ${place.properties.poi?.phone}`,
                        staticProp: 'Static text',
                    });
                places.applyTextConfig({
                    ...places.getConfig()?.text,
                    title: element.value === 'default' ? undefined : multiLineLabel,
                });
            });
        }

        iconStyleSelector?.addEventListener('change', (e) => {
            const value = (e.target as HTMLSelectElement).value;
            if (value === 'custom') {
                places.applyIconConfig(customIconsConfig);
            } else {
                places.applyTheme(value as PlacesTheme);
            }
        });
    };

    await updatePlaces();
    map.mapLibreMap.on('moveend', updatePlaces);
    listenToUIEvents();
})();

Related examples