This is the documentation of version 1.0.0-rc.0. Go to the latest version, 1.0.0-rc.5.

Places module

The Places module draws places, whether search results or your own GeoJSON points, as markers on the map, and reports the clicks and hovers on them.

The module owns the place markers and labels it draws, and how they look (Place styling). A place that joins a route becomes one of the route's waypoints, which the Routing module draws instead. The base map underneath belongs to the Styling Foundations module. Markers without a colour take its accent, and the marker types that borrow the look of the style's POIs follow the knobs that restyle them. Who owns what compares every module.

The code below searches for parking and charging around a geocoded place and draws the results. The search response goes to show unchanged. To search elsewhere, change the address or the poiCategories:

Two features of the module have their own guide:

Guide Covers
Place styling Marker types and clusters, colour, labels, icons, extra properties, layer positioning and the knob catalogue
Connections and entry points Labelled lines between places, and the entrances a place's data carries

Initialization

import { TomTomMap, PlacesModule } from '@tomtom-org/maps-sdk/map';

const map = new TomTomMap({ mapLibre: { container: 'map' } });
const placesModule = await PlacesModule.create(map);

PlacesModule.create makes a new instance on each call. See Multiple module instances.

Displaying places

import { discoverPlaces } from '@tomtom-org/maps-sdk/services';

// One place
await placesModule.show({
  id: 'custom-place-1',
  type: 'Feature',
  geometry: { type: 'Point', coordinates: [4.9041, 52.3676] },
  properties: {
    type: 'POI',
    address: { freeformAddress: 'Custom Place 1, Amsterdam' },
    poi: { name: 'Custom Place' }
  }
});

// Several places
await placesModule.show([place1, place2, place3]);

// Search results, as the service returns them
const searchResults = await discoverPlaces({
    query: 'restaurants',
    geoBias: { position: [4.9041, 52.3676] },
    limit: 10
});
await placesModule.show(searchResults);

// Remove every place
await placesModule.clear();

What show() takes

show() takes a place, an array of places or a Places collection from search, geocoding, reverse geocoding or place details, or your own GeoJSON points, with no fields to map first. Two places services return something else:

Result Not showable because Show instead
getSearchSuggestions Suggestions to choose from, without geometry A place suggestion's resolve(), or a brand or category suggestion's refine()
geometryData Polygons The Geometries module

preparePlacesForDisplay is what show() does to each place before drawing it. Call it to see the properties that layers overrides, extra properties and event handlers read:

import { preparePlacesForDisplay } from '@tomtom-org/maps-sdk/map';

const [first] = preparePlacesForDisplay(searchResults, { markerType: 'base-map' }).features;
console.log(first.properties.title, first.properties.iconID, first.properties.category);

Your own data

Pass your own GeoJSON points to show() as they are. They draw like the places a search returns. Each keeps its own properties, which label.title, layers overrides and click handlers read back. To type the label.title and icon.mapping callbacks, name your points' shape on create:

import type { CommonPlaceProps, Places } from '@tomtom-org/maps-sdk/core';
import { PlacesModule } from '@tomtom-org/maps-sdk/map';

type Store = CommonPlaceProps & { storeName: string };
const stores: Places<Store> = await (await fetch('https://your-api.com/stores.json')).json();

const placesModule = await PlacesModule.create<Store>(map, {
    markerType: 'base-map', // drawn like the map style's own POIs
    icon: { mapping: { to: 'poiCategory', fn: () => 'COFFEE_SHOP' } },
    label: { title: (place) => place.properties.storeName },
});

await placesModule.show(stores);

Showing and hiding

setVisible hides and shows everything the module draws (places, connections and entry points) and keeps its data. To start it hidden, pass visible: false at creation.

const placesModule = await PlacesModule.create(map, { visible: false });
await placesModule.show(searchResults); // loaded, not drawn

placesModule.setVisible(true);
placesModule.isVisible(); // true

Events

User events

const stopListening = placesModule.events.on('click', (feature, lngLat) => {
    console.log(feature.properties.title, lngLat);
});
placesModule.events.on('hover', (feature) => console.log(feature.properties.poi?.name));

