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)
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 withtilesets:write
(for uploading source, creating tileset and publish job) andtilesets: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"
tilesets upload-raster-source $ACCOUNT $SOURCE_NAME $SOURCE_LOCATION
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"
}
}
}
}
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"
tilesets create $TILESET_ID \
--recipe $RECIPE_LOCATION \
--name "tilesets example tileset"
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}"
tilesets publish $TILESET_ID
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>