All docsHelpTutorialsCreate a timezone finder with Mapbox Tilequery API

Create a timezone finder with Mapbox Tilequery API

Intermediate
JavaScript
Prerequisite

Familiarity with front-end development concepts including JavaScript and API calls.

The Mapbox Tilequery API enables users to get data from a tileset without necessarily needing to render a map. This tutorial shows you how to create an app that uses the Tilequery API and a custom tileset to determine which timezone a user is in.

You will not be building a map in this tutorial. To see an example of how Mapbox Tilequery API can be used to interact with a rendered map, take a look at the Tilequery API Playground.

Getting started

There are a few resources you’ll need before getting started:

  • An access token from your Mapbox account. You will use an access token to associate a map with your account. Your access token is on the Account page.
  • Data. Your app will reference a tileset that contains timezone information.
    • This data set was sourced from Evan Savage’s GitHub. For this tutorial, you don't have to download the data or upload it to Mapbox — we've done that for you already!
    • Normally, you would need to upload this data to Mapbox using Mapbox Studio, Mapbox Tiling Service (MTS), or the Mapbox Uploads API. Be aware that uploading using MTS or the Uploads API is priced differently than uploading using Mapbox Studio, which is free. See the pricing documentation for MTS and the Uploads API for more details on how we charge for these services.
  • Mapbox Tilequery API. Our Tilequery API lets you query custom data without needing to render a map to the page.
  • A text editor. Use the text editor of your choice for writing HTML, CSS, and JavaScript.

In this tutorial, you will create an interface using HTML and CSS. Then you will add a call to the native HTML geolocation API, as well as a call to the Mapbox Tilequery API to query a custom tileset that contains timezone data. At the end of this tutorial, you will have created an app that, when a user clicks a button, determines their timezone.

Structure the webpage

Start by pasting the following code into your text editor. This code references a few items in the <head> of your file that you will take advantage of later in this tutorial:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Demo: Create a timezone finder with Mapbox Tilequery API</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      body {
        background-color: #d3f0de;
        font-family: Arial, Helvetica, sans-serif;
      }

      #tz-container {
        text-align: center;
        padding-top: 50px;
      }

      #tz-button {
        position: relative;
        padding: 6px 12px;
        display: inline-flex;
        justify-content: center;
        align-items: center;
      }

      .loading .tz-button-text {
        visibility: hidden;
      }

      @keyframes loading {
        to {
          transform: rotate(360deg);
        }
      }

      .loading::before {
        content: '';
        position: absolute;
        width: 20px;
        height: 20px;
        border-radius: 50%;
        border: 2px solid #fff;
        border-top-color: #000;
        animation: loading 0.5s linear infinite;
      }
    </style>
  </head>

  <body>
    <div id="tz-container">
      <h1>Timezone finder</h1>
      <p>
        <button id="tz-button">
          <span class="tz-button-text">Find my timezone!</span>
        </button>
      </p>
      <p id="return-message"></p>
    </div>
  </body>
  </html>

Save this file and open it in your browser.

a screenshot showing the interface of the Tilequery API timezone finder app

This code doesn't do anything yet, so in the next few steps you will add more functionality to turn it into a fully realized timezone finder.

Build the API request

Next, you will build your request to the Mapbox Tilequery API and test it in your browser. After you have verified that it is returning the expected results, you will add the API call to your app!

The format of a Tilequery API call is:

https://api.mapbox.com/v4/{tileset}/tilequery/{longitude},{latitude}.json?access_token=YOUR_MAPBOX_ACCESS_TOKEN

In a Mapbox Tilequery API call, the tileset indicates the data that your API request will be querying. In this case, your app will query custom data that has been uploaded to Mapbox. You will reference this tileset id (examples.4ze9z6tv) in the API call. You can also tell the Tilequery API to ask questions of Mapbox’s default tilesets, such as Mapbox Streets or Mapbox Terrain. To visualize the results of a call to one of these tilesets, use the Tilequery API Playground.

Parameters

The only required parameters for a call to the Mapbox Tilequery API are the longitude and latitude of a desired location. To test the API, we will set this to -77.033835,38.89975, the coordinates of Mapbox’s Washington D.C. office.

The Tilequery API also accepts several optional query parameters, including radius, that you will not need to use for this project. Read more about these optional parameters and how to use them in the Mapbox API Documentation’s Tilequery section.

Access token

The only required item left is your access token, which you will find on your Mapbox account page. The access token is required to track the requests you make from your account.

Review the response

When you put it all together, your request will look like:

https://api.mapbox.com/v4/examples.4ze9z6tv/tilequery/-77.033835,38.89975.json?access_token=YOUR_MAPBOX_ACCESS_TOKEN

