Sorry, you need to enable JavaScript to visit this website.

Exploring Detailed POI Data with the TomTom Search API

With a little JavaScript, we'll use the TomTom POI Details API to create a simple map web page that highlights different pizza shops around Seattle and allows users to explore each business in finer detail.


TomTom Search API v6 helps developers access Detailed Points of Interest (POI) information. Examples include a store’s ratings and reviews, opening hours, and photos. This adds value to map-based applications, enhancing user experience with detailed information about each POI.

This article uses TomTom POI Details API to create a simple map web page that highlights different pizza shops around Seattle and allows users to explore each business in finer detail. To follow this tutorial, you just need to know some JavaScript.

POI Details Example

The regular search API already returns basic information and points of interest. This information includes business name, contact information (like address and phone number), and POI category type. It also contains a POI Details ID we can use to request additional details. You can find it in the dataSources object nested inside each search API result, like so:

"dataSources": {
  "poiDetails": [
    {
      "id": "45d78933f964a520e1421fe3",
      "sourceName": "Foursquare"
    }
  ]
}

The detailed data adds more specific information, if it is available available. Examples include photos, price range, social media links, popular hours for each day of the week, and even text reviews. 

For example, here is sample detail data in JSON format of one of my favorite pizza places in Seattle:

{
        "result": {
        "description": "Thin crust pizza in bustling Chinatown. World Pizza originated in the 1990's in the Belltown neighborhood.",
        "rating": {
            "totalRatings": 27,
            "value": 7.7,
            "minValue": 0,
            "maxValue": 10
            "id": "Rm91cnNxdWFyZTo0ZTE4YmI1NDdkOGI4YWY5MTM5MGQ3ZDg=",

        },
        "priceRange": {
            "value": 1,
            "label": "Cheap",
            "minValue": 1,
            "maxValue": 4
        },
        "socialMedia": [
            {
                "name": "facebook",
                "url": "https://facebook.com/profile.php?id=158562957492458"
            },
            {
                "name": "twitter",
                "url": "https://twitter.com/World_Pizza"
            }
        ],
        "photos": [
            {
                "id": "a35f4c83-2463-366f-a07b-1fde5877d415"
            },
            {
                "id": "ea498b33-d06c-3fa5-abbb-33662e6f32b7"
            },
            {
                "id": "08f6a677-f976-3c44-a1f2-0baacbd1870c"
            },
            {
                "id": "2b2c84da-df78-3e50-8682-03625e6993a6"
            },
            {
                "id": "0fa0f767-5181-3449-a62f-3d4f2337dcc0"
            }
        ],
        "reviews": [
            {
                "text": "Great by the slice vegetarian pizza!",
                "date": "2018-09-25"
            },
            {
                "text": "The potato-gorgonzola pizza is delicious.",
                "date": "2018-03-10"
            },
            {
                "text": "The crust is crunchy the sauce is delicious!the vegetarian is delicious I was very worried about a pizza joint in the international district , I am here at least once a week yummy",
                "date": "2017-06-28"
            },
            {
                "text": "Owner's very kind. Vegan pizza is honestly the best I've ever had! The happy hour was SUPER cheap (beer was only $1.50) and the atmosphere was perfect",
                "date": "2015-12-18"
            },
            {
                "text": "Best veggie pizzas",
                "date": "2015-05-16"
            },
            {
                "text": "Spicy field roast + pineapple pizza is awesome",
                "date": "2014-08-20"
            },
            {
                "text": "The red potato pizza is a delicious comfort food. Ingredients are super fresh and tasty (and all vegetarian!). The two owners are very nice guys! Excellent happy hour at 3 with pizza & drink specials.",
                "date": "2014-01-28"
            },
            {
                "text": "They have amazingly crispy chocolate chip cookies too!",
                "date": "2013-06-08"
            },
            {
                "text": "I'm not vegetarian, but that doesn't keep me from saying it's perhaps  the best pizza I've ever had. My favorites: the potato & the spicy veg pepperoni. Adam & Aaron are great guys!",
                "date": "2011-09-09"
            }
        ],
        "popularHours": [
            {
                "dayOfWeek": 1,
                "timeRanges": [
                    {
                        "startTime": {
                            "hour": 11,
                            "minute": 0
                        },
                        "endTime": {
                            "hour": 14,
                            "minute": 0
                        }
                    },
                    {
                        "startTime": {
                            "hour": 17,
                            "minute": 0
                        },
                        "endTime": {
                            "hour": 20,
                            "minute": 0
                        }
                    }
                ]
            },
            {
                "dayOfWeek": 2,
                "timeRanges": [
                    {
                        "startTime": {
                            "hour": 11,
                            "minute": 0
                        },
                        "endTime": {
                            "hour": 14,
                            "minute": 0
                        }
                    },
                    {
                        "startTime": {
                            "hour": 16,
                            "minute": 0
                        },
                        "endTime": {
                            "hour": 21,
                            "minute": 0
                        }
                    }
                ]
            },
            {
                "dayOfWeek": 3,
                "timeRanges": [
                    {
                        "startTime": {
                            "hour": 11,
                            "minute": 0
                        },
                        "endTime": {
                            "hour": 19,
                            "minute": 0
                        }
                    }
                ]
            },
            {
                "dayOfWeek": 4,
                "timeRanges": [
                    {
                        "startTime": {
                            "hour": 11,
                            "minute": 0
                        },
                        "endTime": {
                            "hour": 21,
                            "minute": 0
                        }
                    }
                ]
            },
            {
                "dayOfWeek": 5,
                "timeRanges": [
                    {
                        "startTime": {
                            "hour": 11,
                            "minute": 0
                        },
                        "endTime": {
                            "hour": 21,
                            "minute": 0
                        }
                    }
                ]
            },
            {
                "dayOfWeek": 6,
                "timeRanges": [
                    {
                        "startTime": {
                            "hour": 11,
                            "minute": 0
                        },
                        "endTime": {
                            "hour": 20,
                            "minute": 0
                        }
                    }
                ]
            },
            {
                "dayOfWeek": 7,
                "timeRanges": [
                    {
                        "startTime": {
                            "hour": 12,
                            "minute": 0
                        },
                        "endTime": {
                            "hour": 18,
                            "minute": 0
                        }
                    }
                ]
            }
        ]
    }
}

