How to Build a Local Search Map Using Foursquare Places API

Foursquare
Foursquare
Published in
6 min readSep 23, 2022

By: Wen Huang

Building a local search map can be easier than you think. We’ll be walking through how to do so by using simple place search autocomplete inputs and Vanilla Javascript. In this example, we’ll be using and referencing the Foursquare Places API and Mapbox, which is available to try for free by creating a Foursquare account.

Getting started

Here’s what we’ll need:

  1. Create a simple HTML template and import Mapbox gl for rendering the map.
// index.html<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Places Explorer Map</title>
<script src="https://api.mapbox.com/mapbox-gl-js/v2.8.2/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.8.2/mapbox-gl.css" rel="stylesheet" />
</head>

<body>
<div class="explorer">
<div id="map" style="width: 400px; height: 300px;"></div>
</div>
</body>
</html>

2. On the JavaScript file or the < script> tag in the HTML file, initialize the Mapbox map. (For more advance usage, check out Mapbox’s official documentation).

// index.jsmapboxgl.accessToken = 'MAPBOX_ACCESS_TOKEN';

const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
center: [-74.5, 40], // starting position [lng, lat]
zoom: 12,
});

3. Next, add our search box to the HTML.

// index.html<input type="text" id="explorer-search" placeholder="Search Foursquare Places" />
<ul id="explorer-suggestions"></ul>

The explorer-suggestions will be used for the list of places results.

3. Let’s add a method that triggers a call to the Foursquare Autocomplete API.

// index.jsconst fsqAPIToken = 'FSQ_API_TOKEN';
let userLat;
let userLng;
let sessionToken = generateRandomSessionToken();

async function autocomplete(query) {
const { lng, lat } = map.getCenter();
userLat = lat;
userLng = lng;
try {
const searchParams = new URLSearchParams({
query,
types: 'place',
ll: `${userLat},${userLng}`,
radius: 50000,
session_token: sessionToken,
}).toString();
const searchResults = await fetch(
`https://api.foursquare.com/v3/autocomplete?${searchParams}`,
{
method: 'get',
headers: new Headers({
Accept: 'application/json',
Authorization: fsqAPIToken,
}),
}
);
const data = await searchResults.json();
return data.results;
} catch (error) {
throw error;
}
}

/* Generate a random string with 32 characters.
Session Token is a user-generated token to identify a session for billing purposes.
Learn more about session tokens.
https://docs.foursquare.com/reference/session-tokens
*/
function generateRandomSessionToken(length = 32) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
for (let i = 0; i < length; i++) {
result += characters[Math.floor(Math.random() * characters.length)];
}
return result;
}

A few callouts:

  • The types parameter represents the types of results to return. It can be: address, geo, or place. We will use types: ‘place’ in this example.
  • The ll parameter represents the latitude/longitude around which you wish to retrieve the place information.
  • The radius parameter defines the distance (in meters) within which to return place results
  • The sesstion_token is a user-generated token utilized to group the user query and the user’s selected result into a discrete session for billing purposes. (Note that if the session_token parameter is omitted, the API calls are charged per keystroke/request.) In the example above, we generated a random session token when we were starting to make a request. We’ll show you the complete usage afterwards.

For more information about the query parameters, check out this documentation.

Integrating the search input field

We will now integrate the search input field using the autocomplete method so that we can then add the results to the explorer-suggestions we made before.

// index.jsconst inputField = document.getElementById('explorer-search');
const dropDownField = document.getElementById('explorer-dropdown');
const ulField = document.getElementById('explorer-suggestions');

let isFetching = false;
async function changeAutoComplete({
target
}) {
const {
value: inputSearch = ''
} = target;
ulField.innerHTML = '';
if (inputSearch.length && !isFetching) {
try {
isFetching = true;
const results = await autocomplete(inputSearch);
if (results && results.length) {
results.forEach((value) => {
addItem(value);
});
}
} catch (err) {
logError(err);
} finally {
isFetching = false;
dropDownField.style.display = 'block';
}
} else {
dropDownField.style.display = 'none';
}
}

function logError(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
}

function addItem(value) {
const placeDetail = value[value.type];
if (!placeDetail || !placeDetail.geocodes || !placeDetail.geocodes.main) return;
ulField.innerHTML +=
`<li class="explorer--dropdown-item">
<div>${value.text.primary}</div>
<div>${value.text.secondary}</div>
</li>`;
}

We can see that when we search for something on the search bar, the list of results will appear below.

This is exactly what we want — these are the basics of making a Foursquare autocomplete API call.

Selecting a specific location

Let’s now select an item (or place) and enable the map to center on that place’s location.

  1. Add an onClick event to the < li> item in the addItem method.