Now that you have a request, take a look at what you get in return by pasting the full URL into a new browser window. Make sure that access_token= is set equal to your access token.

When you make your request, a JSON object is returned that contains, along with other information, a TZID property. This property shows the timezone of our test coordinates, the America/New_York timezone.

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          -77.033835,
          38.89975
        ]
      },
      "properties": {
        "TZID": "America/New_York",
        "tilequery": {
          "distance": 0,
          "geometry": "polygon",
          "layer": "tz_world-c5z4hv"
        }
      }
    }
  ]
}

Now that you understand how Mapbox Tilequery API requests and responses both work, you can use this API request to build out your app’s functionality.

Add the API request to the app

All modern browsers support the HTML geolocation API, which allows a browser to determine a user’s location if the user provides permission for it to do so. The timezone finder app will use the HTML geolocation API to get the longitude and latitude that will be used in the Mapbox Tilequery API call.

First, add the following code to your file above the closing </body> tag. This creates a place for you to add the JavaScript the app needs. The accessToken constant stores the access token that will be used in the query. Add your own access token between the quotation marks.

<script>
  const accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'; // add your access token here
</script>

Next, you will write a call to the HTML geolocation API to get the user's longitude and latitude. Add the following code after the accessToken constant you created:

navigator.geolocation.getCurrentPosition((position) => {
  const longitude = position.coords.longitude;
  const latitude = position.coords.latitude;
  console.log(`Your location: ${longitude},${latitude}`);
});

Save your work and open this page in your browser. The browser will ask your permission to know your location. The console.log line will print the constants longitude and latitude to your browser’s console so that you can see the data that the HTML geolocation service returns.

Now it’s time to use this information to construct a Tilequery API call!

Include Tilequery API

To use the Tilequery API, you will write a function called getUserLocation that will make a call to the API. This code will replace the call to the HTML geolocation API that you wrote in the last step, so erase that and replace it with the following code. Now the code between your opening and closing script tags should be:

const accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'; // add your access token here

function getUserLocation() {
  async function success(position) {
    // create constants that will hold the
    // user's latitude and longitude data
    const longitude = position.coords.longitude;
    const latitude = position.coords.latitude;
    // make the call to Tilequery API and parse the results
    const query = await fetch(
      `https://api.mapbox.com/v4/examples.4ze9z6tv/tilequery/${longitude},${latitude}.json?access_token=${accessToken}`,
      { method: 'GET' }
    );
    const data = await query.json();
    // get the timezone from the resulting GeoJSON FeatureCollection
    const userTimezone = data.features[0].properties.TZID;
  }
  // pass the results of a successful Tilequery API call
  // to the HTML geolocation API
  navigator.geolocation.getCurrentPosition(success);
}

Add interactivity

Now you’re ready to add interactivity to your app! Add an event listener that calls the getUserLocation function when the user clicks the button.

To access the button, you can use its id tz-button. Create a new constant in your JavaScript right below the accessToken constant:

const tzButton = document.getElementById('tz-button');

Now add an onclick event listener that will trigger the getUserLocation function when the button is clicked. Add the following line to your JavaScript below the tzButton constant declaration:

tzButton.onclick = getUserLocation;

The code between your opening and closing script tags should now be:

const accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'; // add your access token here

// create a constant that selects the button
const tzButton = document.getElementById('tz-button');
// when the button is clicked, call the getUserLocation function
tzButton.onclick = getUserLocation;

function getUserLocation() {
  async function success(position) {
    const longitude = position.coords.longitude;
    const latitude = position.coords.latitude;
    const query = await fetch(
      `https://api.mapbox.com/v4/examples.4ze9z6tv/tilequery/${longitude},${latitude}.json?access_token=${accessToken}`,
      { method: 'GET' }
    );
    const data = await query.json();
    const userTimezone = data.features[0].properties.TZID;
  }

  navigator.geolocation.getCurrentPosition(success);
}

Display messages

The only thing left to do is display the user’s timezone to the page! In the HTML section of your app, you already created a div that contains a p tag with the id return-message. This will be used to display the success or error message for your app. In the getUserLocation function, create a constant for this element:

const returnMessage = document.getElementById('return-message');

Show a loading animation at the beginning of the getUserLocation function that lets users know that the app is working on returning their information. You can do this by adding the CSS loading class to the tzButton element defined in the previous section. You will also need to disable the button when the loading animation appears.

tzButton.classList.add('loading');
tzButton.disabled = true;

Now that you have a loading animation, you need to remove the animation and add a success message. Since a success message is there to tell users when things have gone correctly, your app will display a message that tells users which timezone they are in. First, though, you will need to remove the loading message and enable the button. Add these lines to your JavaScript right below the userTimezone constant:

