Publication cloud native - raster¶
Cette publication convient à un indicateur raster produit par une chaîne de traitement : une température de surface, une classification, ou tout autre indicateur raster. Vous livrez deux choses :
- des fichiers COG sur S3 : les pixels, lus par TiTiler pour générer les tuiles à la volée
- une description STAC de ce fichier : où il se trouve, comment le colorier, où le ranger dans le géoportail.
La plateforme fait le reste. Le géoportail lit le catalogue STAC et demande les tuiles à
TiTiler, qui les calcule à la volée à partir de
votre COG. Vous n'avez rien à préparer en plus : pas de projet QGIS, pas de style .qml,
pas de tuiles pré-calculées.
graph LR
T[Votre traitement] -->|1-2. COG| S[(S3)]
T -->|3-4. Collection + Item| C[Catalogue STAC]
C --> G[Géoportail]
G -->|tuiles, valeur au clic| TT[TiTiler]
TT -->|lit le COG| S
Chaque morceau de la description a un rôle précis :
| Vous fournissez | À quoi ça sert |
|---|---|
| le COG sur S3 | les valeurs des pixels, lues par TiTiler |
| l'Item STAC (une date) | où est le COG, comment l'afficher, sa ville, son indicateur, sa date, sa description |
| la Collection STAC (indicateur × ville × producteur) | le producteur (qui donne les droits d'accès), la licence, les libellés lisibles |
Les quatre étapes sont détaillées ci-dessous. Un exemple complet et une check-list terminent la page.
1. Produire un COG¶
Un Cloud Optimized GeoTIFF est un GeoTIFF organisé pour être lu par morceaux. TiTiler n'en lit que la portion utile à chaque tuile : c'est ce qui rend l'affichage rapide, même sur un gros fichier.
Pour produire vos COG, la plateforme met à votre disposition AntFlow : vous y exécutez votre chaîne de traitement (packagée en CWL), qui exécute votre workflow ou vourrez produire vos COG et faire le stockage programmatiquement dans le depot S3. Le guide Processing explique comment construire et lancer vos chaînes. sur S3. Le guide Processing explique comment construire et lancer cette chaîne.
2. Déposer le COG sur S3¶
Déposez le fichier dans un bucket de la plateforme (voir Stocker mes données). Le chemin est libre, mais suivez de préférence la convention commune :
s3://<votre-bucket>/votre-arborescence/<ville>/<indicateur>/<date>/<fichier>.tif
s3://value-adder/map-products/metropole-grand-nancy/ndvi/20260627/ndvi_20260627.tif
TiTiler lit ce fichier directement, avec son compte de service. Vérifiez qu'il y arrive en ouvrant cette URL : elle doit renvoyer l'emprise et le type du raster.
Si TiTiler ne peut pas lire votre bucket, demandez l'accès à l'IF Operator.
3. Décrire le COG en STAC¶
Les extensions à déclarer¶
Une extension STAC est un ensemble de champs normalisés. Chacune doit être déclarée
dans le tableau stac_extensions de l'Item, par l'URL de son schéma.
| Extension | Statut | Ce qu'elle apporte |
|---|---|---|
UrbiVerde v1.0.0 |
obligatoire | ville, indicateur, date, producteur : le rangement dans le géoportail |
Render v2.0.0 |
obligatoire | comment afficher le raster : une couche du géoportail par render |
Alternate Assets v1.2.0 |
obligatoire | l'adresse s3:// du COG, celle que lit TiTiler |
Classification v2.0.0 |
recommandée pour des classes | le nom de chaque classe, repris dans la légende |
Projection v2.0.0, Raster v2.0.0 |
recommandées | CRS, résolution, statistiques des bandes |
"stac_extensions": [
"https://stac-extensions.github.io/projection/v2.0.0/schema.json",
"https://stac-extensions.github.io/raster/v2.0.0/schema.json",
"https://stac-extensions.github.io/render/v2.0.0/schema.json",
"https://stac-extensions.github.io/alternate-assets/v1.2.0/schema.json",
"https://stac-extensions.github.io/classification/v2.0.0/schema.json",
"https://stac.urbiverde.com/extensions/urbiverde/v1.0.0/schema.json"
]
La Collection : indicateur × ville × producteur¶
Une Collection regroupe toutes les dates d'un indicateur, sur une ville, pour un producteur. Vous la créez une fois, à la première date publiée.
| Champ | Valeur | Exemple |
|---|---|---|
id |
<indicateur>-<ville>-<producteur>, en minuscules, sans accent, mots séparés par - |
ndvi-metropole-grand-nancy-cs-group |
description |
ce que représente l'indicateur ; affichée si l'Item n'a pas la sienne | NDVI Sentinel-2 sur… |
license |
identifiant SPDX (etalab-2.0, CC-BY-4.0…), sinon other avec un lien rel: license |
other |
providers |
au moins {"name": …, "roles": ["producer"]} : c'est lui qui donne les droits d'accès |
CS Group |
keywords |
affichés dans la fiche de la couche | ["ndvi", "sentinel-2"] |
urbiverde:city, urbiverde:indicator, urbiverde:producer |
les mêmes slugs que dans les Items | metropole-grand-nancy, ndvi, cs-group |
urbiverde:city_label, urbiverde:indicator_label |
les noms lisibles | Métropole du Grand Nancy, NDVI |
urbiverde:show_in_geoportal |
facultatif : false masque toute la Collection du géoportail |
true |
Le nom du producteur ne change jamais
L'accès au géoportail est accordé par ville et par producteur. Le producteur est le
name de providers de rôle producer. Écrivez-le exactement de la même façon
dans toutes vos Collections, et demandez à l'équipe UrbiVerde le groupe qui ouvre
l'accès à vos utilisateurs.
L'Item : une date¶
| Champ | Valeur | Pourquoi |
|---|---|---|
id |
<id de la Collection>-<date> |
ndvi-metropole-grand-nancy-cs-group-20260627 |
collection |
l'id de la Collection |
rattache l'Item à son producteur, donc à ses droits |
geometry, bbox |
emprise en WGS 84 (EPSG:4326) | la couche est découpée sur la bbox dans le géoportail |
datetime, ou start_datetime + end_datetime |
période observée : datetime seul pour une acquisition, start_datetime + end_datetime pour une année ou un mois |
recherche temporelle et 3e niveau du géoportail : 2026-06-27, 2022-06 ou 2022 |
properties.description |
ce que montre cette date : source, millésime, méthode | texte affiché sur la couche |
properties.urbiverde:city |
la ville, en slug | 1er niveau du géoportail |
properties.urbiverde:indicator |
l'indicateur, en slug | 2e niveau du géoportail |
properties.urbiverde:producer |
le producteur, en slug | recherche par producteur |
properties.renders |
voir plus bas | les couches affichées |
assets |
voir ci-dessous | le COG |
La date affichée vient de la période : une année entière (2022-01-01 au 2022-12-31)
s'affiche 2022, un mois entier 2022-06, sinon le jour (2026-06-27). Les dates sont
triées par début de période.
L'asset : où se trouve le COG¶
L'asset décrit le fichier. TiTiler le lit à l'adresse donnée par alternate.s3.href.
Le href principal peut rester relatif : il pointe sur le fichier posé à côté de l'Item.
"assets": {
"ndvi": {
"href": "./ndvi_20260627.tif",
"alternate": {
"s3": { "href": "s3://cs-group/map-products/metropole-grand-nancy/ndvi/20260627/ndvi_20260627.tif" }
},
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
"roles": ["data"],
"bands": [{ "data_type": "float32", "nodata": "nan" }]
}
}
Sans alternate.s3.href en s3://, aucune couche n'est créée pour cet asset.
Les renders : comment afficher¶
Un render est une recette d'affichage. Chaque render devient une couche du
géoportail, nommée <indicateur> · <ville> · <date> · <title du render>. Un même COG
peut donc donner plusieurs couches : le NDVI en dégradé, et le NDVI en classes.
| Champ | Rôle |
|---|---|
assets |
exactement un asset (le COG à lire) |
title |
nom de la couche |
colormap_name + rescale |
dégradé de couleurs et ses bornes, pour une valeur continue |
colormap |
une couleur par valeur entière, pour des classes : un objet {"0": [r, g, b, a], …} |
expression |
calcul appliqué aux pixels avant affichage, en texte (b1 = 1re bande) |
nodata, resampling, bidx |
valeur transparente, rééchantillonnage, bandes à lire |
minmax_zoom |
facultatif : niveaux de zoom d'affichage ; par défaut, à partir du zoom 8 |
Deux recettes couvrent la plupart des cas :
"ndvi": {
"title": "NDVI (RdYlGn)",
"assets": ["ndvi"],
"rescale": [[-1, 1]],
"colormap_name": "rdylgn",
"resampling": "nearest"
}
Dans le géoportail :
- la légende est une barre de couleurs, avec
1en haut et-1en bas (les bornes derescale) ; - un clic sur la carte affiche la valeur du pixel :
NDVI (RdYlGn) : 0.066.
Les noms de dégradés disponibles sont listés sur https://titiler.urbiverde.com/colorMaps.
"ndvi_classes": {
"title": "NDVI classes",
"assets": ["ndvi"],
"expression": "where(b1<0,0,where(b1<0.2,1,where(b1<0.4,2,where(b1<0.6,3,4))))",
"colormap": {
"0": [165, 0, 38, 255],
"1": [244, 109, 67, 255],
"2": [255, 255, 191, 255],
"3": [134, 188, 106, 255],
"4": [0, 104, 55, 255]
},
"resampling": "nearest"
}
L'expression range chaque pixel dans une classe : « si b1 < 0 alors 0, sinon si
b1 < 0.2 alors 1… ». La colormap donne la couleur de chaque classe.
Pour nommer les classes, ajoutez sur la bande de l'asset l'extension Classification :
"bands": [{
"data_type": "float32",
"nodata": "nan",
"classification:classes": [
{ "value": 0, "name": "water", "title": "Eau / sans végétation", "color_hint": "A50026" },
{ "value": 4, "name": "dense", "title": "Végétation dense", "color_hint": "006837" }
]
}]
Le géoportail combine les deux sources dans la légende et au clic :
| Classe | title (Classification) |
Seuils (expression) |
Libellé affiché |
|---|---|---|---|
| 0 | Eau / sans végétation | b1 < 0 |
Eau / sans végétation (< 0) |
| 1 | - | 0 ≤ b1 < 0.2 |
0 – 0.2 |
| 4 | Végétation dense | b1 ≥ 0.6 |
Végétation dense (≥ 0.6) |
Sans l'un ni l'autre, le libellé est la valeur de la classe (1). Les seuils ne
sont lus que sous la forme where(b1<seuil, classe, …) imbriquée, avec des seuils
croissants.
Si votre COG contient déjà des classes (des entiers), il suffit d'une
colormap, sans expression.
Les erreurs qui font disparaître une couche
colormapen liste ([[[-1, 0], [r, g, b, a]], …]) : TiTiler l'accepte, mais l'Item n'est plus valide pour l'extension Render. Utilisez un objet, et uneexpressionpour former les classes.- Plusieurs assets dans un render : la couche est ignorée. Un render lit un seul COG.
expressionqui n'est pas du texte : la couche est ignorée.
Une couche ignorée ne bloque jamais le reste du catalogue : les autres couches s'affichent normalement.
4. Enregistrer dans le catalogue¶
Le catalogue https://stac.urbiverde.com accepte l'écriture (extension Transactions). Créez la Collection une seule fois, puis ajoutez ou remplacez chaque Item :
STAC=https://stac.urbiverde.com
# Une fois : créer la Collection (409 si elle existe déjà)
curl -X POST "$STAC/collections" -H "Content-Type: application/json" -d @collection.json
# À chaque date : ajouter ou remplacer l'Item
curl -X POST "$STAC/collections/ndvi-metropole-grand-nancy-cs-group/bulk_items" \
-H "Content-Type: application/json" \
-d '{"method": "upsert", "items": {"ndvi-metropole-grand-nancy-cs-group-20260627": '"$(cat item.json)"'}}'
Republier le même id d'Item remplace la version précédente. Le catalogue est
aujourd'hui ouvert : aucune authentification n'est nécessaire. Dans sa version sécurisée,
ajoutez un jeton OIDC Keycloak à chaque requête, avec -H "Authorization: Bearer <jeton>"
(voir Le catalogue STAC).
Le projet ndvi-demo applique toute cette publication dans une chaîne CWL : stac/build_stac.py
construit la Collection et l'Item, publish/publish.py dépose les fichiers sur S3 et
les enregistre dans le catalogue. Partez de lui pour un nouvel indicateur.
5. Vérifier¶
Dans le géoportail, votre indicateur apparaît sous ville → indicateur, avec une couche par date et par render. Ouvrez une couche : la description, la légende et le producteur doivent être ceux de votre Item et de votre Collection. Cliquez sur la carte : la valeur ou la classe du pixel s'affiche.
| Ce que vous voyez | Cause probable |
|---|---|
| L'indicateur n'apparaît pas | un champ urbiverde:* manquant ou vide ; pas de datetime ni de start_datetime ; urbiverde:show_in_geoportal: false ; votre compte n'a pas de droit sur cette ville ou ce producteur |
| La date apparaît, mais pas une couche | pas de alternate.s3.href en s3://, render multi-assets ou expression non textuelle |
| La couche est vide | TiTiler ne lit pas le fichier : testez l'URL /cog/info de l'étape 2 |
La légende affiche 0, 1, 2… |
ni classification:classes ni expression à seuils |
| Une bande colorée apparaît loin de la ville en vue éloignée | la bbox de l'Item est absente |
Exemple complet¶
Collection
{
"type": "Collection",
"stac_version": "1.1.0",
"stac_extensions": ["https://stac.urbiverde.com/extensions/urbiverde/v1.0.0/schema.json"],
"id": "ndvi-metropole-grand-nancy-cs-group",
"title": "NDVI · Métropole du Grand Nancy · CS Group",
"description": "Sentinel-2 L2A NDVI (SCL cloud masked) over Métropole du Grand Nancy, produced by CS Group.",
"license": "other",
"providers": [{ "name": "CS Group", "roles": ["producer"] }],
"keywords": ["ndvi", "sentinel-2", "metropole-grand-nancy"],
"extent": {
"spatial": { "bbox": [[6.0653, 48.5984, 6.3026, 48.7480]] },
"temporal": { "interval": [["2026-06-27T10:40:41Z", null]] }
},
"urbiverde:city": "metropole-grand-nancy",
"urbiverde:city_label": "Métropole du Grand Nancy",
"urbiverde:indicator": "ndvi",
"urbiverde:indicator_label": "NDVI",
"urbiverde:producer": "cs-group",
"urbiverde:show_in_geoportal": true,
"links": [{ "rel": "license", "href": "https://www.urbiverde.com", "title": "UrbiVerde terms of use" }]
}
Item
{
"type": "Feature",
"stac_version": "1.1.0",
"stac_extensions": [
"https://stac-extensions.github.io/projection/v2.0.0/schema.json",
"https://stac-extensions.github.io/raster/v2.0.0/schema.json",
"https://stac-extensions.github.io/render/v2.0.0/schema.json",
"https://stac-extensions.github.io/alternate-assets/v1.2.0/schema.json",
"https://stac-extensions.github.io/classification/v2.0.0/schema.json",
"https://stac.urbiverde.com/extensions/urbiverde/v1.0.0/schema.json"
],
"id": "ndvi-metropole-grand-nancy-cs-group-20260627",
"collection": "ndvi-metropole-grand-nancy-cs-group",
"geometry": { "type": "Polygon", "coordinates": [[[6.0653, 48.5984], [6.3026, 48.5984], [6.3026, 48.7480], [6.0653, 48.7480], [6.0653, 48.5984]]] },
"bbox": [6.0653, 48.5984, 6.3026, 48.7480],
"properties": {
"datetime": "2026-06-27T10:40:41Z",
"description": "Sentinel-2 L2A NDVI (SCL cloud masked) over Métropole du Grand Nancy on 27/06/2026, produced by CS Group.",
"license": "other",
"proj:code": "EPSG:32631",
"urbiverde:city": "metropole-grand-nancy",
"urbiverde:indicator": "ndvi",
"urbiverde:producer": "cs-group",
"renders": {
"ndvi": {
"title": "NDVI (RdYlGn)",
"assets": ["ndvi"],
"rescale": [[-1, 1]],
"colormap_name": "rdylgn",
"resampling": "nearest"
},
"ndvi_classes": {
"title": "NDVI classes",
"assets": ["ndvi"],
"expression": "where(b1<0,0,where(b1<0.2,1,where(b1<0.4,2,where(b1<0.6,3,4))))",
"colormap": {
"0": [165, 0, 38, 255], "1": [244, 109, 67, 255], "2": [255, 255, 191, 255],
"3": [134, 188, 106, 255], "4": [0, 104, 55, 255]
},
"resampling": "nearest"
}
}
},
"assets": {
"ndvi": {
"href": "./ndvi_20260627.tif",
"alternate": {
"s3": { "href": "s3://cs-group/map-products/metropole-grand-nancy/ndvi/20260627/ndvi_20260627.tif" }
},
"type": "image/tiff; application=geotiff; profile=cloud-optimized",
"title": "NDVI (SCL + AOI masked)",
"roles": ["data"],
"bands": [{
"data_type": "float32",
"nodata": "nan",
"raster:spatial_resolution": 10.0,
"classification:classes": [
{ "value": 0, "name": "water-no-vegetation", "title": "Water / no vegetation", "color_hint": "A50026" },
{ "value": 1, "name": "bare-soil-built-up", "title": "Bare soil / built-up", "color_hint": "F46D43" },
{ "value": 2, "name": "sparse-vegetation", "title": "Sparse vegetation", "color_hint": "FFFFBF" },
{ "value": 3, "name": "moderate-vegetation", "title": "Moderate vegetation", "color_hint": "86BC6A" },
{ "value": 4, "name": "dense-vegetation", "title": "Dense vegetation", "color_hint": "006837" }
]
}]
}
},
"links": [{ "rel": "license", "href": "https://www.urbiverde.com", "title": "UrbiVerde terms of use" }]
}
Check-list¶
- [ ] Le raster est un COG valide (
rio cogeo validate), avec CRS et nodata. - [ ] TiTiler le lit : l'URL
/cog/info?url=s3://…répond. - [ ] Les six extensions utiles sont déclarées dans
stac_extensions. - [ ] Collection :
iden<indicateur>-<ville>-<producteur>,license,providersde rôleproducer,urbiverde:*et libellés. - [ ] Nom du producteur identique dans toutes vos Collections.
- [ ] Item :
iden<collection>-<date>,bbox,description,datetime(oustart_datetime+end_datetime), et les champsurbiverde:*non vides. - [ ] Chaque asset affiché a un
alternate.s3.hrefens3://. - [ ] Chaque render lit un seul asset ;
colormapen objet ;expressionen texte. - [ ] Pour des classes :
classification:classessur la bande, ou uneexpressionà seuils.