Skip to main content

Create a temperature map

In this example, we'll process GFS temperature data from NOAA using Raster MTS. This is a multi-band dataset where each band is a single timestamp.

The goal is to create a raster-array source and add it on a map to visualize temperature.

Prerequisites

To follow along, you'll need:

  • GFS data (sample download below)
arrow-downSample GFS temperature data

Environment variables

  • ACCOUNT - Set this to your account username.
  • ENDPOINT - The path to the Mapbox tilesets API.
  • RECIPE_LOCATION - The local file location of your Raster MTS recipe.
  • SOURCE_NAME - The name used to create your tileset source. Once your tileset source has been created, you must reference that returned tileset source URI in your recipe.
  • SOURCE_LOCATION - The local file location of your raster source.
  • TILESET_ID - A unique identifier given to your tileset. A tileset ID always starts with your Mapbox username, followed by the tileset's unique alphanumeric identifier: username.identifier.
  • MAPBOX_ACCESS_TOKEN - A secret token for your Mapbox account with tilesets:write (for uploading source, creating tileset and publish job) and tilesets:read scope (for getting job status).

We'll reference these values in later steps.

ACCOUNT=your-account-name
ENDPOINT="https://api.mapbox.com/tilesets/v1"
RECIPE_LOCATION="./gfs-temperature.json"
SOURCE_NAME=gfs-temperature-source
SOURCE_LOCATION="./gfs-temperature.nc"
TILESET_ID=your-account-name.gfs-temperature
MAPBOX_ACCESS_TOKEN="<Insert your own sk.* token here>"

Review raster source metadata

The GDAL tool gdalinfo is a useful tool for understanding the structure of your data.

Running gdalinfo on this sample source, we see the (truncated) output:

$ gdalinfo gfs-temperature.nc
...
Band 1 Block=1440x45 Type=Float32, ColorInterp=Undefined
NoData Value=-9999
Unit Type: K
Metadata:
amip=ta
coordinates=reftime time height_above_ground3 latitude longitude
description=Air temperature is the bulk temperature of the air, not the surface (skin) temperature.
grib=11 E130
Grib2_Generating_Process_Type=Forecast
Grib2_Level_Desc=Specified height level above ground
Grib2_Level_Type=103
Grib2_Parameter={0,0,0}
Grib2_Parameter_Category=Temperature
Grib2_Parameter_Discipline=Meteorological products
Grib2_Parameter_Name=Temperature
Grib2_Statistical_Process_Type=UnknownStatType--1
Grib_Variable_Id=VAR_0-0-0_L103
grid_mapping=LatLon_721X1440-0p13S-180p00E
long_name=Temperature @ Specified height level above ground
NETCDF_DIM_height_above_ground3=2
NETCDF_DIM_time=0
NETCDF_VARNAME=Temperature_height_above_ground
standard_name=air_temperature
units=K
_FillValue=-9999

Create a tileset source

Next, we'll create a Tileset Source by uploading our GFS data to MTS. If you're using the API directly, you'll need to make a request for each individual source file that you'll be referencing in your recipe. In the example GFS data that we're processing we only have a single source file, so we'll make the following POST request once.

curl \
-X POST "${ENDPOINT}/sources/${ACCOUNT}/${SOURCE_NAME}?access_token=${MAPBOX_ACCESS_TOKEN}" \
-F file=@${SOURCE_LOCATION} \
--header "Content-Type: multipart/form-data"

Create a recipe

A recipe is a set of instructions used for processing the raster data.

{
"version": 1,
"type": "rasterarray",
"sources": [
{
"uri": "mapbox://tileset-source/{ACCOUNT}/{SOURCE_NAME}"
}
],
"minzoom": 0,
"maxzoom": 3,
"layers": {
// Layer name "2t" is based on naming conventions from https://cfconventions.org/
// 2t represents air temperature at 2 meters above ground.
"2t": {
"tilesize": 256,
"offset": -100,
"scale": 0.1,
"resampling": "bilinear",
"buffer": 1,
"units": "K",
"source_rules": {
"filter":[
"all",
["==",["get","NETCDF_VARNAME"],"Temperature_height_above_ground"],
["==",["get","NETCDF_DIM_height_above_ground3"],"2"]
],
"name":["to-number",["get","NETCDF_DIM_time"]],
"order":"asc"
}
}
}
}

