Place styling
The Places module draws its places in a look of its own: the marker type, colour,
icon and label settings below, each plain-valued one a knob in placesKnobCatalogue.
Two things from outside reach these layers: the map's accent,
for markers given no colour, and the knobs that restyle the style's POIs, for the marker types that
borrow their look.
Marker type
markerType sets the kind of marker every place is drawn with, at creation or at runtime:
pin(default) — teardrop pins.circle-icon— circles with the category icon centred.base-map— the style's own POI look, throughmain,selectedandmicrolayers, so places blend in with the map's POIs.- Hide
mainto draw them through the smallmicrodots alone, for dense results.
- Hide
pin-clustered— pins that merge into a counted cluster while zoomed out (Clusters).
const placesModule = await PlacesModule.create(map, { markerType: 'base-map' });
placesModule.updateConfig({ markerType: 'circle-icon' });
// Micro dots only: `base-map` with a hidden `main` layer
const microPlaces = await PlacesModule.create(map, {
markerType: 'base-map',
layers: { main: { layout: { visibility: 'none' } } },
});
base-map, circle-icon and pin-clustered borrow the look of the style's POIs, so the knobs that
restyle those POIs restyle them too; Borrowed looks lists which per
marker type.
Clusters
Under the pin-clustered marker type, cluster sets how places cluster and how the count badge
looks; every other marker type ignores it. badge styles the badge of single-category and
mixed-category clusters together, and source takes MapLibre's cluster options:
const placesModule = await PlacesModule.create(map, {
markerType: 'pin-clustered',
// The per-place value the cluster aggregates
extraFeatureProps: {
connectorCount: (place) =>
place.properties.chargingPark?.connectors.reduce((total, { count }) => total + count, 0) ?? 0,
},
cluster: {
badge: { color: '#3f9cd9', radius: { single: 13, mixed: 18 } },
source: {
clusterRadius: 100, // default 60
clusterMaxZoom: 14, // default 17
// Total charging points across each cluster
clusterProperties: { connectors: ['+', ['get', 'connectorCount']] },
},
},
// An aggregated value lands on the cluster feature's properties
layers: { clusterCount: { layout: { 'text-field': ['get', 'connectors'] } } },
});
- A
layersoverride ofclusterBadge,clusterCount,clusterMixedBadgeorclusterMixedCountwins overbadgefor the paint and layout properties it names, and keeps the rest. badge.labelsets the count'scolorandsize.- Changing
sourceat runtime recreates the places source, and shows its places again. cluster: trueis not settable, since the marker type switches clustering on, and theclusterBaseMapIconIDskey ofclusterPropertiesis reserved for the module's own aggregator.
Colour
color is the colour of the module's own drawing: the default pin fill, the label of a hovered
or clicked place, connection
lines and entry points. Connection labels take a darker shade of it on a light
map and a lighter one on a dark map; entry points take the darker one on a light map.
const placesModule = await PlacesModule.create(map, { color: '#2A9D8F' });
- A more specific setting wins for its element:
icon.default.style.fillColorfor the pin,label.coloror alayers.*paint override for a label or layer. - Unset, it is the map's accent, and the markers repaint when the accent changes. Once set, an accent change leaves them as they are.
- Cluster badges do not take it:
cluster.badge.colorsets them.
Label styling
label sets the content and look of the labels next to the markers:
import { PlacesModule, type PlaceLabelConfig } from '@tomtom-org/maps-sdk/map';
// POI names red, other places blue
const label: PlaceLabelConfig = {
title: (place) => place.properties.poi?.name ?? place.properties.address.freeformAddress,
color: ['match', ['get', 'type'], 'POI', '#D32F2F', '#1976D2'],
haloWidth: 2,
};
const placesModule = await PlacesModule.create(map, { label });
// `label` replaces the whole label: spread the current one to change a single field
placesModule.updateConfig({ label: { ...placesModule.getConfig()?.label, opacity: 0.8 } });
PlaceLabelConfigtakes the label settings shared across modules (size,color,haloColor,haloWidth,opacityandfont), plus the place's owntitleandoffset.- Unset, they keep the marker type's label: under
base-mapthe style's POI labels, otherwise dark text on a light halo, reversed on a dark map. A hovered or clicked place's label takescolor. - Set labels here, not with MapLibre's
setPaintPropertyon the module's layers: a style change and a marker type change rebuild those layers from the configuration, dropping what was written on them. Atitlecomputed from each place has no MapLibre equivalent. - A
layers.*override, such aslayers.main.layout['text-size'], wins overlabelfor the properties it names. Changing a setting covers howupdateConfigmerges.
Icon styling
icon.categoryIcons draws the places of a category with an image of your own, under every marker
type; places in other categories keep the marker type's icon.
import { PlacesModule, type PlaceIconConfig } from '@tomtom-org/maps-sdk/map';
const icon: PlaceIconConfig = {
categoryIcons: [
{ id: 'RESTAURANT', image: '/icons/food.png', pixelRatio: 2 },
// A pin whose tip sits 10 pixels below its centre
{ id: 'HOTEL_OR_MOTEL', image: '/icons/hotel.png', pixelRatio: 2, offsetY: -10 },
],
};
const placesModule = await PlacesModule.create(map, { markerType: 'pin', icon });
- A place takes the entry for its own category, else the first entry for the map category its own
falls in:
RESTAURANTreaches anITALIAN_RESTAURANTa search returns, andCAFEaCAFE_PUB.- That broader match takes only entries with an
imageand noavailabilityLevel, the ones the POIs module applies, so one list restyles these places and the map's own POIs alike.
- That broader match takes only entries with an
offsetXandoffsetYshift the image from its coordinate, in pixels. An entry naming a sprite icon byidalone ignores them. Route charging stop icons take the same two fields.icon.defaultis the pin drawn for places without a matched category:stylerecolours it, andimagereplaces it, after which neitherstylenor the image's offsets apply.icon.mappingpicks the icon from your own data (Your own data).
Extra properties
extraFeatureProps adds properties to every place feature, for layers expressions to read with
['get', '<name>'] and for event handlers:
placesModule.updateConfig({
extraFeatureProps: {
firstCategory: (place) => place.properties.poi?.categories?.[0],
brand: (place) => place.properties.poi?.brands?.[0] ?? '',
source: 'search',
},
});
An entry is a fixed JSON value or a callback that receives a Place and returns one. When the
places you show carry a narrower shape, such as EV charging stations with availability, name it on
the config, or as the type parameter of applyConfig / updateConfig, and the callbacks are typed
for it, along with label.title and icon.mapping:
import type { EVChargingStationWithAvailabilityPlaceProps } from '@tomtom-org/maps-sdk/core';
import type { PlacesModuleConfig } from '@tomtom-org/maps-sdk/map';
const config: PlacesModuleConfig<EVChargingStationWithAvailabilityPlaceProps> = {
extraFeatureProps: {
availableShare: (place) => {
const { count, statusCounts } = place.properties.chargingPark.availability.chargingPointAvailability;
return count ? (statusCounts.Available ?? 0) / count : 0;
},
},
};
Layer positioning
By default the markers sit above the map's layers, and the connection lines below its labels.
beforeLayerConfig (PlacesBeforeLayerConfig)
moves them: one target for everything the module draws, 'top' or a key of mapStyleLayerIDs, or a
record keyed by part, plus all for the parts it leaves out. The parts are named after the
event scopes:
places— the markers and their labels, selected places, clusters with their badges and counts, and entry points.- The small POI dots of the
base-mapandpin-clusteredmarker types sit below the map's place labels by default; a target puts them right under the markers.
- The small POI dots of the
connections— the lines drawn byshowConnectionsand their labels.
// The markers under the map's labels; the connections stay where they are
placesModule.updateConfig({ beforeLayerConfig: { places: 'lowestLabel' } });
- Each part keeps its own layers in order wherever it goes; given the same target, the connections stay under the markers.
applyConfigorupdateConfigrestacks them at runtime, andresetConfig()puts them back.- A target the style lacks, such as
lowestRoadLineon the satellite style, puts the part on top.
Every setting as data
placesKnobCatalogue lists every plain-valued display setting of the module with its kind, its
range or options and, where one value holds everywhere, its default. Each id is the setting's path
in the config, and the catalogue reads with no map loaded, so a UI can build its controls from it:
import { placesKnobCatalogue } from '@tomtom-org/maps-sdk/map';
const badgeKnobs = placesKnobCatalogue.filter(({ id }) => id.startsWith('cluster.badge.'));
// [{ id: 'cluster.badge.color', kind: 'color', description: '…', default: '#000000' }, …,
// { id: 'cluster.badge.radius.single', kind: 'number', description: '…', range: { min: 4, max: 32, step: 1, bounds: 'hard-min' }, default: 11 }, …]
| Knob | Kind | Default |
|---|---|---|
markerType |
enum | 'pin' |
color |
color | the map's accent |
visible |
toggle | true |
icon.default.style.fillColor |
color | color |
icon.default.style.outlineColor |
color | |
icon.default.style.outlineOpacity |
number | |
label.size |
number | 14 to 16 pixels by zoom |
label.color |
color | by the map's light/dark theme |
label.haloColor |
color | by the map's light/dark theme |
label.haloWidth |
number | 1 to 1.5 pixels by zoom |
label.opacity |
number | |
label.offset |
number | from the icon's size |
label.font |
enums | Noto-Bold, or the style's under base-map |
cluster.badge.color |
color | '#000000' |
cluster.badge.outlineColor |
color | '#FFFFFF' |
cluster.badge.outlineWidth |
number | 1.5 |
cluster.badge.label.color |
color | '#FFFFFF' |
cluster.badge.label.size |
number | 12 |
cluster.badge.radius.single |
number | 11 |
cluster.badge.radius.mixed |
number | 15 |
cluster.badge.offset |
offset | [12, -14] |
cluster.source.clusterRadius |
number | 60 |
cluster.source.clusterMaxZoom |
number | 17 |
cluster.source.clusterMinPoints |
number | MapLibre's, 2 |
evAvailability.visible |
toggle | false |
evAvailability.threshold |
number | 0.3 |
entryPoints.showFor |
enum | 'selected' |
entryPoints.minZoom |
number | 15 |
connections.color |
color | the module's color, else the map's accent, else by the map's light/dark theme |
connections.label.color |
color | a shade of the line's colour for the map's light/dark theme |
connections.label.haloColor |
color | by the map's light/dark theme |
- A default in words is not in the entry: the accent, the theme, the zoom or the marker type decides it.
setKnob,getKnobandresetKnobset, read and reset one by its id (Setting a knob by its id).label.fontoffersmapFonts; its order matters, since the first font the map has wins.cluster.badge.offsetshifts a single-category cluster's badge, in pixels; mixed-category badges stay centred.placesKnobIdslists the ids in catalogue order,PlacesKnobIdis their union, andPlacesKnobValueOf<ID>the plain value one takes. The MapLibre expression a label or badge setting also accepts is not a knob value.- Not in it: the label's
title, the cluster source'sclusterProperties, the EV availability'sformatText, theiconimages and mapping, the connections'label,layers,beforeLayerConfig,extraFeaturePropsandevents, which take callbacks, images, structured or MapLibre values. - Entry points draw nothing until
entryPointsis set, EV availability untilevAvailability.visibleistrue, andclusterapplies underpin-clusteredonly. - Its entries share their shape,
KnobEntry, with every module's catalogue.
Related guides and examples
Related guides
- Places module - Showing places, events and the data shown
- Connections and entry points - The lines and entrances drawn around places
- Map modules - The knob kinds every catalogue shares
- POIs - The same
categoryIconslist on the map's own POIs
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 - Places clustering playground - How places cluster, how the count badge looks and what it counts
- Borrowed looks -
base-mapplaces restyled live by the knobs that restyle the style's POIs