Discover places (fuzzy/geometry/along-route)
discoverPlaces() is the one
search function. Its parameters pick which of three searches runs:
| Search | Runs when | Finds |
|---|---|---|
| Along-route | route is given |
Places within a detour budget of a route |
| Geometry | geometries is given |
Places inside circles or polygons |
| Fuzzy | neither is | Places and addresses for free text, optionally near a geoBias |
There is no separate export per search. Each returns places as a GeoJSON
FeatureCollection that the Places module shows as it is.
Available search types
Fuzzy search
Fuzzy search tolerates typos, partial input and natural-language phrasing, for POIs and addresses alike:
import { discoverPlaces } from '@tomtom-org/maps-sdk/services';
const results = await discoverPlaces({
query: 'coffe shop near dam square',
geoBias: { position: [4.9041, 52.3676] },
});
Only fuzzy search takes geoBias, cursor, typeahead and
filters.countries. See Fuzzy search parameters.
Geometry search
Geometry search finds places inside one or more areas:
import { discoverPlaces } from '@tomtom-org/maps-sdk/services';
const inCircle = await discoverPlaces({
query: 'restaurants',
geometries: [{ type: 'Circle', coordinates: [4.9041, 52.3676], radius: 5000 }], // radius in metres
});
const inPolygon = 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]]],
}],
});
geometriestakes aCircle(coordinatesplusradiusin metres, an SDK extension to GeoJSON), aPolygon, aMultiPolygonor a polygonFeatureCollectionsuch as ageometryDataresult, passed as it is. You can mix several in one array.- Only a polygon's outer ring is sent to the API, so holes are ignored. The ring is sampled
down to 50 vertices, and a
MultiPolygonof more than 50 polygons keeps its 50 largest.
Along-route search
Along-route search finds places reachable within maxDetourTimeSeconds of a route, such as fuel stops, cafés or EV
chargers:
import { calculateRoute, discoverPlaces } from '@tomtom-org/maps-sdk/services';
const routes = await calculateRoute({ locations: [[4.9041, 52.3676], [5.1214, 52.0907]] });
const chargers = await discoverPlaces({
route: routes.features[0],
maxDetourTimeSeconds: 600,
filters: { poiCategories: ['CHARGING_LOCATION'] },
limit: 10,
});
const cafes = await discoverPlaces({
route: [[4.9, 52.37], [4.95, 52.28], [5.1, 52.09]], // or a GeoJSON LineString
maxDetourTimeSeconds: 300,
query: 'cafe',
sortBy: 'detourOffset',
});
Search as you type
For a search box, use getSearchSuggestions(). One call returns places
with brand and category suggestions.
Parameter reference
The full type is DiscoverPlacesParams.
An option of another search variant, such as cursor beside geometries or geoBias beside route,
is a compile error. It isn't silently dropped.
Common parameters
All three searches take these, plus the options every service call takes (language, signal,
validateRequest), covered in Request options and errors:
| Parameter | Type | Description |
|---|---|---|
query |
string (optional) |
Search text, as plain text. The SDK encodes it, so a value you already encoded is encoded again and can resolve to a different place. Omit it to search by filters or geometry alone. |
limit |
number |
Maximum results, up to 100. Default 10. |
filters |
PlaceFilters (SearchFilters on fuzzy search) |
What may come back. See Filters. |
openingHours |
OpeningHoursMode |
Adds opening hours to POIs, e.g. 'nextSevenDays'. |
timeZone |
TimeZoneRequest |
'iana' adds the POI's IANA time zone, e.g. 'Europe/Amsterdam'. |
relatedPois |
RelatedPoisRequest |
'off' (default), 'child', 'parent' or 'all'. |
geopoliticalView |
GeopoliticalView |
Which country a place in a disputed territory belongs to. Defaults to the global one. See Geopolitical views. |
mapcodes |
MapcodeType[] |
Adds mapcodes: 'Local', 'International', 'Alternative'. |
extendedPostalCodesFor |
SearchIndexType[] |
Indexes to return extended postal codes for. Default: all except 'Geo'. |
Filters
Every option that drops results lives under filters. geoBias stays separate.
It sets where to look, not what to accept.
| Member | Type | Description |
|---|---|---|
poiCategories |
POICategory[] |
POI category codes, found through POI categories. |
poiCategoryQuery |
string | string[] |
Categories in words, discoverPlaces only, never with poiCategories. See Naming the category in words. |
poiBrands |
string[] |
Brand names, case-sensitive, e.g. ['Starbucks', 'Costa Coffee']. Quote a name with a comma, as in '"A,B"'. |
indexes |
SearchIndexType[] |
Which indexes to search: 'Geo', 'PAD', 'Addr', 'Str', 'XStr', 'POI'. Default: all. |
geographyTypes |
GeographyType[] |
Kinds of Geography result, e.g. ['Municipality', 'Neighbourhood']. |
connectors |
ConnectorType[] |
EV connector types, e.g. ['IEC62196Type2CCS', 'Chademo']. |
fuelTypes |
Fuel[] |
Fuel types, e.g. ['Petrol', 'Diesel', 'E85']. |
minPowerKW / maxPowerKW |
number |
EV charging power bounds, in kilowatts. |
countries |
string[] |
ISO 3166-1 alpha-2 or alpha-3 codes, e.g. ['NL', 'DE']. Fuzzy search only. |
- A filter excludes. A place that doesn't match is dropped, not ranked lower.
- Values within one member are OR'd, and members are AND'd.
poiBrands: ['Shell', 'BP']withfuelTypes: ['Diesel']keeps stations of either brand that sell diesel. - The type follows the search. 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
Fuzzy search and geocoding take one geoBias, either a point or a rectangle.
The compiler rejects both together.
// Around a point: ranks nearer results higher; with radiusMeters, also confines results to the circle
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() }
positiontakes[longitude, latitude]or any GeoJSON carrying one.- Omit
geoBiasto search without a geographic bias. - Geometry and along-route search take none, because their
geometriesorrouteis the search area.
Fuzzy search parameters
On top of the common parameters:
| Parameter | Type | Description |
|---|---|---|
geoBias |
GeoBias |
{ position, radiusMeters? } or { boundingBox }. See Geographic bias. |
typeahead |
boolean |
Treat the query as partial input. Default false. |
cursor |
string |
The previous response's nextCursor, to fetch the next page. See Pagination. |
minFuzzyLevel |
number |
Minimum fuzziness, 1–4. Default 1. |
maxFuzzyLevel |
number |
Maximum fuzziness, 1–4. Default 2. Higher tolerates more typos. |
Geometry search parameters
On top of the common parameters:
| Parameter | Type | Description |
|---|---|---|
geometries |
SearchGeometryInput[] |
Required. The areas to search: Circle, Polygon, MultiPolygon or a polygon FeatureCollection. |
Along-route search parameters
On top of the common parameters:
| Parameter | Type | Description |
|---|---|---|
route |
Route | LineString | Position[] |
Required. A Route from calculateRoute, its LineString geometry, or [longitude, latitude] positions. |
maxDetourTimeSeconds |
number |
Required. The detour budget, in seconds, as a positive integer. |
sortBy |
'detourTime' | 'detourOffset' |
Order by detour time (default) or by position along the route. |
POI categories
filters.poiCategories takes POICategory codes, such as ITALIAN_RESTAURANT, FUEL_STATION,
HOTEL, PHARMACY, PARKING_GARAGE or CHARGING_LOCATION. The
POI categories service lists them and finds them by name or synonym.
import { discoverPlaces, getPOICategoryCodes } from '@tomtom-org/maps-sdk/services';
const geoBias = { position: [4.9041, 52.3676], radiusMeters: 1000 };
const italian = await discoverPlaces({ geoBias, filters: { poiCategories: ['ITALIAN_RESTAURANT'] } });
// Codes looked up by keyword first
const restaurantCodes = await getPOICategoryCodes({ filters: ['restaurant'] });
const restaurants = await discoverPlaces({ geoBias, filters: { poiCategories: restaurantCodes } });
// Or named in words, in one call
const sushi = await discoverPlaces({ geoBias, filters: { poiCategoryQuery: 'sushi' } });
// EV charging, by connector and power
const fastChargers = await discoverPlaces({
geoBias: { position: [4.9041, 52.3676], radiusMeters: 5000 },
filters: { poiCategories: ['CHARGING_LOCATION'], connectors: ['IEC62196Type2CCS', 'Tesla'], minPowerKW: 50 },
});
Naming the category in words: poiCategoryQuery
filters.poiCategoryQuery takes categories as a person says them, such as 'sushi' or ['gym', 'parking'],
and searches the POI categories they name. Every search variant takes it, whether near a geoBias, inside
geometries or along a route.
- The words go through
resolvePOICategoriesin the call'slanguage, and the codes it returns become the search'spoiCategories. - The category list costs one request per language and session, and is then cached.
- An unmatched word is left out. If no word matches, the call rejects with an
SDKErrorand sends no search. To handle unmatched words yourself, callresolvePOICategoriesfirst. - When the user names a kind of place, prefer it to
query: 'sushi', becausequeryalso matches names and addresses.
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
Each feature is a Place with SearchPlaceProps. Places describes every
property, including the search-specific ones.
const results = await discoverPlaces({ query: 'coffee', geoBias: { position: [4.9041, 52.3676] } });
results.features.forEach((place) => {
console.log(place.properties.poi?.name); // POI results only
console.log(place.properties.address.freeformAddress);
console.log(place.geometry.coordinates); // [longitude, latitude]
console.log(place.properties.poi?.categories); // POICategory[], e.g. ['CAFE_PUB']
console.log(place.properties.poi?.localizedCategories); // string[] in the call's language
console.log(place.properties.distance); // metres from geoBias.position, when given
});
discoverOnePlace() returns the first fuzzy-search result, and throws an Error when there is none:
import { discoverOnePlace } from '@tomtom-org/maps-sdk/services';
const place = await discoverOnePlace('Eiffel Tower');
// The params form takes the fuzzy search parameters except `limit`
const nearby = await discoverOnePlace({ query: 'coffee', geoBias: { position: [2.2945, 48.8584] } });
Pagination
A fuzzy search response carries nextCursor in its properties while a next page exists. Pass it back
as cursor, with the same parameters:
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);
}
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. Pass it back unchanged. Its contents aren't part of the
contract. A value the SDK didn't produce throws an
SDKError. - A cursor continues one search. A new query or different
filtersstart a new search, so leavecursorout. nextCursor, nottotalResults, tells whether a next page exists. It is absent once the results are exhausted, after an empty page, and once the next page would start past result 1900, the furthest the backend serves.- Only fuzzy search and geocoding paginate. Geometry and along-route search return one page, with no
nextCursor.geocodepages the same way.
Fields that may be absent
The type is the contract. Where a property is optional, handle its absence instead of 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 |
const results = await discoverPlaces({ query: 'pizza', limit: 10 });
if (results.properties?.totalResults !== undefined) {
const { numResults, totalResults } = results.properties;
console.log(`Showing ${numResults} of ${totalResults}`);
}
A missing fuzzyLevel means "not reported", not "matched exactly", so don't read it as 0.
Examples
Find bus stops in an area (Node.js)
A category search confined to a rectangle, as in the Node.js POI search example:
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
The Places module shows a search response without mapping fields first:
import { TomTomMap, PlacesModule } from '@tomtom-org/maps-sdk/map';
import { discoverPlaces } from '@tomtom-org/maps-sdk/services';
const map = new TomTomMap({ mapLibre: { container: 'map' } });
const placesModule = await PlacesModule.create(map);
await placesModule.show(await discoverPlaces({ query: 'museums', geoBias: { position: [4.9041, 52.3676] } }));
Searching again as the map moves
A search re-run on every map move can answer out of order. A slow answer for the previous
viewport then paints over the current one. To prevent this, 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);
});
To keep POI categories on the map as it moves without writing this yourself, use the viewport places plugin.
Related guides and examples
Map integration
- Places module - Search results as interactive places on the map
- POIs - The map's own POI icons, filtered by the same categories
Related examples
- Along route search - Beaches and viewpoints within a one-minute detour of a drive
- Along route search (Node.js) - Fast chargers along a route, in Node.js
- Along route search playground - A query and POI categories along a route
- Geometry searches along a route - Corridors of different widths around a route, as polygons
- Geometry search playground - Searching inside area boundaries
- Geometry search (Node.js) - Cafés inside a polygon, in Node.js
- Places by category in words - A category named in words, in five languages, inside a city outline
- Search playground - Search-as-you-type, and a picked brand or category paged in with
nextCursor - Search places in geometry - Places inside the outline of a geocoded area
- Search places nearby location - Places around a geocoded location
- Places customization playground - Customizing how search results look
Related services
- Geocoding - Addresses to coordinates
- Reverse geocoding - Coordinates to an address
- Geometry data - The outlines a geometry search can run inside