Note

The source_rules field allows you to select a single band within a raster file. For example, in this recipe, the temperature data exists in a band labeled with "Temperature_height_above_ground".

The following construct indicates that Raster MTS should only process bands that have a NETCDF_VARNAME that includes Temperature_height_above_ground where the NETCDF_DIM_height_above_ground3 dimension is 2.

"filter":[
"all",
["==",["get","NETCDF_VARNAME"],"Temperature_height_above_ground"],
["==",["get","NETCDF_DIM_height_above_ground3"],"2"]
],

We then name the constructed slice of data with:

"name":["to-number",["get","NETCDF_DIM_time"]],

These value will be later available as "band" used to add on the map.

And then organize those distinct slices of data with:

"sort_key":["to-number",["get","NETCDF_DIM_time"]],
"order":"asc"

Create an empty tileset

Create an empty tileset and provide the recipe to be used for processing.

curl \
-X POST "${ENDPOINT}/${TILESET_ID}?access_token=${MAPBOX_ACCESS_TOKEN}" \
-d @"${RECIPE_LOCATION}" \
--header "Content-Type:application/json"

Note

If you're using the API directly, you'll need to wrap your recipe in an additional JSON structure to include a recipe and name property.

{
"recipe": {...},
"name": "example_tileset_name"
}

Publish a tileset

At this point you have uploaded a source, created an empty tileset, and provided a recipe to be used for processing the source to this tileset. Now you can start processing your tileset by publishing it.

curl -X POST "${ENDPOINT}/${TILESET_ID}/publish?access_token=${MAPBOX_ACCESS_TOKEN}"

Review tileset job information

At this point, Raster MTS will begin to process your job with a job_id. You can retrieve your job's information by calling the API endpoint below with your job_id.

curl "\${ENDPOINT}/\${TILESET_ID}/jobs/{job_id}?access_token=\${MAPBOX_ACCESS_TOKEN}"

Review tileset TileJSON

Once the tileset is finished processing, you can retrieve TileJSON for this tileset, which stores all the useful metadata that can be used for map visualizations.

curl "\${ENDPOINT}/\${TILESET_ID}.json?access_token=\${MAPBOX_ACCESS_TOKEN}"

Below is the (truncated) output

"raster_layers": [
{
"fields": {
// Values extracted from NetCDF's "NETCDF_DIM_time" variables become bands
// Used in "raster-array-band" paint property
"bands": ["0", "3", "6"...],
"buffer": 1,
"name": "2t",
"offset": 0,
// Used in "raster-color-range" paint property
"range": [
204.5353240966797,
323.64263916015625
],
"scale": 1,
"tilesize": 256,
"units": ""
},
// Used in "raster-array-layer" layout property
"id": "2t",
"maxzoom": 3,
"minzoom": 0
}
],

The id, band and range keys in the TileJSON identify which layer and band to display and how to assign colors to the values.

Add tileset to map

Using this tileset as raster-array source we will add it as a raster layer on map.

We will use raster-array-band to allot value 3, which represents the band named 3 in the source NetCDF file. We will use paint properties to assign color using Mapbox GL JS.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Create a temperature map</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.5.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.5.1/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v11',
zoom: 0,
center: [0, 0],
projection: 'mercator'
});

map.on('load', () => {
map.addSource('gfs-temperature', {
type: 'raster-array',
// Replace this URL with a 'mapbox://TILESET_ID'
url: 'mapbox://mapbox.gfs-temperature',
tileSize: 256
});
map.addLayer({
id: 'gfs-temperature-layer',
type: 'raster',
source: 'gfs-temperature',
// If your tileset has multiple layers, you can specify which one to use with the 'source-layer'.
// In this example we're specifying the '2t' temperature layer defined in the recipe above.
'source-layer': '2t',
paint: {
'raster-color-range': [204, 323],
'raster-array-band': '3',
'raster-color': [
'interpolate',
['linear'],
['raster-value'],
204,
'#50509B',
266,
'#FAFAA0',
323,
'#96053C'
],
'raster-resampling': 'nearest'
}
});
});
</script>

</body>
</html>
Was this example helpful?