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

Discover places (fuzzy/geometry/along-route)

The TomTom Maps SDK provides three powerful search services that help you find places, addresses, and points of interest with different search strategies optimized for various use cases. Each search type is designed to handle specific user interaction patterns and application requirements.

Available search types

Fuzzy search

Comprehensive search that handles typos, variations in queries, and natural language input. Fuzzy search is robust and forgiving, making it ideal for situations where users might not know exact spellings or when dealing with international locations.

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

const results = await discoverPlaces({
    query: 'coffe shop near dam square',  // Handles typos and variations
    geoBias: { position: [4.9041, 52.3676] },
});

Advanced Capabilities:

Ideal Applications:

Geometry search

Find places within specific geographic boundaries or custom areas. Geometry search enables location discovery within precisely defined regions, making it perfect for area-specific applications and location-based services.

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

// Search within a circle
const results = await discoverPlaces({
    query: 'restaurants',
    geometries: [{
        type: 'Circle',
        coordinates: [4.9041, 52.3676], // [longitude, latitude]
        radius: 5000                     // meters
    }]
});

// Search within a GeoJSON polygon
const results = await discoverPlaces({
    query: 'museums',
    geometries: [{
        type: 'Polygon',
        coordinates: [[[4.7, 52.2], [5.1, 52.2], [5.1, 52.5], [4.7, 52.5], [4.7, 52.2]]]
    }]
});

discoverPlaces() runs geometry search when geometries is provided, along-route search when route is, and fuzzy search otherwise. It is the one function for all three: there is no separate export per search.

Geometric Search Options:

Perfect For:

Along-route search

Discover places of interest along a planned route, filtered to a maximum detour time. Along-route search is ideal for trip planning scenarios where users want to find fuel stops, restaurants, or EV charging stations without significantly deviating from their journey.

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

// Pass a GeoJSON LineString directly
const results = await discoverPlaces({
    route: {
        type: 'LineString',
        coordinates: [
            [4.9, 52.37],  // Amsterdam [longitude, latitude]
            [4.95, 52.28],
            [5.0, 52.15],
            [5.1, 52.09],  // Utrecht
        ]
    },
    maxDetourTimeSeconds: 600,
    filters: { poiCategories: ['CHARGING_LOCATION'] },
    limit: 10,
});

// Or pass a Route Feature returned by calculateRoute
import { calculateRoute } from '@tomtom-org/maps-sdk/services';

const routes = await calculateRoute({ locations: [amsterdamWaypoint, utrechtWaypoint] });
const results = await discoverPlaces({
    route: routes.features[0],
    maxDetourTimeSeconds: 600,
    query: 'cafe',
});

The discoverPlaces() function dispatches to along-route search when a route is provided.

Key Features:

Ideal Applications:

Search as you type

For a search box, use getSearchSuggestions(): one call returns places together with brand and category suggestions.

Parameter reference

Common parameters

These parameters apply to fuzzy search, geometry search, and along-route search:

Parameter Type Description
query string (optional) Search query text. Omit when searching purely by filters or geometry.
limit number Maximum results to return (1–100, default: 10).
filters PlaceFilters (SearchFilters on fuzzy search) Everything that narrows which places come back, in one object. See Filters.
openingHours OpeningHoursMode Include opening hours in results (e.g. 'nextSevenDays').
timeZone TimeZoneRequest Include timezone info. Use 'iana' to receive IANA timezone IDs (e.g. 'Europe/Amsterdam').
relatedPois RelatedPoisRequest Include related POIs in results. Options: 'off' (default), 'child', 'parent', 'all'.
geopoliticalView GeopoliticalView Geopolitical view: which country a place in a disputed territory belongs to. Defaults to the global geopoliticalView; search does not serve 'KR' and 'US'. See Geopolitical views.
mapcodes MapcodeType[] Request mapcode formats in results: 'Local', 'International', 'Alternative'.
extendedPostalCodesFor SearchIndexType[] Include extended postal codes for specific indexes (default: all except 'Geo').

Filters

Every option that drops results lives under filters. geoBias stays separate: it is where to look, not what to accept.

Member Type Description
poiCategories POICategory[] Restrict to POI categories. Use the getPOICategories service to discover values. See POI Categories.
poiCategoryQuery string | string[] discoverPlaces only. Categories in words, resolved to poiCategories for you; never together with them. See Naming the category in words.
poiBrands string[] Restrict by brand name (case-sensitive). Example: ['Starbucks', 'Costa Coffee'].
indexes SearchIndexType[] Which indexes to search: 'Geo', 'PAD', 'Addr', 'Str', 'XStr', 'POI'. Defaults to all.
geographyTypes GeographyType[] Restrict Geography results to specific types (e.g. ['Municipality', 'Neighbourhood']).
connectors ConnectorType[] Restrict EV charging stations by connector type (e.g. ['IEC62196Type2CCS', 'Chademo']).
fuelTypes Fuel[] Restrict fuel stations by fuel type (e.g. ['Petrol', 'Diesel', 'E85']).
minPowerKW / maxPowerKW number EV charging power bounds, in kilowatts.
countries string[] Restrict to countries, as ISO 3166-1 alpha-2 or alpha-3 codes (e.g. ['NL', 'DE']). Fuzzy search and geocoding only.
import { discoverPlaces } from '@tomtom-org/maps-sdk/services';
import type { SearchFilters } from '@tomtom-org/maps-sdk/services';

