> ## Content Index
> Fetch the complete content index at: https://blog.afi.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Uber h3 js tutorial: How to draw hexagons on a map
- URL: https://blog.afi.io/blog/uber-h3-js-tutorial-how-to-draw-hexagons-on-a-map/
- Published: 2023-07-14T04:24:26.000Z
- Updated: 2025-08-15T18:24:18.000Z
- Description: A gentle introduction to the Uber h3-js library with worked examples.
- Author: Afian Anwar
- Tags: tech

I thought it would be fun to go outside of my comfort zone and write about something completely different and new. So today, I'm going to show you how to use the [h3-js library](https://github.com/uber/h3-js?ref=blog.afi.io) to draw hexagons on a map! Here's what we are going to build by the end of this series - an interactive map of taxi demand across Singapore.

![Taxi demand in Singapore visualized using the Uber h3-js library and h3 map](https://storage.ghost.io/c/c6/4d/c64da7e8-63a6-4cff-acdc-2782a6ebc377/content/images/2023/07/Screen-Shot-2023-07-06-at-1.32.03-AM.png)

Taxi demand across Singapore from 6 am to 10 am on a weekday

**Part 1: Uber h3 js tutorial: How to draw hexagons on a map (this article)**  
Part 2: [Mapping taxi demand with Uber h3 js and react map gl](https://afi.io/blog/mapping-taxi-demand-with-uber-h3-js-and-react-map-gl/?ref=blog.afi.io)  
Part 3: [Visualizing taxi demand over time with Mapbox and React range slider rc slider](https://afi.io/blog/visualizing-taxi-demand-over-time-with-mapbox-and-react-slider-rc-slider/?ref=blog.afi.io)

Recently, I had the opportunity to use h3-js in a data visualization project for a client, and I wanted to share what I've learned. For those of you not familiar with h3-js, it's the JavaScript port of [H3](https://uber.github.io/h3?ref=blog.afi.io), Uber's hexagon-based geospatial indexing system. Hexagons (unlike say, squares or triangles) approximate various shapes like neighborhoods and city blocks more accurately. This flexibility makes H3 perfect for diverse geospatial use cases, including transportation, urban planning, logistics, and location-based services (also - [Hexagons are the Bestagons](https://www.youtube.com/watch?v=thOifuHs6eY&ref=blog.afi.io)).

> [Afi Labs](https://www.afi.io/?ref=blog.afi.io) delivers custom software to meet your logistics needs. From vehicle routing, live tracking to proof of delivery, we've got you covered!   
> 
> [👋 Say Hello!](https://afi.io/?ref=blog.afi.io#contact-us) 

I've created a new [Github repository with the examples from this post](https://github.com/afilabs/h3js%5Fhexagons/?ref=blog.afi.io), all based on h3-js. To try them out, clone the repository, run `npm install` followed by `npm start` .

To make the most of this tutorial, you need to have basic experience writing applications in JavaScript in general and [React](https://react.dev/?ref=blog.afi.io), specifically. This is because we'll be using the amazing [react-map-gl](https://github.com/visgl/react-map-gl?ref=blog.afi.io) library to render our [Mapbox](https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/?ref=blog.afi.io) base map and programmatically add data to it, so in some ways this post doubles up as a React and react-map-gl tutorial as well. Let's get started.

### Introduction to the h3-js API

The core of the h3-js library are the functions that provide the H3 index for geographic coordinates and vice versa. A H3 index e.g. "876520c86ffffff" is the unique "id" of the hexagon, which is defined by two pieces of information: a center coordinate and the "size" or resolution. The higher the resolution of the grid, the smaller the hexagons, from res 0 (continental) to res 15 (1 square meter). Res 9 is roughly a city block. For this tutorial, we'll be using res 7 which roughly corresponds to a large neighborhood.

First, import the required methods from h3-js (note: all the demo code listed here can be found in `h3ExampleMethods.js` in the [Github repository](https://github.com/afilabs/h3js%5Fhexagons/?ref=blog.afi.io):

```
/* h3ExampleMethods.js */
const { cellToLatLng, latLngToCell, cellToBoundary } = require('h3-js');

```

### cellToLatLng

To convert a H3 index to a latitude and longitude, we use the `cellToLatLng()` method ([API reference](https://github.com/uber/h3-js?ref=blog.afi.io#module%5Fh3.cellToLatLng)) to obtain the center of the hexagon:

**cellToLatLng(h3Index) -> `CoordPair`**

**Input**

For a given h3Index `H3IndexInput` string:

```
/* h3ExampleMethods.js */
function cellToLatLngExample() {
  const cell = '876520d95ffffff';
  return cellToLatLng(cell);
}
```

**Output**

Returns the center coordinate as a `CoordPair` \[lat, lng\] pair:

```
[ 1.3049570274662716, 103.84333471371895 ]
```

![h3 map that uses the hexagon geospatial library h3-js](https://storage.ghost.io/c/c6/4d/c64da7e8-63a6-4cff-acdc-2782a6ebc377/content/images/2023/07/Screen-Shot-2023-07-09-at-11.48.46-PM.png)

### latLngToCell

The inverse of `cellLatToLng()` is `latLngToCell()` ([API reference](https://github.com/uber/h3-js?ref=blog.afi.io#module%5Fh3.latLngToCell)) which takes in a latitude and longitude pair together with a specified resolution and returns the H3 index of the hexagon that it belongs to.

**latLngToCell(lat, lng, res) -> `H3Index`**

**Input**

For a lat `number` (latitude), lng `number` (longitude) and res `number` (resolution):

```
/* h3ExampleMethods.js */
function latLngToCellExample() {
    // Newton Hawker Center
    const lat = 1.3119888;
    const lng = 103.8369993;
    const res = 7;

    return latLngToCell(lat, lng, res);
}
```

**Output**

Returns the `H3Index` string of the containing hexagon.

```
876520d95ffffff
```

![h3 map created using the h3-js library](https://storage.ghost.io/c/c6/4d/c64da7e8-63a6-4cff-acdc-2782a6ebc377/content/images/2023/07/Screen-Shot-2023-07-09-at-11.48.04-PM.png)

### cellToBoundary

To draw the H3 cell on a map, you need the coordinates of each vertex. The `cellToBoundary()` method ([API reference](https://github.com/uber/h3-js?ref=blog.afi.io#module%5Fh3.cellToBoundary)) accepts a `H3Index` string and returns and array of \[lat, lng\] pairs corresponding to the cell's vertices.

**cellToBoundary(h3Index) -> `Array.<CoordPair>`**

**Input**

For a given h3Index `H3IndexInput` string:

```
/* h3ExampleMethods.js */
function cellToBoundaryExample() {
    const cell = '876520d95ffffff';

    return cellToBoundary(cell);
}
```

**Output**

```
[
  [ 1.3013837085594995, 103.85630417742213 ],
  [ 1.314435751149279, 103.85314992083924 ],
  [ 1.3180097264283908, 103.84017971194217 ],
  [ 1.3085305652330135, 103.83036417616988 ],
  [ 1.295478307425089, 103.8335196239885 ],
  [ 1.2919054258381997, 103.84648941655875 ]
]
```

Returns the vertices of the H3 cell as an `Array.<CoordPair>` i.e. an array of \[lat, lng\] pairs.

![hexagon example of a h3 map that uses the Uber h3 js indexing system](https://storage.ghost.io/c/c6/4d/c64da7e8-63a6-4cff-acdc-2782a6ebc377/content/images/2023/07/Screen-Shot-2023-07-10-at-12.17.12-AM.png)

To run these examples, [clone the Github repository](https://github.com/afilabs/h3js%5Fhexagons/?ref=blog.afi.io) and in your terminal, navigate to the `/src/tasks` folder and run `npx run-func h3ExampleMethods.js <method_name>` e.g. `npx run-func h3ExampleMethods.js cellToBoundaryExample`.

![](https://storage.ghost.io/c/c6/4d/c64da7e8-63a6-4cff-acdc-2782a6ebc377/content/images/2023/07/Screen-Shot-2023-07-10-at-10.07.18-PM.png)

With these building blocks in place - `cellToLatLng()`, `latLngToCell()` and `cellToBoundary()`, we can start drawing hexagons on a map. 

### How our code is organized

Like most projects on this blog, we scaffolded this one using the [Create React App](https://create-react-app.dev/?ref=blog.afi.io) with code that follows this standard folder structure:

```html
my-app/
  README.md
  node_modules/
  package.json
  public/
  src/
    components/
       MapBoxSingleHex.jsx
    tasks/
       h3ExampleMethods.js
    App.css
    App.js
    App.test.js
    index.css
    index.jsx
    logo.svg
```

The new files in the `/src` folder are `tasks/h3ExampleMethods.js` which we used to test the H3 indexing methods described earlier and `components/MapBoxSingleHex.jsx` which will be our main focus going forward.

### MapBoxSingleHex.jsx

`/components/MapBoxSingleHex.jsx` is made available to the user from `index.jsx`, the entry point to our React app by importing the file:

```
/* index.jsx */
import MapBoxSvg from './components/MapBoxSingleHex';

```

and rendering it:

```
/* index.jsx */
root.render(
  <React.StrictMode>
    <MapBoxSvg />
  </React.StrictMode>
);

```

The `MapBoxSvg` component, which implements the [react-map-gl](https://github.com/visgl/react-map-gl?ref=blog.afi.io) map layer to draw the hexagon, is shown below (you can download and run this code by visiting the [h3js\_hexagons](https://github.com/afilabs/h3js%5Fhexagons/?ref=blog.afi.io) Github repository).

```
/* mapBoxSingleHex.jsx */
import "mapbox-gl/dist/mapbox-gl.css";
import Map, { Layer, Source, MapRef, Marker } from "react-map-gl";
import React, {useRef, useState } from "react";
import { cellToBoundary } from "h3-js";

function MapBox() {

  const hexindex7Id = "876520d95ffffff";

  return (
      <div>
        <div className="map">
          <Map
            initialViewState={{
              latitude: 1.290270,
              longitude: 103.851959,
              zoom: 10,
              bearing: 0,
              pitch: 0,
            }}
            mapStyle="mapbox://styles/mapbox/light-v9"
            mapboxAccessToken="YOUR_MAPBOX_ACCESS_TOKEN"
            style={{
              height: "100vh",
              width: "100vw",
            }}
          >
            <Source
              type="geojson"
              data= {{
                "type": "Feature",
                "geometry": {
                  "type": "Polygon",
                  "coordinates": [cellToBoundary(hexindex7Id, true)]
                },
                "id": "abc123"
              }}
            >
              <Layer
                {...{
                  type: "fill",
                  paint: {
                    'fill-outline-color': 'white',
                    "fill-color": "#E14C48",
                    "fill-opacity": 0.7,
                  },
                }}
              />
              
            </Source>
          </Map>
        </div> 
      </div>
  );

};

export default MapBox;

```

Let's work through the code to figure out what's going on.

```
/* mapBoxSingleHex.jsx */
import "mapbox-gl/dist/mapbox-gl.css";
import Map, { Layer, Source, MapRef, Marker } from "react-map-gl";
import React, {useRef, useState } from "react";
import { cellToBoundary } from "h3-js";

```

Read from top to bottom, this imports:

- The default Mapbox GL stylesheet `mapbox-gl/dist/mapbox-gl.css`.
- The `Map, { Layer, Source, MapRef, Marker }` components from [react-map-gl](https://visgl.github.io/react-map-gl/?ref=blog.afi.io).
- Two specific hooks ( `useRef` and `useState`) used by React to create and manage state, as well as reference DOM elements.
- The `cellToBoundary()` method from [h3-js](https://github.com/uber/h3-js?ref=blog.afi.io) that returns the H3 cell's vertex coordinates.

The heavy lifting is done in the `MapBox()` function:

```
/* mapBoxSingleHex.jsx */
function MapBox() {

  const hexindex7Id = "876520d95ffffff";

  return (
      <div>
        <div className="map">
          <Map
            // Map component code
          >
            <Source
              // Source component code
            >
              <Layer
                // Layer component code
              />
            </Source>
          </Map>
        </div> 
      </div>
  );
};

```

First, we store the H3 index of the cell we want to display:

```
/* mapBoxSingleHex.jsx */
function MapBox() {

  const hexindex7Id = "876520d95ffffff";

  // Rest of MapBox() code
};

```

Next, we return a JSX component (a virtual representation of HTML that can be easily combined with JavaScript expressions) that contains the map layer.

```
/* mapBoxSingleHex.jsx */
function MapBox() {

  // Earlier code

  return (
      <div>
        <div className="map">
          <Map
            // Map component code
          >
            <Source
              // Source component code
            >
              <Layer
                // Layer component code
              />
            </Source>
          </Map>
        </div> 
      </div>
  );
};

```

This React style code is very different from your typical vanilla JavaScript implementation of Mapbox GL (e.g. [add a polygon to a map using a GeoJSON source](https://docs.mapbox.com/mapbox-gl-js/example/geojson-polygon/?ref=blog.afi.io)) which requires you to use the `addSource()` function to add GeoJSON data to a polygon before calling `addLayer()` to create a new fill layer and applies paint properties to style the polygon's appearance.

Instead, because [react-map-gl](https://visgl.github.io/react-map-gl/?ref=blog.afi.io) gives us a ready made collection of React components that play well with Mapbox, we can inject data and style our map using [Source](https://visgl.github.io/react-map-gl/docs/api-reference/source?ref=blog.afi.io) and [Layer](https://visgl.github.io/react-map-gl/docs/api-reference/layer?ref=blog.afi.io) components like this:

```html
<Map>
  <Source>
    <Layer/>
  </Source>
</Map>
```

I'll walk through each component individually and explain what each does.

**<Map/>**

```
/* mapBoxSingleHex.jsx */

          <Map
            initialViewState={{
              latitude: 1.290270,
              longitude: 103.851959,
              zoom: 10,
              bearing: 0,
              pitch: 0,
            }}
            mapStyle="mapbox://styles/mapbox/light-v9"
            mapboxAccessToken="MAPBOX_ACCESS_TOKEN"
            style={{
              height: "100vh",
              width: "100vw",
            }}
          >
            <Source>
              <Layer/>
            </Source>
          </Map>
 

```

This initializes our map with the `light-v9` style and centers it at ([1.290270, 103.851959](https://www.google.com/maps/place/1%C2%B017'25.0%22N+103%C2%B051'07.1%22E/@1.29027,103.8493841,17z/data=!3m1!4b1!4m4!3m3!8m2!3d1.29027!4d103.851959?entry=ttu&ref=blog.afi.io)) in downtown Singapore. The `height` and `width` parameters "\`100vh", "100vw" stretch the map to occupy the entire page.

**<Source/>**

```
/* mapBoxSingleHex.jsx */

          <Source
              type="geojson"
              data= {{
                "type": "Feature",
                "geometry": {
                  "type": "Polygon",
                  "coordinates": [cellToBoundary(hexindex7Id, true)]
                },
                "id": "abc123"
              }}
            >
              <Layer/>
            </Source>
            

```

The <Source/> component stores data that we can display on the map using <Layer/>. In this example, we specify that the data `type` is "[geojson](https://geojson.org/?ref=blog.afi.io)" (a format for encoding a variety of geographic data structures such as points, lines, polygons etc), with `geometry` "Polygon" and vertices (`coordinates`) set to `cellToBoundary("876520d95ffffff")` which returns an array of latitude and longitude points.

**<Layer/>**

```
/* mapBoxSingleHex.jsx */
              <Layer
                {...{
                  type: "fill",
                  paint: {
                    'fill-outline-color': 'white',
                    "fill-color": "#E14C48",
                    "fill-opacity": 0.7,
                  },
                }}
              />
            

```

Now we can begin styling the <Source/> data set with the <Layer/> component. Here, we use ES6 destructuring ("...") to unpack the [style object](https://docs.mapbox.com/help/glossary/style/?ref=blog.afi.io) and inject it into <Layer/>. We instruct Mapbox to draw the polygon with `fill-color` "#E14C48" (dark red) and give it a `fill-opacity` of 0.7 (semi transparent) and `fill-outline-color` "white" (white border).

Here's the final result:

![Single hexagon drawn using the Uber h3-js library and react-map-gl](https://storage.ghost.io/c/c6/4d/c64da7e8-63a6-4cff-acdc-2782a6ebc377/content/images/2023/07/Screen-Shot-2023-07-13-at-10.49.23-AM.png)

So that's how you draw a hexagon on a map in React using the Uber [h3-js](https://github.com/uber/h3-js?ref=blog.afi.io) library and [react-map-gl](https://github.com/visgl/react-map-gl?ref=blog.afi.io). This isn't too useful right now, but in the next section I'll show you how to use what we learned in this post to build a heatmap of taxi demand in Singapore.

👋 **As always, if you have any questions or suggestions for me, please** [**reach out**](https://yourls.afi.io/newcontact?ref=blog.afi.io) **or** [**say hello on LinkedIn**](https://www.linkedin.com/in/afian-anwar-a023b143/?ref=the-afi-labs-blog)**.**

Next: [Part 2: Mapping taxi demand with Uber h3 js and react map gl](https://afi.io/blog/mapping-taxi-demand-with-uber-h3-js-and-react-map-gl/?ref=blog.afi.io)