Back to all examples

POI category icons playground

This example on GitHub

Pick a circle, square, diamond or pin, and its colour, per POI category, on the map's own POIs and on searched places alike

POI category icons playground
import { TomTomConfig } from '@tomtom-org/maps-sdk/core';
import { calculatePaddedBBox, PlacesModule, POIsModule, TomTomMap } from '@tomtom-org/maps-sdk/map';
import { createLatestRequest, discoverPlaces } from '@tomtom-org/maps-sdk/services';
import { type CategoryStyle, categories, type PanelCategory, toCategoryIcons } from './categoryIcons';
import { initCategoryRows } from './panel';
import './style.css';
import { API_KEY } from './config';
import { initTogglePanel } from './togglePanel';

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

(async () => {
    const map = new TomTomMap({
        mapLibre: {
            container: 'sdk-map',
            center: [4.8907, 52.3712],
            zoom: 16,
        },
    });
    // The POIs the map style draws, and the places a search finds, drawn as the map draws its POIs
    const pois = await POIsModule.get(map);
    const searchResults = await PlacesModule.create(map, { markerType: 'base-map' });

    const categoryStyles = new Map<PanelCategory, CategoryStyle>(
        categories.map((category) => [category, category.initialStyle]),
    );

    // One list for both modules: each draws its own places of a listed category with that icon
    const applyCategoryIcons = () => {
        const categoryIcons = categories.flatMap((category) =>
            toCategoryIcons(category, categoryStyles.get(category) ?? category.initialStyle),
        );
        pois.updateConfig({ icon: { categoryIcons } });
        searchResults.updateConfig({ icon: { categoryIcons } });
    };

    const searchResultsToggle = document.getElementById('search-results-toggle') as HTMLInputElement;
    // A new map move retires the previous move's search, so a slow answer cannot paint over a newer viewport
    const latestSearch = createLatestRequest();
    const searchViewport = async () => {
        if (!searchResultsToggle.checked) return;

        // Null when the panel leaves too little of the map to search in.
        const boundingBox = calculatePaddedBBox({ map, surroundingElements: ['.ui-customPanel'] });
        if (!boundingBox) return;

        const search = await latestSearch.run((signal) =>
            discoverPlaces({
                filters: { poiCategories: categories.flatMap(({ ids }) => ids) },
                geoBias: { boundingBox },
                limit: 100,
                signal,
            }),
        );
        if (search.current) await searchResults.show(search.value);
    };

    initTogglePanel();
    initCategoryRows((category, style) => {
        categoryStyles.set(category, style);
        applyCategoryIcons();
    });
    (document.getElementById('map-pois-toggle') as HTMLInputElement).addEventListener('change', (event) =>
        pois.setVisible((event.target as HTMLInputElement).checked),
    );
    searchResultsToggle.addEventListener('change', () =>
        searchResultsToggle.checked ? searchViewport() : searchResults.clear(),
    );

    applyCategoryIcons();
    await searchViewport();
    map.mapLibreMap.on('moveend', searchViewport);
})();

Every category’s icon changes at once on the POIs the map style draws and on the places a search found, from one list of images.

  • POIsModule takes icon.categoryIcons and redraws the style’s own POI layer with it, leaving every unlisted category its map icon.
  • PlacesModule takes the same list, matched by map category, so RESTAURANT also reaches a searched ITALIAN_RESTAURANT.
  • Each icon is an SVG string that places a category’s glyph file inside a shape outline, so any glyph goes with any shape.
  • One offsetY per entry stands a pin’s tip on its place under both modules, since each draws the icons centred.

Related examples