const dieselStations: SearchFilters = {
    countries: ['NL', 'BE'],
    poiBrands: ['Shell', 'BP'],
    fuelTypes: ['Diesel'],
};

const results = await discoverPlaces({
    filters: dieselStations,
    geoBias: { position: [4.9041, 52.3676], radiusMeters: 20000 },
});

Geographic bias

Search and geocoding take a single geoBias option, which is either a point or a rectangle — the two cannot be combined, so the compiler rejects passing both:

// Around a point: biases ranking, and confines results when radiusMeters is given
geoBias: { position: [4.9041, 52.3676], radiusMeters: 2000 }

// Inside a rectangle: [minLng, minLat, maxLng, maxLat], or any GeoJSON a bbox derives from
geoBias: { boundingBox: map.getBBox() }

Omit geoBias entirely to search without a geographic bias.

Fuzzy search parameters

In addition to all common parameters, fuzzy search supports:

Parameter Type Description
geoBias GeoBias Where to look — { position, radiusMeters? } or { boundingBox }, never both. See Geographic bias.
typeahead boolean Treat the query as partial, for search-as-you-type (default: false).
cursor string Continues a previous search. Pass back nextCursor from the previous response, unchanged, alongside the same parameters. Its contents are opaque — do not build or parse one. See Pagination.
minFuzzyLevel number Minimum fuzziness level (1–4, default: 1). Lower values require closer matches.
maxFuzzyLevel number Maximum fuzziness level (1–4, default: 2). Higher values tolerate more typos.

Geometry search parameters

In addition to all common parameters, geometry search requires:

Parameter Type Description
geometries SearchGeometryInput[] Required. One or more geometries to search within. Supports Circle, Polygon, and MultiPolygon.

Note: Geometry search does not support geoBias. The geometry shapes define the search area.

Along-route search parameters

Along-route search requires route and maxDetourTimeSeconds. All common parameters also apply. geoBias is not available — the route geometry defines where to search. Parameters from fuzzy/geometry search such as geometries and cursor also do not apply.

Parameter Type Description
route LineString | Position[] | Route Required. The route to search along. Accepts a GeoJSON LineString, an array of [longitude, latitude] positions, or a Route Feature returned by calculateRoute.
maxDetourTimeSeconds number Required. Maximum allowed detour time in seconds (positive integer).
sortBy 'detourTime' | 'detourOffset' Sort results by minimum detour time or by position along the route.

POI categories

Use filters.poiCategories to filter results to specific types of places. Each value must be a POICategory enum constant. Use the getPOICategories service to search and browse available categories by name or synonym, then pass the resulting code values directly here.

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

// Filter to a known category
const italian = await discoverPlaces({
    geoBias: { position: [4.9041, 52.3676], radiusMeters: 1000 },
    filters: { poiCategories: ['ITALIAN_RESTAURANT'] }
});

// Discover categories first, then search
const restaurantCodes = await getPOICategoryCodes({ filters: ['restaurant'] });
const restaurants = await discoverPlaces({
    geoBias: { position: [4.9041, 52.3676], radiusMeters: 1000 },
    filters: { poiCategories: restaurantCodes }
});

// The same in one call: name the category in words
const sushi = await discoverPlaces({
    geoBias: { position: [4.9041, 52.3676], radiusMeters: 1000 },
    filters: { poiCategoryQuery: 'sushi' }
});

// EV charging with connector and power filtering
const evChargers = await discoverPlaces({
    geoBias: { position: [4.9041, 52.3676], radiusMeters: 5000 },
    filters: {
        poiCategories: ['CHARGING_LOCATION'],
        connectors: ['IEC62196Type2CCS', 'Tesla'],
        minPowerKW: 50,
    }
});

Example category values: ITALIAN_RESTAURANT, FUEL_STATION, HOTEL, PHARMACY, MUSEUM, PARKING_GARAGE, CHARGING_LOCATION, MALL. See the POICategory type for the full list, or use the getPOICategories service to search and explore them at runtime.

Naming the category in words — poiCategoryQuery

filters.poiCategoryQuery takes categories as a person says them — 'sushi', or ['gym', 'parking'] — and keeps the POI categories they name. discoverPlaces takes it with every search variant: near a geoBias, inside geometries or along a route.

For a category the user picks while typing, search suggestions return it as a category suggestion whose refine() filters on its code, with no lookup.

In the demo below, a word in the chosen language searches its category inside the Paris outline:

Working with search results

Search results follow a GeoJSON FeatureCollection structure. Each feature is a GeoJSON Feature<Point> with place data in properties:

const results = await discoverPlaces({
    query: 'coffee',
    geoBias: { position: [4.9041, 52.3676] },
});

results.features.forEach(place => {
    console.log(place.properties.poi?.name);              // Business name (POI results)
    console.log(place.properties.address.freeformAddress);// Formatted address
    console.log(place.geometry.coordinates);              // [longitude, latitude]
    console.log(place.properties.poi?.categories);        // POICategory[] (e.g. ['CAFE'])
    console.log(place.properties.poi?.localizedCategories); // string[] (e.g. ['café'])
    console.log(place.properties.score);                  // Relevance score
    console.log(place.properties.distance);               // Distance in meters (if position was given)
});

To retrieve only the first result, use discoverOnePlace() — it throws if no results are found:

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

const place = await discoverOnePlace('Eiffel Tower');
console.log(place.properties.poi?.name); // 'Eiffel Tower'

// The params form takes everything `discoverPlaces` does except `limit`
const nearby = await discoverOnePlace({ query: 'coffee', geoBias: { position: [2.2945, 48.8584] } });

Pagination

A response carries nextCursor in its properties when more results are available. Pass it back as cursor, alongside the same parameters, to get the following page:

let response = await discoverPlaces({ query: 'cafe', limit: 20 });
const all = [...response.features];

while (response.properties?.nextCursor) {
    response = await discoverPlaces({ query: 'cafe', limit: 20, cursor: response.properties?.nextCursor });
    all.push(...response.features);
}

nextCursor is absent when there is no next page, so it doubles as the loop terminator.

The loop spends a request per page up front. To fetch one page per Load more click instead, keep the request and the last nextCursor between clicks, as the Search playground does:

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

const request: FuzzySearchParams = { query: 'cafe', geoBias: { position: [4.9041, 52.3676] }, limit: 20 };
let nextCursor: string | undefined;

const loadPage = async (cursor?: string) => {
    loadMoreButton.disabled = true; // a second click before the page lands would fetch it twice
    const page = await discoverPlaces({ ...request, cursor });
    appendToResultList(page.features);
    nextCursor = page.properties?.nextCursor;
    loadMoreButton.disabled = !nextCursor;
};

await loadPage();
loadMoreButton.onclick = () => loadPage(nextCursor);

Fields that may be absent

A few result and summary fields are not guaranteed. The type is the contract: where a property is optional, handle its absence rather than assuming a value.

Field What it carries
places.properties.totalResults The total number of matches, beyond the page returned
places.properties.fuzzyLevel How loosely the query was matched
places.properties.geoBias The position results were biased towards
places.properties.queryIntent (fuzzy search) Detection of coordinates, what3words codes and "near X" phrasings
place.properties.score A relevance score, for thresholding and merging results
places.bbox A bounding box, on results that cover an area

The pattern to follow — guard, then use:

const results = await discoverPlaces({ query: 'pizza', limit: 10 });

if (results.properties?.totalResults !== undefined) {
    const { numResults, totalResults } = results.properties;
    console.log(`Showing ${numResults} of ${totalResults}`);
}

Treating fuzzyLevel as 0 when it is missing is the one to avoid: absent means "not reported", not "matched exactly".

Examples

Find bus stops in an area (Node.js)

Use geoBias: { boundingBox } to search for all bus stops within a rectangular geographic area. This example searches the Amsterdam city area:

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

const results = await discoverPlaces({
    filters: { poiCategories: ['BUS_STOP'] },
    geoBias: { boundingBox: [4.72, 52.27, 5.07, 52.43] }, // [minLng, minLat, maxLng, maxLat]
    limit: 100
});

results.features.forEach(stop => {
    const [lng, lat] = stop.geometry.coordinates;
    console.log(`${stop.properties.poi?.name} — ${lng}, ${lat}`);
});

Integration with map visualization

Search results can be directly integrated with the TomTom Maps SDK for immediate visual representation:

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

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

const results = await discoverPlaces({
    query: 'museums',
    geoBias: { position: [4.9041, 52.3676] },
});

// Display search results as markers on the map
await placesModule.show(results);

Searching again as the map moves

A search re-run on every map move can answer out of order, and a slow answer for the previous viewport would paint over the current one. Run it through createLatestRequest, as the places customization playground does:

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

const latestSearch = createLatestRequest();

map.mapLibreMap.on('moveend', async () => {
    const search = await latestSearch.run((signal) =>
        discoverPlaces({
            filters: { poiCategories: ['CAFE_PUB'] },
            geoBias: { boundingBox: map.getBBox() },
            signal
        })
    );
    if (search.current) await placesModule.show(search.value);
});

See Keeping only the latest request for what a retired search resolves to.

To keep POI categories on the map as it moves without writing this yourself, use the viewport places plugin.

Performance optimization

For optimal performance in production applications, consider these best practices:

Search Strategy Selection:

Result Limiting:

For complete search implementation examples and advanced usage patterns, go to the examples gallery.

Related guides and examples

Map integration

Related examples

Related services