stopListening(); // removes this handler alone
placesModule.events.off('hover'); // removes every hover handler

events covers every surface the module draws, so its handlers receive the raw MapLibre feature. Three named scopes narrow it:

placesModule.events.places.on('click', (place) => console.log(place.properties.poi?.name));
placesModule.events.connections.on('click', () => console.log('connection line clicked'));
placesModule.events.entryPoints.on('click', (entryPoint) => {
    // A point at the entrance, whether its dot or its line was hit, with its place attached
    console.log(entryPoint.properties.type, entryPoint.properties.place?.properties.title);
    // It is a valid route location
    calculateRoute({ locations: [origin, entryPoint] });
});

Result cards and popups

The module tells you which place was clicked or hovered, and does the hit-testing behind it. It draws no result card, popup or info window for a place, so open your own from the event.

import { Popup } from 'maplibre-gl';

const popup = new Popup({ closeButton: false, offset: 35 });

placesModule.events.places.on('click', (place) => {
    popup
        .setLngLat(place.geometry.coordinates)
        .setText(place.properties.poi?.name ?? place.properties.address.freeformAddress)
        .addTo(map.mapLibreMap);
});

// The popup is yours, so close it when the places change
popup.remove();
await placesModule.show(newResults);

Programmatic event states

Put, clean and read the event states of places as on every module that draws your data. They act on the places, whichever marker type draws them, clustered or not. events.connections and events.entryPoints have their own.

// Highlight the first place as if clicked
placesModule.setEventState({ index: 0, state: 'click' });

const { click: [selectedID] = [], hover = [] } = placesModule.getEventStates();

placesModule.clearEventStates({ states: ['hover'] });

A pin draws its state, and so do its entry points. A show() of new results drops the states. To keep a selection across a re-show, read it first and put it back:

const [selectedID] = placesModule.getEventStates().click ?? [];
await placesModule.show(morePlaces);
if (selectedID) placesModule.setEventState({ id: selectedID, state: 'click' });

Multiple module instances

Each PlacesModule.create call returns an independent instance with its own configuration, data, events and layers, so different sets of places can look and behave differently:

const restaurantPlaces = await PlacesModule.create(map, {
    markerType: 'pin',
    icon: {
        categoryIcons: [{ id: 'RESTAURANT', image: '/icons/restaurant.png', pixelRatio: 2 }]
    }
});

const hotelPlaces = await PlacesModule.create(map, {
    markerType: 'circle-icon',
    icon: {
        categoryIcons: [{ id: 'HOTEL_OR_MOTEL', image: '/icons/hotel.png', pixelRatio: 2 }]
    }
});

await restaurantPlaces.show(restaurantResults);
await hotelPlaces.show(hotelResults);

Reading shown data

getShown() returns the places passed to show() as drawn, each with its display properties (title, iconID…) and current eventState, and the connections passed to showConnections():

const shown = placesModule.getShown();
console.log(`Showing ${shown.places.features.length} places`);

Identity across show, clear and style changes

The table shows what happens to the shown places, their event states and their connections when you show again, clear or change the style:

Call Places Event states Connections
show(results) Replaced, never merged Dropped, even for a place shown again under the same id Dropped
show(placesModule.getShown().places) Shown again Kept, because they travel on each feature's properties.eventState Dropped
clear() Removed Removed Dropped
applyConfig, updateConfig, setKnob, resetKnob Kept Kept Kept, ids resolved again
setStyle Kept Kept Kept, ids resolved again
setStyle(style, { resetState: true }) Removed Removed Removed

So in a search, select and search again loop, put the selection back by id after the new show():

// `selectedID` is the id your app kept when the place was clicked
await placesModule.show(await discoverPlaces({ query: 'coffee', geoBias: { position } }));
placesModule.setEventState({ id: selectedID, state: 'click' }); // does nothing if the id is gone

API reference

PlacesModule lists every method, and PlacesModuleConfig every setting.

Related guides and examples

Services integration

Related examples

Related map modules