> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/help/ja/llms.txt)

# Data-joins with Mapbox Boundaries v3

This guide covers how to create a data-join using Mapbox Boundaries with Mapbox GL JS to style a choropleth map.

Mapbox users who have access to Mapbox Boundaries can add global administrative, postal, and statistical boundaries to their maps and data visualizations.

> **Note (legacy): Boundaries V4 is available**
> 
> Boundaries V4 is available to use and a [newer version of this tutorial](https://docs.mapbox.com/help/ja/help/tutorials/data-joins-with-mapbox-boundaries) is available to follow along with.

![final choropleth map using Boundaries, local data, and expressions](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--boundaries-data-join-final.0d0dd0f.480.png)

## Prerequisites

Here are a few resources you'll need throughout this tutorial:

-   **Access to Mapbox Boundaries**. Access to the Mapbox Boundaries tilesets is controlled by your Mapbox account access token. To request access to Mapbox Boundaries, contact [Mapbox sales](https://www.mapbox.com/contact/sales/).
    -   This includes a Mapbox access token with special permissions to access Boundaries tilesets.
-   [**Mapbox GL JS**](https://docs.mapbox.com/mapbox-gl-js/api/). The Mapbox JavaScript API for building web maps.
-   **Familiarity with front-end development.** Beginner experience with CSS, HTML and JavaScript.

## What is a data-join?

A data-join is a technique that involves inner joins between your custom local data, such as the unemployment rate by US state, to vector tile features, such as state boundaries in the appropriate Mapbox Boundaries tileset, using data-driven style notation.

In this tutorial, you will use Mapbox GL JS, local data, Mapbox Boundaries, feature state, and data-driven styling with expressions to join local data to a vector tile source and style a choropleth map.

## Create a map with Mapbox GL JS

Begin by initializing a map with Mapbox GL JS.

1.  Make sure the access token you are using is from **your account with access to Mapbox Boundaries**.

-   If your account does not have access to Boundaries, reach out to the [Mapbox Sales](https://www.mapbox.com/contact/sales/).

2.  Add the following code into your project:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Join local JSON data with Boundaries</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.25.0/mapbox-gl.js"></script>
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v3.25.0/mapbox-gl.css"
      rel="stylesheet"
    />
    <style>
      body {
        margin: 0;
        padding: 0;
      }

      #map {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      // Initialize a map
      const map = new mapboxgl.Map({
        accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN ',
        container: 'map',
        style: 'mapbox://styles/mapbox/light-v11',
        center: [-99.9, 41.5],
        zoom: 1
      });

      // Local data code from the next step will go here.
    </script>
  </body>
</html>
```

## Local data

Once you've created a map, we can begin to work with the local data.

In this example, you'll join unemployment data for the US to the admin-1 Boundaries tileset:

Set the array of objects equal to a constant called `localData`. In your own application, you can pull local data into your application how you'd like.

Here's the data used in this example as a JavaScript constant called `localData` that can be directly added to the HTML file's `script` tags alongside the code used to initialize the map:

```js
const localData = [
  { STATE_ID: '01', unemployment: 13.17 },
  { STATE_ID: '02', unemployment: 9.5 },
  { STATE_ID: '04', unemployment: 12.15 },
  { STATE_ID: '05', unemployment: 8.99 },
  { STATE_ID: '06', unemployment: 11.83 },
  { STATE_ID: '08', unemployment: 7.52 },
  { STATE_ID: '09', unemployment: 6.44 },
  { STATE_ID: '10', unemployment: 5.17 },
  { STATE_ID: '12', unemployment: 9.67 },
  { STATE_ID: '13', unemployment: 10.64 },
  { STATE_ID: '15', unemployment: 12.38 },
  { STATE_ID: '16', unemployment: 10.13 },
  { STATE_ID: '17', unemployment: 9.58 },
  { STATE_ID: '18', unemployment: 10.63 },
  { STATE_ID: '19', unemployment: 8.09 },
  { STATE_ID: '20', unemployment: 5.93 },
  { STATE_ID: '21', unemployment: 9.86 },
  { STATE_ID: '22', unemployment: 9.81 },
  { STATE_ID: '23', unemployment: 7.82 },
  { STATE_ID: '24', unemployment: 8.35 },
  { STATE_ID: '25', unemployment: 9.1 },
  { STATE_ID: '26', unemployment: 10.69 },
  { STATE_ID: '27', unemployment: 11.53 },
  { STATE_ID: '28', unemployment: 9.29 },
  { STATE_ID: '29', unemployment: 9.94 },
  { STATE_ID: '30', unemployment: 9.29 },
  { STATE_ID: '31', unemployment: 5.45 },
  { STATE_ID: '32', unemployment: 4.21 },
  { STATE_ID: '33', unemployment: 4.27 },
  { STATE_ID: '34', unemployment: 4.09 },
  { STATE_ID: '35', unemployment: 7.83 },
  { STATE_ID: '36', unemployment: 8.01 },
  { STATE_ID: '37', unemployment: 9.34 },
  { STATE_ID: '38', unemployment: 11.23 },
  { STATE_ID: '39', unemployment: 7.08 },
  { STATE_ID: '40', unemployment: 11.22 },
  { STATE_ID: '41', unemployment: 6.2 },
  { STATE_ID: '42', unemployment: 9.11 },
  { STATE_ID: '44', unemployment: 10.42 },
  { STATE_ID: '45', unemployment: 8.89 },
  { STATE_ID: '46', unemployment: 11.03 },
  { STATE_ID: '47', unemployment: 7.35 },
  { STATE_ID: '48', unemployment: 8.92 },
  { STATE_ID: '49', unemployment: 7.65 },
  { STATE_ID: '50', unemployment: 8.01 },
  { STATE_ID: '51', unemployment: 7.62 },
  { STATE_ID: '53', unemployment: 7.77 },
  { STATE_ID: '54', unemployment: 8.49 },
  { STATE_ID: '55', unemployment: 9.42 },
  { STATE_ID: '56', unemployment: 7.59 }
];
```

## Creating Mapbox Boundaries lookup tables

Each Boundaries tileset has its own feature lookup table in which each feature in that tileset is indexed. Lookup tables are designed to be used locally in your application.

You can read more about feature lookup tables in the [Get started with Mapbox Boundaries v3](https://docs.mapbox.com/data/boundaries/reference/mapbox-boundaries-v3/#basic-example) guide.

The sample below shows how the lookup JSON is structured for the administrative level-1 table which includes the boundary metadata for the US states. Note the `unit_code` property, which contains the state FIPS code that you will use to create the data-join with the sample data. Other identifiers useful for data-joins with custom data are the [`wikidata_id`](https://www.wikidata.org/wiki/Wikidata:Identifiers) and `name`.

```json
{
  "adm1": {
    - type": "admin
    "level": 1,
    "PolyTilesetName": "mapbox.boundaries-adm1-v3",
    "PolyLayerName": "boundaries_admin_1",
    "PointTilesetName": "mapbox.boundaries-admPoints-v3",
    "PointLayerName": "points_admin_1",
    "data": {
      "all": {
        "USA101": {
          "feature_id": 70377,
          "wikidata_id": "Q173",
          "worldview": "all",
          "unit_code": "01",
          "name": "Alabama",
          "names": {
            "en": ["Alabama", "State of Alabama", "AL", "Heart of Dixie", "The Yellowhammer State"]
          },
          "description": "state",
          "source_date": "2018",
          "iso_3166_1_alpha_3": "USA",
          "iso_3166_1": "US",
          "z_min": 0,
          "area_sqkm": 159864
        },
        ...
```

## Importing Mapbox Boundaries lookup tables

The code snippet below illustrates how to import the feature lookup table from a file hosted in the application, then make the API request when the map loads, retrieve the contents of the lookup table, and print the response in the console.

Replace `./path/to/adm1/lookup/table` with the path to the adm1 lookup table, available in the reference documentation that is provided with Boundaries access.

```js
const lookupTable = async () => {
  const query = await fetch('./path/to/lookup/table', { method: 'GET' });
  return await query.json();
};

map.on('load', () => {
  createViz(lookupTable);
});

function createViz(lookupTable) {
  const lookupTableData = lookupTable.adm1.data;
  console.log(lookupTableData);
}
```

Explore the response in the console to learn more about what is included in the lookup tables and better understand how you'll be using it in the next step.

## Feature state

A **Feature state** is a set of attributes that can be dynamically assigned to a feature on the map.

The [Mapbox GL JS feature state](https://docs.mapbox.com/mapbox-gl-js/api/#map#setfeaturestate) API can be used to dynamically style the features of a vector or GeoJSON source, enabling new ways to handle map interactivity, data joins, and time series animations.

Using feature state requires knowing the unique feature `id` property for each boundary in the vector tiles to manipulate its rendering. In the Mapbox Boundaries lookup tables, `feature_id` is this unique integer-only identifier.

To join the local unemployment data to the vector tiles:

-   Add a property in your local data that can be used to match the corresponding boundary in the lookup table and find its `feature_id`.
-   Inspect the `lookupTableData` and compare the properties for any state with the unemployment data to see if there is a match of values.

```
// Alabama in unemployment data
{ STATE_ID: '01', unemployment: 13.17 }

// Alabama in lookupTableData
"USA101": {
  "feature_id": 70377,
  "wikidata_id": "Q173",
  "worldview": "all",
  "unit_code": "01",
  "name": "Alabama",
  "names": {
    "en": ["Alabama", "State of Alabama", "AL", "Heart of Dixie", "The Yellowhammer State"]
}
```

We can see that `STATE_ID` which is the [FIPS state code](https://en.wikipedia.org/wiki/Federal_Information_Processing_Standard_state_code) is present as the `unit_code` value in the lookup table. It is now possible to find the `feature_id` of any boundary feature in the tiles using a `STATE_ID` -> `unit_code` lookup.

Boundary lookup tables can contain millions of rows as they include features for all countries. Filtering the lookup object by the country of interest and keying object by the matching property can optimize performance. The function below traverses the adm1 lookup table and creates a new `lookupData` object that can be used for our join.

```js
const lookupTable = async () => {
  const query = await fetch('./path/to/lookup/table', { method: 'GET' });
  return await query.json();
};

const lookupData = filterLookupTable(lookupTable);

// Filters the lookup table to features with the 'US' country code
// and keys the table using the `unit_code` property that will be used for the join
function filterLookupTable(lookupTable) {
  const lookupData = {};

  for (const layer of lookupTable)
    for (const worldview of lookupTable[layer].data)
      for (const feature of lookupTable[layer].data[worldview]) {
        const featureData = lookupTable[layer].data[worldview][feature];
        // Filter the lookup data for the US
        if (featureData.iso_3166_1 === 'US') {
          // Use `unit_code` property that has the FIPS code as the lookup key
          lookupData[featureData['unit_code']] = featureData;
        }
      }
  return lookupData;
}
```

Building on the `createViz` function that was defined in the previous step, add the Mapbox Boundaries admin-1 tileset as a source named `statesData`.

> **Note**
> 
> Find the complete list of Mapbox Boundaries tilesets in the [reference documentation](https://docs.mapbox.com/data/boundaries/reference/mapbox-boundaries-v3/#vector-tilesets).

Then, create a new function called `setStates` within `createViz` to set the feature state. To get the vector tile feature `id` to set the feature state, you end up with `id: lookupData[item.STATE_ID].feature_id` to find the boundary in `lookupData` from the lookup table with the `unit_code` to `STATE_ID` and then getting the value of the `feature_id` property.

Finally, you'll wait until the `statesData` source has been added to the map before calling your custom `setState` function to set the feature state.

```js
function createViz(lookupData) {
  const dataValues = lookupData.data;

  // Add Mapbox Boundaries source for state polygons.
  map.addSource('statesData', {
    type: 'vector',
    url: 'mapbox://mapbox.boundaries-adm1-v3'
  });

  // Join the JSON unemployment data with the corresponding vector features where
  // feature.unit_code === `STATE_ID`.
  function setStates() {
    for (const item of localData) {
      map.setFeatureState(
        {
          source: 'statesData',
          sourceLayer: 'boundaries_admin_1',
          id: lookupData[item.STATE_ID].feature_id
        },
        {
          unemployment: item.unemployment
        }
      );
    }
  }

  // Check if `statesData` source is loaded.
  function setAfterLoad(event) {
    if (event.sourceID !== 'statesData' && !event.isSourceLoaded) return;
    setStates();
    map.off('sourcedata', setAfterLoad);
  }

  // If `statesData` source is loaded, call `setStates()`.
  if (map.isSourceLoaded('statesData')) {
    setStates();
  } else {
    map.on('sourcedata', setAfterLoad);
  }
}
```

## Data-driven styling with expressions

Now that the local data and the vector data in the Mapbox Boundaries tileset have been joined, you can style the features in the Boundaries tileset according to the unemployment value from your local data. Inside the `createViz` function, add a new layer using `map.addLayer()`. The source will be `statesData` (added in the previous step), and the `source-layer` will be `boundaries_admin_1`.

You can use expressions to set the fill color of each feature according to the unemployment value found in the feature state. Mapbox GL JS expressions uses a Lisp-like syntax, represented using JSON arrays. Expressions follow this format:

```
[expression_name, argument_0, argument_1, ...]
```

The `expression_name` is the **expression operator**, for example, you would use [`'*'`](https://docs.mapbox.com/style-spec/reference/expressions/#*) to multiply two arguments or [`'case'`](https://docs.mapbox.com/style-spec/reference/expressions/#case) to create conditional logic. For a complete list of all available expressions see the [Mapbox Style Specification](https://docs.mapbox.com/style-spec/reference/expressions/).

The **arguments** are either *literal* (numbers, strings, or boolean values) or else themselves expressions. The number of arguments varies based on the expression.

In this example, you'll use a combination of expressions to style the data as a choropleth map:

-   [`case`](https://docs.mapbox.com/style-spec/reference/expressions/#case): Use the `case` expression to (1) check if the unemployment feature state property is not null, (2) if unemployment is not null, you'll assign the fill color according to the value of unemployment, (3) if it is null, you'll assign a fill color of `rgba(255, 255, 255, 0)`.
-   [`feature-state`](https://docs.mapbox.com/style-spec/reference/expressions/#feature-state): Use the `feature-state` expression to retrieve the value of the unemployment property in the current feature's state.
-   [`!=`](https://docs.mapbox.com/style-spec/reference/expressions/#!=): Use the `!=` expression to check if the feature state unemployment property is not equal to null.
-   [`interpolate`](https://docs.mapbox.com/style-spec/reference/expressions/#interpolate): Use the `interpolate` expression to assign a fill color to two different values of unemployment and infer a continuous, smooth set of fill colors between the stops.

When you put all these expressions together, your code will look like this:

```js
map.addLayer(
  {
    id: 'states-join',
    type: 'fill',
    source: 'statesData',
    'source-layer': 'boundaries_admin_1',
    paint: {
      'fill-color': [
        'case',
        ['!=', ['feature-state', 'unemployment'], null],
        [
          'interpolate',
          ['linear'],
          ['feature-state', 'unemployment'],
          4,
          'rgba(222,235,247,1)',
          14,
          'rgba(49,130,189,1)'
        ],
        'rgba(255, 255, 255, 0)'
      ]
    }
  },
  'waterway-label'
);
```

## Final product

You created a choropleth map using data-joins and Mapbox Boundaries.

![final choropleth map using Boundaries, local data, and expressions](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--boundaries-data-join-final.0d0dd0f.480.png)

Here's the full code:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Join local JSON data with Boundaries</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://api.mapbox.com/mapbox-gl-js/v3.25.0/mapbox-gl.js"></script>
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v3.25.0/mapbox-gl.css"
      rel="stylesheet"
    />
    <style>
      body {
        margin: 0;
        padding: 0;
      }

      #map {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      const map = new mapboxgl.Map({
        accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN ',
        container: 'map',
        style: 'mapbox://styles/mapbox/light-v11',
        center: [-99.9, 41.5],
        zoom: 1
      });

      // Join local JSON data with vector tile geometry
      // unemployment rate in 2009
      // Source https://data.bls.gov/timeseries/LNS14000000
      const localData = [
        { STATE_ID: '01', unemployment: 13.17 },
        { STATE_ID: '02', unemployment: 9.5 },
        { STATE_ID: '04', unemployment: 12.15 },
        { STATE_ID: '05', unemployment: 8.99 },
        { STATE_ID: '06', unemployment: 11.83 },
        { STATE_ID: '08', unemployment: 7.52 },
        { STATE_ID: '09', unemployment: 6.44 },
        { STATE_ID: '10', unemployment: 5.17 },
        { STATE_ID: '12', unemployment: 9.67 },
        { STATE_ID: '13', unemployment: 10.64 },
        { STATE_ID: '15', unemployment: 12.38 },
        { STATE_ID: '16', unemployment: 10.13 },
        { STATE_ID: '17', unemployment: 9.58 },
        { STATE_ID: '18', unemployment: 10.63 },
        { STATE_ID: '19', unemployment: 8.09 },
        { STATE_ID: '20', unemployment: 5.93 },
        { STATE_ID: '21', unemployment: 9.86 },
        { STATE_ID: '22', unemployment: 9.81 },
        { STATE_ID: '23', unemployment: 7.82 },
        { STATE_ID: '24', unemployment: 8.35 },
        { STATE_ID: '25', unemployment: 9.1 },
        { STATE_ID: '26', unemployment: 10.69 },
        { STATE_ID: '27', unemployment: 11.53 },
        { STATE_ID: '28', unemployment: 9.29 },
        { STATE_ID: '29', unemployment: 9.94 },
        { STATE_ID: '30', unemployment: 9.29 },
        { STATE_ID: '31', unemployment: 5.45 },
        { STATE_ID: '32', unemployment: 4.21 },
        { STATE_ID: '33', unemployment: 4.27 },
        { STATE_ID: '34', unemployment: 4.09 },
        { STATE_ID: '35', unemployment: 7.83 },
        { STATE_ID: '36', unemployment: 8.01 },
        { STATE_ID: '37', unemployment: 9.34 },
        { STATE_ID: '38', unemployment: 11.23 },
        { STATE_ID: '39', unemployment: 7.08 },
        { STATE_ID: '40', unemployment: 11.22 },
        { STATE_ID: '41', unemployment: 6.2 },
        { STATE_ID: '42', unemployment: 9.11 },
        { STATE_ID: '44', unemployment: 10.42 },
        { STATE_ID: '45', unemployment: 8.89 },
        { STATE_ID: '46', unemployment: 11.03 },
        { STATE_ID: '47', unemployment: 7.35 },
        { STATE_ID: '48', unemployment: 8.92 },
        { STATE_ID: '49', unemployment: 7.65 },
        { STATE_ID: '50', unemployment: 8.01 },
        { STATE_ID: '51', unemployment: 7.62 },
        { STATE_ID: '53', unemployment: 7.77 },
        { STATE_ID: '54', unemployment: 8.49 },
        { STATE_ID: '55', unemployment: 9.42 },
        { STATE_ID: '56', unemployment: 7.59 }
      ];

      async function fetchLookupTable() {
        const response = await fetch('./mapbox-boundaries-adm1-v3_4.json', {
          method: 'GET'
        });
        return await response.json();
      }

      map.on('load', () => {
        // Wait for the lookup table to load before proceeding with the data join
        fetchLookupTable().then((lookupTable) => {
          createViz(lookupTable);
        });
      });

      function createViz(lookupTable) {
        const lookupData = filterLookupTable(lookupTable);

        // Filters the lookup table to features with the 'US' country code
        // and keys the table using the `unit_code` property that will be used for the join
        function filterLookupTable(lookupTable) {
          const lookupData = {};

          for (const layer in lookupTable)
            for (const worldview in lookupTable[layer].data)
              for (const feature in lookupTable[layer].data[worldview]) {
                const featureData = lookupTable[layer].data[worldview][feature];
                // Filter the lookup data for the US
                if (featureData.iso_3166_1 === 'US') {
                  // Use `unit_code` property that has the FIPS code as the lookup key
                  lookupData[featureData['unit_code']] = featureData;
                }
              }
          return lookupData;
        }

        // Add Mapbox Boundaries source for state polygons.
        map.addSource('statesData', {
          type: 'vector',
          url: 'mapbox://mapbox.boundaries-adm1-v3'
        });

        // Add layer from the vector tile source with data-driven style
        // Use a feature-state dependent expression to compute the green color band based on
        //  the unemployment percentage
        map.addLayer(
          {
            id: 'states-join',
            type: 'fill',
            source: 'statesData',
            'source-layer': 'boundaries_admin_1',
            paint: {
              'fill-color': [
                'case',
                ['!=', ['feature-state', 'unemployment'], null],
                [
                  'interpolate',
                  ['linear'],
                  ['feature-state', 'unemployment'],
                  4,
                  'rgba(222,235,247,1)',
                  14,
                  'rgba(49,130,189,1)'
                ],
                'rgba(255, 255, 255, 0)'
              ]
            }
          },
          'waterway-label'
        );

        // Join the JSON unemployment data with the corresponding vector features where
        // feature.unit_code === `STATE_ID`.
        function setStates() {
          for (const item of localData) {
            map.setFeatureState(
              {
                source: 'statesData',
                sourceLayer: 'boundaries_admin_1',
                id: lookupData[item.STATE_ID].feature_id
              },
              {
                unemployment: item.unemployment
              }
            );
          }
        }

        // Check if `statesData` source is loaded.
        function setAfterLoad(event) {
          if (event.sourceID !== 'statesData' && !event.isSourceLoaded) return;
          setStates();
          map.off('sourcedata', setAfterLoad);
        }

        // If `statesData` source is loaded, call `setStates()`.
        if (map.isSourceLoaded('statesData')) {
          setStates();
        } else {
          map.on('sourcedata', setAfterLoad);
        }
      }
    </script>
  </body>
</html>
```

## Next steps

Learn more about how you can use Mapbox Boundaries.

Explore our other Mapbox Boundaries tutorials:

-   [Point-in-polygon query with Mapbox Boundaries](https://docs.mapbox.com/help/ja/tutorials/point-in-polygon-query-with-mapbox-boundaries/): Determine what polygons exist at a single point using the Mapbox Tilequery API.

You can also explore this [example](https://demos.mapbox.com/boundaries-explorer/), which uses the concepts outlined in both this data-join tutorial and the [Point-in-polygon query](https://docs.mapbox.com/help/ja/tutorials/point-in-polygon-query-with-mapbox-boundaries/) tutorial to create an application that features an interactive choropleth map.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--data-joins-with-mapbox-boundaries--choropleth-demo-75969d4a96c17ba855302dbbddb47ad0.mp4).