tzButton.classList.remove('loading');
tzButton.disabled = false;
returnMessage.textContent = `You are in the ${userTimezone} timezone.`;

There’s always the possibility for something to go wrong, and if that happens you need to let the user know. Add an error message near the end of the getUserLocation function:

function error() {
  tzButton.classList.remove('loading');
  tzButton.disabled = false;
  returnMessage.textContent =
    'Sorry, unable to determine your current location.';
}

The code between your script tags will look like this now:

const accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
// create variable for the return message -
// this makes it easier to access it later!
const returnMessage = document.getElementById('return-message');
// create a variable that selects the button
const tzButton = document.getElementById('tz-button');
// when the button is clicked, call the getUserLocation function
tzButton.onclick = getUserLocation;

function getUserLocation() {
  // show a loading animation while we wait for the data
  tzButton.classList.add('loading');
  tzButton.disabled = true;
  async function success(position) {
    // create variables that will hold the
    // user's latitude and longitude data
    const longitude = position.coords.longitude;
    const latitude = position.coords.latitude;
    // use the Mapbox Tilequery API to query the timezone tileset
    const query = await fetch(
      `https://api.mapbox.com/v4/examples.4ze9z6tv/tilequery/${longitude},${latitude}.json?access_token=${accessToken}`,
      { method: 'GET' }
    );
    const data = await query.json();
    // grab the timezone from the resulting JSON
    const userTimezone = data.features[0].properties.TZID;
    // on success, display the user's timezone
    tzButton.classList.remove('loading');
    tzButton.disabled = false;
    returnMessage.textContent = `You are in the ${userTimezone} timezone.`;
  }

  // if anything goes wrong, remove the loading animation and display an error message
  function error() {
    tzButton.classList.remove('loading');
    tzButton.disabled = false;
    returnMessage.textContent =
      'Sorry, unable to determine your current location.';
  }

  navigator.geolocation.getCurrentPosition(success, error);
}

Finished product

You used the Mapbox Tilequery API to build an app that queries a custom tileset to determine a user’s timezone, without ever having to reference a rendered map. Open your app in the browser and try it out.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Demo: Create a timezone finder with Mapbox Tilequery API</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
background-color: #d3f0de;
font-family: Arial, Helvetica, sans-serif;
}
#tz-container {
text-align: center;
padding-top: 50px;
}
#tz-button {
position: relative;
padding: 6px 12px;
display: inline-flex;
justify-content: center;
align-items: center;
}
.loading .tz-button-text {
visibility: hidden;
}
@keyframes loading {
to {
transform: rotate(360deg);
}
}
.loading::before {
content: '';
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #fff;
border-top-color: #000;
animation: loading 0.5s linear infinite;
}
</style>
</head>
<body>
<div id="tz-container">
<h1>Timezone finder</h1>
<p>
<button id="tz-button">
<span class="tz-button-text">Find my timezone!</span>
</button>
</p>
<p id="return-message"></p>
</div>
<script>
const accessToken = '<your access token here>';
// create variable for the return message -
// this makes it easier to access it later!
const returnMessage = document.getElementById('return-message');
// create a variable that selects the button
const tzButton = document.getElementById('tz-button');
// when the button is clicked, call the getUserLocation function
tzButton.onclick = getUserLocation;
function getUserLocation() {
// show a loading animation while we wait for the data
tzButton.classList.add('loading');
tzButton.disabled = true;
async function success(position) {
// create variables that will hold the
// user's latitude and longitude data
const longitude = position.coords.longitude;
const latitude = position.coords.latitude;
// use the Mapbox Tilequery API to query the timezone tileset
const query = await fetch(
`https://api.mapbox.com/v4/examples.4ze9z6tv/tilequery/${longitude},${latitude}.json?access_token=${accessToken}`,
{ method: 'GET' }
);
const data = await query.json();
// grab the timezone from the resulting JSON
const userTimezone = data.features[0].properties.TZID;
// on success, display the user's timezone
tzButton.classList.remove('loading');
tzButton.disabled = false;
returnMessage.textContent = `You are in the ${userTimezone} timezone.`;
}
// display an error message if anything goes wrong
function error() {
tzButton.classList.remove('loading');
tzButton.disabled = false;
returnMessage.textContent =
'Sorry, unable to determine your current location.';
}
navigator.geolocation.getCurrentPosition(success, error);
}
</script>
</body>
</html>

Next steps

To learn how to use cURL to upload large custom data sets like the one used in this tutorial to your Mapbox account, read the Upload to Mapbox using cURL tutorial.