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:
- Tolerates spelling mistakes and variations in input
- Supports natural language queries for intuitive user interaction
- Category and POI search with intelligent matching
- Flexible radius-based or bounding box search options
Ideal Applications:
- General search functionality where user input quality varies
- International applications with diverse language inputs
- Voice-to-text search implementations
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]]]
}]
});
The discoverPlaces() function automatically dispatches to geometry search when geometries is provided, and to fuzzy search otherwise.
Geometric Search Options:
- Circle: Custom radius around a point (
type: 'Circle',coordinates,radius) - Polygon: GeoJSON polygon for custom area boundaries
- MultiPolygon: Multiple non-contiguous areas in a single query
Perfect For:
- Location discovery within specific neighborhoods or districts
- Delivery and service area applications
- Tourism and local business discovery
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:
- Detour-time filtering — only surfaces places reachable within the allowed detour
- Flexible route input — accepts a
LineString,Position[], or aRouteFeature fromcalculateRoute - Category and text search — combine
filters.poiCategoriesandqueryas needed - Sort control — order results by detour time or position along the route
Ideal Applications:
- EV trip planning with charging stop discovery
- Road-trip itinerary building
- Fleet routing with service stop optimization
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'. |
view |
View |
Geopolitical view for disputed territories. Options: 'Unified', 'AR', 'IN', 'PK', 'IL', 'MA', 'RU', 'TR', 'CN', 'TW', 'RS'. All eleven are accepted by the backend and change how disputed territories are attributed. |
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. |
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. |
- A filter excludes. A place that does not match is dropped, not ranked lower.
- Values within one member are OR'd; members are AND'd.
poiBrands: ['Shell', 'BP']withfuelTypes: ['Diesel']keeps stations of either brand that sell diesel. - The type follows the service. Fuzzy search takes
SearchFilters(PlaceFilters & GeographicFilters); geometry and along-route search takePlaceFilters, sofilters.countriesbesidegeometriesorrouteis a compile error. Geocoding takescountriesandgeographyTypes.
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 }
});
// 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.
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);
- The cursor is opaque. Hand it back exactly as you received it; its contents are not part of
the contract, so the pagination mechanism can change without breaking your code. Passing a value
the SDK did not produce throws an
SDKError. - A cursor continues one search. A new query or different
filtersstarts a new search: leavecursorout. nextCursordecides whether a next page exists, nottotalResults. The backend serves no page starting past result 1900, so beyond thatnextCursoris absent even whentotalResultsreports more matches. An empty page ends pagination too.- Fuzzy search and geocoding only. Geometry and along-route search return a single page, with
no
nextCursor, anddiscoverPlacesrejectscursorbesidegeometriesorrouteat compile time.geocodepages the same way.
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:
- Use
getSearchSuggestions()for search boxes - Use fuzzy search for comprehensive location finding
- Use geometry search for area-specific discovery
Result Limiting:
- Set appropriate
limitvalues based on UI requirements - Use
cursorfor pagination of large result sets - Cache frequent search results for faster response times
For complete search implementation examples and advanced usage patterns, go to the examples gallery.
Related guides and examples
Map integration
- Places Module - Display search results as interactive places on the map with customizable styling and user interactions
- POIs - Advanced point-of-interest visualization and filtering for search results
Related examples
- Along Route Search (Node.js) - Find EV charging stations along a planned route using Node.js
- Along Route Search Playground - Interactive along-route search with query input and POI category checkboxes
- Search playground - Real-time search-as-you-type, and a picked brand or category paged in with
nextCursor - Geometry Search Playground - Search within custom geographic boundaries and areas
- Geometry Search with POI Categories - Category-filtered search within specific regions
- Places customization playground - Customizing the appearance and behavior of search result markers
- Search Places in Geometry - Specialized search example for finding places in a defined area
- Search Places nearby Location - Specialized search example for finding places nearby a location
Related services
- Geocoding - Convert search result addresses to precise coordinates for enhanced accuracy
- Reverse Geocoding - Get address information for search result coordinates