Pretty nice, right? Most of the data inside the JSON object should be self-explanatory, but you might notice the photos section contains a collection of IDs instead of URLs.

You can use these IDs with TomTom Points of Interest Photos API to retrieve the image at a specified width and height. When using this API, be sure to follow the Terms and Conditions. This includes attributing results from Details API and Photos API as “powered by Foursquare.” Also, do not cache results longer than 30 days.

Getting POI Details Data from TomTom

Now, see how to use Points of Interest Details API to get the detailed data using JavaScript.

First, collect a TomTom API key to use the APIs. It is quick and free to register for the TomTom Developer Portal. TomTom automatically generates the first key.

The next step is to set up the webpage with TomTom Maps SDK for Web so points of interest can be searched in detail. Create a new webpage and include the TomTom SDK files in the header. The Maps JS Library, its CSS stylesheet, and the Services JS Library are used below.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv='X-UA-Compatible' content='IE=Edge' />
        <meta charset='UTF-8'>
        <title>TomTom POI Details API Example</title>
        <meta name='viewport'
              content='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no'/>
        <link rel='stylesheet' type='text/css' href='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.12.0/maps/maps.css'>
        <script src='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.12.0/maps/maps-web.min.js'></script>
        <script src='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.13.0/services/services-web.min.js'></script>
    </head>
    <body>
        <script>
            ( async () => {
                // Your Code Here
            })();
        </script>
    </body>
</html>

Next, get some points of interest locations. Choose from various options to search and retrieve POI locations from TomTom, such as Fuzzy Search, Category Search, and Nearby Search. Use TomTom Maps POI Search API and center it around a latitude and longitude coordinate to get results within a default distance radius.

POI Search API supports numerous options to help narrow results, including types of businesses or locations. This example uses the query field with the center option to seek any points of interest that match the search phrase near a location. We will try searching for pizza near Seattle.

Using the SDK, pass the API key along with query text “pizza” and the longitude and latitude location coordinate for Seattle (-122.33, 47.60) to the tt.services.poiSearch service:

const data = await tt.services.poiSearch({
    key: "YOUR API KEY HERE",
    query: "pizza",
    center: [ -122.33, 47.60 ] // Seattle
});

The returned data should look similar to the following:

{
    "summary": {
        "query": "pizza",
        "queryType": "NON_NEAR",
        "queryTime": 276,
        "numResults": 100,
        "offset": 0,
        "totalResults": 369504,
        "fuzzyLevel": 1,
        "geoBias": {
            "lat": 47.6,
            "lon": -122.33
        }
    },
    "results": [
        {
            "type": "POI",
            "id": "g6JpZK84NDA1MzkwMDMwNzQ0MTShY6NVU0GhdqdVbmlmaWVk",
            "score": 2.5745153427,
            "dist": 195.61886948402355,
            "info": "search:ta:840539003074414-US",
            "poi": {
                "name": "Mod Pizza",
                "phone": "+1 206-338-7356",
                "brands": [
                    {
                        "name": "Mod Pizza"
                    }
                ],
                "categorySet": [
                    {
                        "id": 7315036
                    }
                ],
                "url": "https://modpizza.com/locations/occidental-square/",
                "categories": [
                    "pizza",
                    "restaurant"
                ],
                "classifications": [
                    {
                        "code": "RESTAURANT",
                        "names": [
                            {
                                "nameLocale": "en-US",
                                "name": "restaurant"
                            },
                            {
                                "nameLocale": "en-US",
                                "name": "pizza"
                            }
                        ]
                    }
                ]
            },
            "address": {
                "streetNumber": "202",
                "streetName": "Occidental Avenue South",
                "municipalitySubdivision": "Downtown Seattle",
                "municipality": "Seattle",
                "countrySecondarySubdivision": "King",
                "countrySubdivision": "WA",
                "countrySubdivisionName": "Washington",
                "postalCode": "98104",
                "extendedPostalCode": "98104-3120",
                "countryCode": "US",
                "country": "United States",
                "countryCodeISO3": "USA",
                "freeformAddress": "202 Occidental Avenue South, Seattle, WA 98104",
                "localName": "Seattle"
            },
            "position": {
                "lng": -122.33251,
                "lat": 47.60048
            },
            "viewport": {
                "topLeftPoint": {
                    "lng": -122.33384,
                    "lat": 47.60138
                },
                "btmRightPoint": {
                    "lng": -122.33118,
                    "lat": 47.59958
                }
            },
            "entryPoints": [
                {
                    "type": "main",
                    "position": {
                        "lng": -122.33289,
                        "lat": 47.60079
                    }
                }
            ],
            "dataSources": {
                "poiDetails": [
                    {
                        "id": "Rm91cnNxdWFyZTo1Y2UzMTJlNTAxYmM1YTAwMmM3M2MzNDE=",
                        "sourceName": "Foursquare"
                    }
                ]
            }
        },
        ...
    ]
}

Some results include a dataSource field indicating they have additional POI detail. Retrieve these details from TomTom by referencing the ID value using Points of Interest Details API. As a test, the first POI result’s detailed information is recieved by passing the API key and the POI Data Source ID value using TomTom SDK for Web:

const details = await tt.services.poiDetails({
    key: "YOUR API KEY HERE",
    id: data.results[ 0 ].dataSources.poiDetails[ 0 ].id
});

That is all it takes to access a whole new level of detail in an app’s location data. But, this is not done yet.

Now that we are set up to retrieve detailed information for points of interest, let’s combine that with TomTom Map Display API to create an interactive map of pizza shops around the area.

Integrating POI Details Data

To complete the app, add some user interface (UI) elements to the code: a map display and details section.

Adding a Map Display

Start by adding the following CSS code inside a <style> tag in your web page header. This sizes the two UI elements appropriately and ensures the details section sticks to the left side:

<style>
    #map {
        width: 48vw;
        height: 98vh;
    }


    #details {
        width: 48vw;
        padding: 3px;
        float: left;
    }
</style>

Next, add these two <div> elements inside the UI page body:

<div id="details"><h1>Click on a shop!</h1></div>
<div id="map"></div>

Keep the API key, location coordinate, and search query as global variables at the top. (Note that in a production release application, keep the API key more secure than placing it directly inside the web page).

Then initialize the map display and add a few controls:

const key = "YOUR API KEY HERE";
const position = [ -122.33, 47.60 ]; // Seattle
const query = "pizza";

( async () => {
    var map = tt.map({
        key: key,
        container: "map",
        dragPan: true,
        center: position,
        zoom: 13
    });
    map.addControl(new tt.FullscreenControl());
    map.addControl(new tt.NavigationControl());
    const data = await tt.services.poiSearch({
        key: key,
        query: query,
        center: position
    });
})();

At this point, when you open the webpage in a browser, you should see an interactive map centered on Seattle on the right half of the page, and space for detailed information on the left half of the page.

poi1

Adding POI Search Results

Next, add markers for our POI search results into the map, and enable users to click them to dynamically load and view the POI’s detailed information.

Create a helper function named createMarker to place a custom-colored marker on the map display at the specific location with a pop-up for the location name. It should assign and call the onclick event handler if it is passed in as a parameter:

function createMarker( map, position, color, data, onClick ) {
    const popup = new tt.Popup({ offset: 30 }).setText( data.poi.name );
    const marker = new tt.Marker({ anchor: "bottom", color: color })
        .setLngLat( position )
        .setPopup( popup )
        .addTo( map );
    if( onClick ) {
        marker._element.addEventListener( "click", onClick );
    }
}