// index.jsfunction addItem(value) {
const placeDetail = value[value.type];
if (!placeDetail || !placeDetail.geocodes || !placeDetail.geocodes.main) return;
const { latitude, longitude } = placeDetail.geocodes.main;
const { link } = value;
const dataObject = JSON.stringify({ latitude, longitude, link });
ulField.innerHTML +=
`<li data-object='${dataObject}'>
<div>${value.text.primary}</div>
<div>${value.text.secondary}</div>
</li>`;
}

2. Now you can add the onClick event. Note that we are generating a new session token after a complete search.

// index.jsulField.addEventListener('click', selectItem);async function selectItem({ target }) {
if (target.tagName === 'LI') {
const valueObject = JSON.parse(target.dataset.object);
const { latitude, longitude } = valueObject;
flyToLocation(latitude, longitude);
// generate new session token after a complete search
sessionToken = generateRandomSessionToken();
const name = target.dataset.name;
inputField.value = target.children[0].textContent;
dropDownField.style.display = 'none';
}
}
function flyToLocation(lat, lng) {
map.flyTo({
center: [lng, lat],
});
}

3. We also need to disable the pointer event for the div inside the < li> item in the CSS file.

// style.css
.explorer--dropdown-item div { pointer-events: none; }

4. Now, in order to show the place’s details, we have to pin the location on the actual map. To do so, we need to make an additional API call to the Foursquare Places Details endpoint — and luckily, the Foursquare Autocomplete API responses make it very easy to do that. In fact, the response value link gives you the exact path of the place’s details endpoint.

[
{
"type": "place",
"text": {
"primary": "Staples",
"secondary": "601 Washington Ave, Manahawkin, NJ 08050",
"highlight": [
{
"start": 0,
"length": 5
}
]
},
"link": "/v3/places/4c682eae31ba2d7f5b00f772?session_token=S7DKSAWDZO26MK2436AAUIZLX7ZG3N6R",
...
}
]

5. Make another Fetch API call with the link provided by the response.

// index.jsasync function fetchPlacesDetails(link) {
try {
const searchParams = new URLSearchParams({
fields: 'fsq_id,name,geocodes,location,photos,rating',
}).toString();
const results = await fetch(
`https://api.foursquare.com${link}${searchParams}`,
{
method: 'get',
headers: new Headers({
Accept: 'application/json',
Authorization: fsqAPIToken,
}),
}
);
const data = await results.json();
return data;
} catch (err) {
logError(err);
}
}

6. Lastly, we want to add the pin and place’s detail as pop-ups on the map — see below.

// index.js
let currentMarker;function createPopup(placeDetail) {
const { location = {}, name = '', photos = [], rating } = placeDetail;
let photoUrl = '';
if (photos.length && photos[0]) {
photoUrl = `${photos[0].prefix}56${photos[0].suffix}`;
}
const popupHTML = `<div>
<image src="${photoUrl}" alt="photo of ${name}"/>
<div>
<div>${name}</div>
<div>${location.address}</div>
</div>
${rating ? `<div>${rating}</div>` : `<div />`}
</div>`;
const markerHeight = 35;
const markerRadius = 14;
const linearOffset = 8;
const verticalOffset = 8;
const popupOffsets = {
top: [0, verticalOffset],
'top-left': [0, verticalOffset],
'top-right': [0, verticalOffset],
bottom: [0, -(markerHeight + verticalOffset)],
'bottom-left': [0, (markerHeight + verticalOffset - markerRadius + linearOffset) * -1],
'bottom-right': [0, (markerHeight + verticalOffset - markerRadius + linearOffset) * -1],
left: [markerRadius + linearOffset, (markerHeight - markerRadius) * -1],
right: [-(markerRadius + linearOffset), (markerHeight - markerRadius) * -1],
};
return new mapboxgl.Popup({
offset: popupOffsets,
closeButton: false,
}).setHTML(popupHTML);
}
function addMarkerAndPopup(lat, lng, placeDetail) {
if (currentMarker) currentMarker.remove();
currentMarker = new mapboxgl.Marker({
color: '#3333FF',
})
.setLngLat([lng, lat])
.setPopup(createPopup(placeDetail))
.addTo(map);
currentMarker.togglePopup();
}

Voilà — we have now completed a simple autocomplete place search on a map.

You can access our live demo of this here and additionally refer to the Foursquare documentation page for a more in-depth look.

Want to build this out yourself? Sign up to get started with $200 worth of API calls.

--

--

Foursquare
Foursquare

Foursquare is the leading independent location technology company dedicated to building meaningful bridges between digital spaces and physical places.