Places module
The Places Module allows you to display and interact with places on your TomTom map. You can add search results, custom locations, and handle user interactions.
What this module owns: the place markers and labels it draws, and how they look — the marker type, colour, icon and label configuration in Place styling. A place that joins a route becomes one of the route's waypoints, drawn by the Routing module instead. It never restyles the base map underneath, which belongs to the Styling module. Markers given no colour read the map's accent as their default, and the marker types that borrow the look of the style's own POIs follow the knobs that restyle them (Borrowed looks). See Who owns what.
This guide covers showing places, events and the data shown. Two features of the module have a guide of their own:
| 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';
// Initialize map
const map = new TomTomMap({ mapLibre: { container: 'map' } });
// Initialize Places module with default configuration
const placesModule = await PlacesModule.create(map);
Displaying places
Display one or multiple places on the map:
import { discoverPlaces } from '@tomtom-org/maps-sdk/services';
// Show single 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' }
}
});
// Show multiple places
await placesModule.show([place1, place2, place3]);
const searchResults = await discoverPlaces({
query: 'restaurants',
geoBias: { position: [4.9041, 52.3676] }, // Amsterdam
limit: 10
});
// Show search results
await placesModule.show(searchResults);
// Remove all places from the map:
await placesModule.clear();
What show() takes
show() takes a place, an array of places or a Places collection as the service returns it:
search, geocoding, reverse geocoding, place details and EV availability results, or your own GeoJSON
points. There are no fields to map first. Three 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 |
evChargingStationsAvailability |
Availability alone, no place | getPlacesWithEVAvailability, over the stations a search found |
preparePlacesForDisplay is
what show() does to each place before drawing it. Call it to see the properties your
layer 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);
- Every property of the place is kept.
idis copied intoproperties, andtitle,iconID, the extra properties and, underbase-mapandpin-clustered,categoryandgroupare added. buildPlaceTitleis the default label — the POI name, else the address — to fall back on in alabel.titlefunction of your own.toPlacesturns any of the three shapesshow()accepts into one collection, such as the one ashown-featureshandler receives.- An
iconIDnaming an image the module adds, the default pin or a custom icon, is the first module's name for it.getShown()returns what a given module drew.
Your own data
Your own GeoJSON points go to show() as they are, and draw like the places a search returns. Each
keeps its own properties, which label.title, layer overrides and click handlers read back. Name
their shape on create to have the label.title and icon.mapping callbacks typed for it:
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);
- Draw them through the module, not a layer of your own fed by
preparePlacesForDisplay. The module adds the images its icons name, and brings the events, clustering and restoration after a style change with it. icon.mappinggives every point the base map's icon and label colour for the POI category it returns, so your points sit among the style's POIs as if they were its own. A point it returnsundefinedfor keeps the icon it would have without a mapping. Route waypoints take the samemappingandlabel.title.- The default label is
poi.name, elseaddress.freeformAddress. Points carrying neither need alabel.titlefunction.
Showing and hiding
setVisible hides and shows everything the module draws — places, connections
and entry points — while keeping its data. visible: false at creation starts it hidden.
const placesModule = await PlacesModule.create(map, { visible: false });
await placesModule.show(searchResults); // loaded, not drawn
placesModule.setVisible(true);
placesModule.isVisible(); // true
show()andclear()never change it, and it survives a style change.- Showing the module leaves hidden the layers its marker type does not use, and any layer you hid
through
layers.*.layout.visibility, such asmainin micro-only rendering.
Events
User events
Handle user interactions with places:
// Click events
placesModule.events.on('click', (feature, lngLat) => {
console.log('Click coordinates:', lngLat);
console.log('Place title:', feature.properties.title);
console.log('Place coordinates:', feature.geometry.coordinates);
});
// Hover events
placesModule.events.on('hover', (feature, lngLat) => {
console.log('Hovering over:', feature.properties.poi?.name);
});
// Remove event listeners
placesModule.events.off('click', handler);
events covers every surface the module draws. Three named scopes narrow it: events.places for the
pins alone — typed as Place, which is what you want in TypeScript — events.connections for
the lines and labels drawn by showConnections,
and events.entryPoints for the entry points:
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: 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);
- A card is application chrome. What it shows, how it looks and how it closes are your app's,
so the SDK leaves them to it: a MapLibre
Popup, or a panel of your own beside the map. - A place already has its anchor. The click hands over the place, whose coordinates a
Popupsits on. The Routing module draws its own route summary bubbles because a route has no single point to anchor one to: placing them along the line takes the module. - For the full details of a clicked place, such as opening hours, fetch them in the handler with
getPlaceDetails.
Programmatic event states
Put, clean and read the states of places the way you do on every module that draws your data — see
event states. They act on the places, whichever marker type draws
them, clustered or not; events.connections and events.entryPoints carry their own.
// Highlight the first place as if clicked
placesModule.putEventState({ index: 0, state: 'click' });
const { click: [selectedID] = [], hover = [] } = placesModule.getEventStates();
placesModule.cleanEventStates({ 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.putEventState({ id: selectedID, state: 'click' });
Multiple module instances
You can create multiple independent instances of the Places module, each with its own configuration and data. This is useful for displaying different sets of places with distinct styling or behavior.
// Create separate instances for different purposes
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 }]
}
});
// Each instance manages its own places independently
await restaurantPlaces.show(restaurantResults);
await hotelPlaces.show(hotelResults);
Reading shown data
Use getShown() to retrieve the places currently displayed on the map: 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
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: they travel on each feature's properties.eventState |
Dropped |
clear() |
Removed | Removed | Dropped |
applyConfig, updateConfig, setKnob |
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, 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.putEventState({ id: selectedID, state: 'click' }); // does nothing if the id is gone
- A connection endpoint given as a place id resolves against the places shown each time the
connections are drawn; one that matches no shown place is skipped, yet stays in
getShown().connections. - Since
show()andclear()drop the connections, none outlives the places it referred to, and none comes back when those places are shown again: callshowConnections()aftershow().
API reference
For complete documentation of all Places Module properties, methods, and types, see the PlacesModule API Reference.
Related guides and examples
Services integration
- Search Services - Find places and POIs to display using the Places Module with fuzzy, geometry and along-route search
- Geocoding - Convert addresses to coordinates for precise place positioning on the map
- Reverse Geocoding - Get address information for places and display them with rich location context
Related examples
- Places customization playground - Advanced place styling and customization techniques, with the pins drawn over or under the map's labels
- Places default icon styling - One
colorper module instance, with the default pin's outline set alongside - POI category icons playground - One
categoryIconslist restyling searched places and the map's own POIs together - Place connection lines - Labelled lines from a charging station to the cafés around it, in a colour of their own
- Place entry points playground - When and how the entrances of places are drawn, and what clicking one returns
- Places clustering playground - How places cluster, how the count badge looks and what it counts
- Places in Geometry - Display places within specific geographic boundaries
- Search playground - Search for places and display results interactively
- Places by Category in Words - A category named in words, in five languages, searched inside a city outline
- Pin Interaction - Interactive place markers with click events and popups
- Bring your own data: Yorkshire heatmap - An open-data GeoJSON file drawn as base-map places, with
icon.mappingand alabel.titleof its own - Borrowed looks -
base-mapplaces restyled live by the knobs that restyle the style's POIs
Related map modules
- Place styling - Marker types, colour, labels, icons and the knob catalogue
- Connections and entry points - The lines and entrances drawn around places
- POIs - Filter the map's own POIs by category, and draw a category with an icon of your own
- User Interaction Events - Handle user interactions with places on the map