Then, create a second helper function named displayDetails. This uses the basic POI data and detailed data to populate our details section, which is an information panel on the left half of the page. Also, ensure the text “Results powered by Foursquare” display at the bottom per API terms and conditions.

function displayDetails( data, details ) {
    const element = document.getElementById( "details" );
    // Clear out the current details
    element.innerHTML = "";
    // Show the location name
    const title = document.createElement( "h1" );
    title.innerText = data.poi.name;
    element.append( title );
    // Show additional information if it is included
    if( data.address ) {
        const address = document.createElement( "p" );
        address.innerText = `Address: ${data.address.freeformAddress}`;
        element.append( address );
    }
    if( data.poi.phone ) {
        const phone = document.createElement( "p" );
        phone.innerText = `Phone: ${data.poi.phone}`;
        element.append( phone );
    }
    if( details ) {
        if( details.photos ) {
            const photo = document.createElement( "img" );
            photo.src = `https://api.tomtom.com/search/2/poiPhoto?key=${key}&id=${details.photos[ 0 ].id}&width=200&height=200`;
            element.append( photo );
        }
        if( details.rating ) {
            const rating = document.createElement( "p" );
            rating.innerText = `Rating: ${details.rating.value} / ${details.rating.maxValue} (${details.rating.totalRatings} ratings)`;
            element.append( rating );
        }
        if( details.priceRange ) {
            const price = document.createElement( "p" );
            price.innerText = `Price: ${details.priceRange.label} ${details.priceRange.value} / ${details.priceRange.maxValue}`;
            element.append( price );
        }
        if( details.socialMedia ) {
            const social = document.createElement( "p" );
            social.innerHTML = `Social: ${details.socialMedia.map( s => `<a href="${s.url}">${s.name}</a>` ).join( " " )}`;
            element.append( social );
        }
        if( details.reviews ) {
            const reviewsHeader = document.createElement( "p" );
            reviewsHeader.innerText = "Reviews:";
            element.append( reviewsHeader );
            const reviews = document.createElement( "ul" );
            details.reviews.forEach( r => {
                const review = document.createElement( "li" );
                review.innerHTML = `<li>${r.date}: ${r.text}</li>`;
                reviews.append( review );
            });
            element.append( reviews );
        }
        // Per usage terms and conditions
        const poweredText = document.createElement( "p" );
        poweredText.innerText = "Results powered by Foursquare";
        element.append( poweredText );
    }
    else {
        const detailText = document.createElement( "p" );
        detailText.innerText = "No Additional Details";
        element.append( detailText );
    }
}

Also, expand the POI Search from the default limit of 10 results to 100. This shows more locations on the map.

const data = await tt.services.poiSearch({
    key: key,
    query: query,
    center: position,
    limit: 100,
});

Finally, iterate through the search results. Place a marker on the map for each result, and have it load and display the detailed information inside the click event handler:

data.results.forEach( p => {
    createMarker( map, [ p.position.lng, p.position.lat ], "#c30b82", p, async () => {
        if( p.dataSources && p.dataSources.poiDetails.length > 0 ) {
            // Detail sources available
            const details = await tt.services.poiDetails({
                key: key,
                id: p.dataSources.poiDetails[ 0 ].id
            });
            displayDetails( p, details.result );
        }
        else {
            // No detail sources
            displayDetails( p, null );
        }
    });
});

With everything in place, click on pizza shops on the map to see photos, ratings and reviews, price range, and more.

poi2

Here is the full project code, for reference:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv='X-UA-Compatible' content='IE=Edge' />
        <meta charset='UTF-8'>
        <title>TomTom POI Details API Example</title>
        <meta name='viewport'
              content='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no'/>
        <link rel='stylesheet' type='text/css' href='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.12.0/maps/maps.css'>
        <script src='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.12.0/maps/maps-web.min.js'></script>
        <script src='https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.13.0/services/services-web.min.js'></script>
        <style>
            #map {
                width: 48vw;
                height: 98vh;
            }


            #details {
                width: 48vw;
                padding: 3px;
                float: left;
            }
        </style>
    </head>
    <body>
        <div id="details"><h1>Click on a shop!</h1></div>
        <div id="map"></div>
        <script>
            const key = "YOUR API KEY HERE";
            const position = [ -122.33, 47.60 ]; // Seattle
            const query = "pizza";


            function createMarker( map, position, color, data, onClick ) {
                const popup = new tt.Popup({ offset: 30 }).setText( data.poi.name );
                const marker = new tt.Marker({ anchor: "bottom", color: color })
                    .setLngLat( position )
                    .setPopup( popup )
                    .addTo( map );
                if( onClick ) {
                    marker._element.addEventListener( "click", onClick );
                }
            }


            function displayDetails( data, details ) {
                const element = document.getElementById( "details" );
                // Clear out the current details
                element.innerHTML = "";
                // Show the location name
                const title = document.createElement( "h1" );
                title.innerText = data.poi.name;
                element.append( title );
                // Show additional information if it is included
                if( data.address ) {
                    const address = document.createElement( "p" );
                    address.innerText = `Address: ${data.address.freeformAddress}`;
                    element.append( address );
                }
                if( data.poi.phone ) {
                    const phone = document.createElement( "p" );
                    phone.innerText = `Phone: ${data.poi.phone}`;
                    element.append( phone );
                }
                if( details ) {
                    if( details.photos ) {
                        const photo = document.createElement( "img" );
                        photo.src = `https://api.tomtom.com/search/2/poiPhoto?key=${key}&id=${details.photos[ 0 ].id}&width=200&height=200`;
                        element.append( photo );
                    }
                    if( details.rating ) {
                        const rating = document.createElement( "p" );
                        rating.innerText = `Rating: ${details.rating.value} / ${details.rating.maxValue} (${details.rating.totalRatings} ratings)`;
                        element.append( rating );
                    }
                    if( details.priceRange ) {
                        const price = document.createElement( "p" );
                        price.innerText = `Price: ${details.priceRange.label} ${details.priceRange.value} / ${details.priceRange.maxValue}`;
                        element.append( price );
                    }
                    if( details.socialMedia ) {
                        const social = document.createElement( "p" );
                        social.innerHTML = `Social: ${details.socialMedia.map( s => `<a href="${s.url}">${s.name}</a>` ).join( " " )}`;
                        element.append( social );
                    }
                    if( details.reviews ) {
                        const reviewsHeader = document.createElement( "p" );
                        reviewsHeader.innerText = "Reviews:";
                        element.append( reviewsHeader );
                        const reviews = document.createElement( "ul" );
                        details.reviews.forEach( r => {
                            const review = document.createElement( "li" );
                            review.innerHTML = `<li>${r.date}: ${r.text}</li>`;
                            reviews.append( review );
                        });
                        element.append( reviews );
                    }
                    // Per usage terms and conditions
                    const poweredText = document.createElement( "p" );
                    poweredText.innerText = "Results powered by Foursquare";
                    element.append( poweredText );
                }
                else {
                    const detailText = document.createElement( "p" );
                    detailText.innerText = "No Additional Details";
                    element.append( detailText );
                }
            }


            ( async () => {
                var map = tt.map({
                    key: key,
                    container: "map",
                    dragPan: true,
                    center: position,
                    zoom: 13
                });
                map.addControl(new tt.FullscreenControl());
                map.addControl(new tt.NavigationControl());
                const data = await tt.services.poiSearch({
                    key: key,
                    query: query,
                    center: position,
                    limit: 100,
                });
                data.results.forEach( p => {
                    createMarker( map, [ p.position.lng, p.position.lat ], "#c30b82", p, async () => {
                        if( p.dataSources && p.dataSources.poiDetails.length > 0 ) {
                            // Detail sources available
                            const details = await tt.services.poiDetails({
                                key: key,
                                id: p.dataSources.poiDetails[ 0 ].id
                            });
                            displayDetails( p, details.result );
                        }
                        else {
                            // No detail sources
                            displayDetails( p, null );
                        }
                    });
                });
            })();
        </script>
    </body>
</html>

Next Steps

With just a little bit of code, TomTom Maps API, and Points of Interest Details API, we created a fully explorable pizza map within a web page. Visitors can view useful information on each pizza restaurant. Now we can look for a place to eat dinner tonight!

Fun ideas to try with this code include:

  • Change the location to a different city or enable city selection in the app
  • Create a different POI search map by changing the query to “ice cream” or “coffee”
  • Add custom icons to the markers to indicate the type of shop
  • Add an HTML element overlay to directly embed the detailed information inside the pop-up

TomTom’s iOS SDK and Android SDK help build this functionality into mobile apps as well. To learn more about TomTom Map APIs, check out the documentation and tutorials. Also, the online documentation and examples are great resources that show what else TomTom SDK for Web can do.

First published: 
Wednesday, May 26, 2021 - 12:37
Last edited: 
Friday, June 4, 2021 - 17:58