Documentation

API Marche à l'ombre

L'API Marche à l'ombre permet d'intégrer le calcul d'itinéraires piétons optimisés pour l'ombre dans vos propres outils, applications ou systèmes d'information géographique. Elle expose les deux fonctionnalités principales de la plateforme : le routage ombragé et la carte thermique.

Les réponses sont en GeoJSON standard — compatibles nativement avec Leaflet, Mapbox GL JS, QGIS, ArcGIS, et tout outil géospatial moderne.

5
Endpoints
GeoJSON
Format de réponse
REST
Architecture
HTTPS
Transport
L'API est actuellement en accès partenaire. Pour obtenir une clé, contactez jbmeneroud@gmail.com.

Authentification

Toutes les requêtes doivent inclure un header X-Api-Key contenant votre clé d'API. Les clés sont au format sk_live_… pour les partenaires, sk_free_… pour les accès de test.

Exemple

curl 'https://alombre.app/api/v1/route?from_lat=45.75&from_lng=4.83&to_lat=45.76&to_lng=4.84' \
  -H 'X-Api-Key: sk_live_votre_clé'
// Fetch API
const res = await fetch('https://alombre.app/api/v1/route?from_lat=45.75&from_lng=4.83&to_lat=45.76&to_lng=4.84', {
  headers: { 'X-Api-Key': 'sk_live_votre_clé' }
});
const data = await res.json();
import requests

resp = requests.get(
    "https://alombre.app/api/v1/route",
    params={"from_lat": 45.75, "from_lng": 4.83, "to_lat": 45.76, "to_lng": 4.84},
    headers={"X-Api-Key": "sk_live_votre_clé"},
)
data = resp.json()

URL de base

Base URL https://alombre.app/api/v1

Toutes les réponses sont encodées en UTF-8, content-type application/json.

Endpoints

Calcul d'itinéraire ombragé DÉPRÉCIÉ

Cet endpoint utilise le moteur V1 (ombre des arbres uniquement). Préférez /v1/route/v2 qui intègre également l'ombre des bâtiments et une interpolation temporelle précise. L'endpoint V1 reste fonctionnel mais ne sera plus amélioré.

Calcule les deux itinéraires piétons entre une origine et une destination : le plus ombragé (optimisé pour le confort thermique) et le plus rapide (distance minimale). Le score d'ombre de chaque tronçon est ajusté dynamiquement selon la position du soleil à l'heure demandée.

GET /v1/route

Paramètres

Paramètre Type Description
from_latrequis float Latitude du point de départ (WGS84)
from_lngrequis float Longitude du point de départ (WGS84)
to_latrequis float Latitude de la destination (WGS84)
to_lngrequis float Longitude de la destination (WGS84)
time string Heure de départ au format HH:MM (ex. 14:30). Par défaut : heure courante.

Réponse

{
  "shadiest_route": {
    "type": "Feature",
    "geometry": {
      "type": "LineString",
      "coordinates": [[4.83, 45.75], /* … */]  // [lng, lat]
    },
    "properties": {
      "distance_m": 1420,
      "duration_s": 1065,
      "avg_shade_score": 0.724,     // 0 (plein soleil) → 1 (ombre totale)
      "segments": [
        { "distance_m": 85, "shade_score": 0.87, "name": "Rue de la Paix" },
        /* … */
      ]
    }
  },
  "fastest_route": { /* même structure */ },
  "sun": {
    "azimuth": 224.3,    // degrés depuis le nord
    "elevation": 52.1,   // degrés au-dessus de l'horizon
    "time": "14:30"
  },
  "computed_at": "2026-06-28T14:30:00+02:00"
}

Exemple complet

curl 'https://alombre.app/api/v1/route?from_lat=45.7597&from_lng=4.8372&to_lat=45.7640&to_lng=4.8320&time=14:30' \
  -H 'X-Api-Key: sk_live_votre_clé' | jq .
const params = new URLSearchParams({
  from_lat: 45.7597, from_lng: 4.8372,
  to_lat:   45.7640, to_lng:   4.8320,
  time:     '14:30',
});

const { shadiest_route, fastest_route, sun } = await fetch(
  `https://alombre.app/api/v1/route?${params}`,
  { headers: { 'X-Api-Key': 'sk_live_votre_clé' } }
).then(r => r.json());

// Afficher sur une carte Leaflet
L.geoJSON(shadiest_route, { style: { color: '#22c55e', weight: 5 } }).addTo(map);
L.geoJSON(fastest_route,  { style: { color: '#60a5fa', weight: 3 } }).addTo(map);
import requests, folium

resp = requests.get(
    "https://alombre.app/api/v1/route",
    params={"from_lat": 45.7597, "from_lng": 4.8372,
            "to_lat":   45.7640, "to_lng":   4.8320, "time": "14:30"},
    headers={"X-Api-Key": "sk_live_votre_clé"},
).json()

shady = resp["shadiest_route"]["properties"]
print(f"Distance : {shady['distance_m']}m — Score ombre : {shady['avg_shade_score']:.0%}")

Carte thermique de l'ombre

Retourne les tronçons de rue avec leur score d'ombre pour une zone géographique donnée, au format GeoJSON FeatureCollection. Idéal pour construire une couche de visualisation sur une carte interactive ou un SIG.

GET /v1/heatmap

Paramètres

ParamètreTypeDescription
southrequis float Latitude minimale de la zone (WGS84)
westrequis float Longitude minimale de la zone (WGS84)
northrequis float Latitude maximale de la zone (WGS84)
eastrequis float Longitude maximale de la zone (WGS84)
limit integer Nombre maximum de segments retournés. Défaut : 500. Max : 2000.

Réponse

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": { "type": "LineString", "coordinates": [[4.83, 45.75], /* … */] },
      "properties": {
        "shade_score": 0.812,   // 0 → 1
        "name": "Avenue du Parc",  // null si inconnu
        "orientation": 142.5    // azimut du tronçon en degrés
      }
    }
  ],
  "sun": { "azimuth": 224.3, "elevation": 52.1 },
  "meta": {
    "count": 500,
    "limit": 500,
    "bbox": { "south": 45.74, "west": 4.82, "north": 45.76, "east": 4.85 },
    "computed_at": "2026-06-28T14:30:00+02:00"
  }
}

Exemple — couche QGIS via WFS-like

curl 'https://alombre.app/api/v1/heatmap?south=45.74&west=4.82&north=45.76&east=4.85&limit=2000' \
  -H 'X-Api-Key: sk_live_votre_clé' \
  -o heatmap_lyon.geojson
const bounds = map.getBounds();
const params = new URLSearchParams({
  south: bounds.getSouth(), west: bounds.getWest(),
  north: bounds.getNorth(), east: bounds.getEast(),
  limit: 1000,
});

const data = await fetch(`https://alombre.app/api/v1/heatmap?${params}`, {
  headers: { 'X-Api-Key': 'sk_live_votre_clé' }
}).then(r => r.json());

L.geoJSON(data, {
  style: f => ({
    color: shadeColor(f.properties.shade_score),
    weight: 3, opacity: 0.7
  })
}).addTo(map);

function shadeColor(score) {
  if (score >= 0.6) return '#16a34a';
  if (score >= 0.35) return '#84cc16';
  if (score >= 0.15) return '#facc15';
  return '#f97316';
}
import requests, geopandas as gpd

resp = requests.get(
    "https://alombre.app/api/v1/heatmap",
    params={"south": 45.74, "west": 4.82, "north": 45.76, "east": 4.85, "limit": 2000},
    headers={"X-Api-Key": "sk_live_votre_clé"},
)

gdf = gpd.GeoDataFrame.from_features(resp.json()["features"], crs="EPSG:4326")
print(gdf[["name", "shade_score"]].sort_values("shade_score", ascending=False).head(10))

Couverture géographique

Retourne les métadonnées de couverture pour chaque ville supportée : nombre de tronçons, d'arbres et de parcs indexés, date de la dernière mise à jour OSM, et bounding box. Utile pour vérifier la disponibilité d'une zone avant de lancer un calcul d'itinéraire.

GET /v1/coverage
Cet endpoint ne prend aucun paramètre et n'est pas soumis au rate limiting.

Réponse

{
  "cities": [
    {
      "id":         "lyon",
      "name":       "Lyon (Métropole de Lyon)",
      "supported":  true,
      "segments":   167610,    // tronçons de rue indexés
      "trees":      88243,     // arbres dans la zone
      "parks":      412,       // parcs et espaces verts
      "updated_at": "2026-06-27T14:02:00.000000Z",
      "bbox": {
        "south": 45.60, "west": 4.70,
        "north": 45.95, "east": 5.08
      }
    },
    /* bruxelles, la_hulpe… */
  ],
  "computed_at": "2026-06-28T10:15:00+02:00"
}

Exemple

curl 'https://alombre.app/api/v1/coverage' \
  -H 'X-Api-Key: sk_live_votre_clé' | jq '.cities[] | {id, segments, trees}'
const { cities } = await fetch('https://alombre.app/api/v1/coverage', {
  headers: { 'X-Api-Key': 'sk_live_votre_clé' }
}).then(r => r.json());

const supported = cities.filter(c => c.supported);
console.log(`${supported.length} ville(s) disponible(s)`);
import requests

cities = requests.get(
    "https://alombre.app/api/v1/coverage",
    headers={"X-Api-Key": "sk_live_votre_clé"},
).json()["cities"]

for c in cities:
    print(f"{c['name']}: {c['segments']:,} tronçons — {c['trees']:,} arbres")

Simulation multi-horaire

Calcule le même itinéraire à plusieurs créneaux horaires en un seul appel. Permet de comparer l'ombre disponible selon l'heure de départ — par exemple pour afficher un graphique d'évolution du score d'ombre au fil de la journée.

GET /v1/route/simulate
Chaque créneau horaire supplémentaire après le premier consomme un appel API supplémentaire contre votre quota. 4 créneaux = 4 requêtes décomptées.

Paramètres

ParamètreTypeDescription
from_latrequis float Latitude du point de départ (WGS84)
from_lngrequis float Longitude du point de départ (WGS84)
to_latrequis float Latitude de la destination (WGS84)
to_lngrequis float Longitude de la destination (WGS84)
date string Date de simulation au format YYYY-MM-DD. Par défaut : aujourd'hui.
times string Créneaux horaires séparés par des virgules au format HH:MM. Par défaut : 08:00,12:00,16:00,20:00. Maximum 8 créneaux.

Réponse

{
  "simulations": {
    "08:00": {
      "shadiest_route": { /* Feature GeoJSON — même structure que /v1/route */ },
      "fastest_route":  { /* … */ },
      "sun": { "azimuth": 98.4, "elevation": 22.1, "time": "08:00" }
    },
    "12:00": { /* … */ },
    "16:00": { /* … */ },
    "20:00": { /* … */ }
  },
  "slots":       4,
  "computed_at": "2026-06-28T10:15:00+02:00"
}

Exemple

curl 'https://alombre.app/api/v1/route/simulate?from_lat=45.7597&from_lng=4.8372&to_lat=45.7640&to_lng=4.8320&times=08:00,11:00,14:00,17:00' \
  -H 'X-Api-Key: sk_live_votre_clé' | jq '[.simulations | to_entries[] | {time: .key, score: .value.shadiest_route.properties.avg_shade_score}]'
const params = new URLSearchParams({
  from_lat: 45.7597, from_lng: 4.8372,
  to_lat:   45.7640, to_lng:   4.8320,
  times:    '08:00,11:00,14:00,17:00,20:00',
});

const { simulations } = await fetch(
  `https://alombre.app/api/v1/route/simulate?${params}`,
  { headers: { 'X-Api-Key': 'sk_live_votre_clé' } }
).then(r => r.json());

// Construire un graphique score d'ombre / heure
const chartData = Object.entries(simulations).map(([time, data]) => ({
  time,
  score: data.shadiest_route.properties.avg_shade_score,
}));
import requests, matplotlib.pyplot as plt

data = requests.get(
    "https://alombre.app/api/v1/route/simulate",
    params={"from_lat": 45.7597, "from_lng": 4.8372,
            "to_lat":   45.7640, "to_lng":   4.8320,
            "times": "08:00,10:00,12:00,14:00,16:00,18:00,20:00"},
    headers={"X-Api-Key": "sk_live_votre_clé"},
).json()["simulations"]

times  = list(data.keys())
scores = [data[t]["shadiest_route"]["properties"]["avg_shade_score"] for t in times]

plt.plot(times, scores, marker="o", color="#22c55e")
plt.ylabel("Score d'ombre"); plt.title("Ombre selon l'heure"); plt.show()

Meilleur horaire de départ

Analyse tous les créneaux dans une fenêtre horaire et retourne le moment où l'itinéraire sera le plus ombragé, accompagné d'une recommandation textuelle en français. Idéal pour alimenter une notification push ou un assistant conversationnel.

GET /v1/route/best-departure
Chaque créneau testé dans la fenêtre consomme un appel API. Une fenêtre de 6h–22h avec interval_min=30 génère 33 créneaux = 33 requêtes décomptées.

Paramètres

ParamètreTypeDescription
from_latrequis float Latitude du point de départ (WGS84)
from_lngrequis float Longitude du point de départ (WGS84)
to_latrequis float Latitude de la destination (WGS84)
to_lngrequis float Longitude de la destination (WGS84)
date string Date au format YYYY-MM-DD. Par défaut : aujourd'hui.
window_start string Début de la fenêtre au format HH:MM. Par défaut : 06:00.
window_end string Fin de la fenêtre au format HH:MM. Par défaut : 22:00.
interval_min integer Intervalle entre les créneaux testés, en minutes. Min : 15. Max : 60. Par défaut : 30. Maximum 24 créneaux.

Réponse

{
  "best_time":        "07:30",
  "shade_score":      0.831,   // score à l'horaire optimal
  "sun_exposure_pct": 17,      // pourcentage de trajet en plein soleil
  "recommendation":   "Attendre 45 min améliore l'ombre de 12 points de pourcentage.",
  "best_route": {
    "shadiest_route": { /* Feature GeoJSON — même structure que /v1/route */ },
    "fastest_route":  { /* … */ },
    "sun": { "azimuth": 114.2, "elevation": 18.7, "time": "07:30" }
  },
  "computed_at": "2026-06-28T10:15:00+02:00"
}

Exemple

curl 'https://alombre.app/api/v1/route/best-departure?from_lat=45.7597&from_lng=4.8372&to_lat=45.7640&to_lng=4.8320&window_start=07:00&window_end=20:00&interval_min=30' \
  -H 'X-Api-Key: sk_live_votre_clé' | jq '{best_time, shade_score, recommendation}'
const params = new URLSearchParams({
  from_lat: 45.7597, from_lng: 4.8372,
  to_lat:   45.7640, to_lng:   4.8320,
  window_start: '07:00', window_end: '20:00',
  interval_min: 30,
});

const result = await fetch(
  `https://alombre.app/api/v1/route/best-departure?${params}`,
  { headers: { 'X-Api-Key': 'sk_live_votre_clé' } }
).then(r => r.json());

// Afficher la recommandation dans l'interface
console.log(`Meilleur départ à ${result.best_time} — ${result.recommendation}`);
import requests

result = requests.get(
    "https://alombre.app/api/v1/route/best-departure",
    params={"from_lat": 45.7597, "from_lng": 4.8372,
            "to_lat":   45.7640, "to_lng":   4.8320,
            "window_start": "07:00", "window_end": "20:00",
            "interval_min": 30},
    headers={"X-Api-Key": "sk_live_votre_clé"},
).json()

print(f"Optimal : {result['best_time']} (score {result['shade_score']:.0%})")
print(result["recommendation"])
Moteur V2

Itinéraire avec ombre bâtiments

Version avancée du calcul d'itinéraire. Le moteur V2 utilise la géométrie réelle des bâtiments (hauteur incluse) pour calculer l'ombre portée selon la position du soleil. Les profils d'ombre sont précalculés (3 saisons × 6 créneaux = 18 profils par tronçon) et interpolés entre les deux créneaux encadrant l'heure demandée.

Chaque segment retourne la part d'ombre issue des arbres, des bâtiments, et leur combinaison. La ville est détectée automatiquement depuis les coordonnées d'origine.

GET /v1/route/v2
Le moteur V2 couvre actuellement Lyon (Métropole), Bruxelles et La Hulpe — soit 3,9 M de profils d'ombre en base. La ville est inférée depuis les coordonnées, aucun paramètre city n'est nécessaire.

Paramètres

ParamètreTypeDescription
from_latrequis float Latitude du point de départ (WGS84)
from_lngrequis float Longitude du point de départ (WGS84)
to_latrequis float Latitude de la destination (WGS84)
to_lngrequis float Longitude de la destination (WGS84)
time string Heure de départ au format HH:MM. Par défaut : heure courante. Utilisé pour l'interpolation entre créneaux.
date string Date au format YYYY-MM-DD. Détermine la saison (été / équinoxe / hiver). Par défaut : aujourd'hui.

Réponse

{
  "shadiest_route": {
    "type": "Feature",
    "geometry": { "type": "LineString", "coordinates": [[4.83, 45.75], /* … */] },
    "properties": {
      "distance_m":      1480,
      "duration_s":      1057,
      "avg_shade_score": 0.731,   // score combiné pondéré par la distance
      "segments": [
        {
          "distance_m":   85,
          "name":          "Rue de la Paix",
          "shade_score":  0.712,  // = combined_pct interpolé
          "tree_pct":      0.421,  // part de l'ombre due aux arbres (0→1)
          "building_pct":  0.291,  // part de l'ombre due aux bâtiments (0→1)
          "combined_pct":  0.712   // combinaison probabiliste (0→1)
        },
        /* … */
      ]
    }
  },
  "fastest_route": { /* même structure — segments enrichis V2 */ },
  "sun": {
    "azimuth": 224.3,
    "elevation": 52.1,
    "time": "14:30"
  },
  "profile": {
    "season":  "summer",    // "summer" | "equinox" | "winter"
    "slot":    840,         // créneau le plus proche en min. depuis minuit (ex. 840 = 14h00)
    "version": 1
  },
  "computed_at": "2026-06-28T14:30:00+02:00"
}
Les créneaux disponibles sont 08:00, 10:00, 12:00, 14:00, 16:00, 18:00. En dehors de cette plage (avant 8h ou après 18h), le profil du créneau extrême le plus proche est utilisé sans interpolation.

Exemple complet

curl 'https://alombre.app/api/v1/route/v2?from_lat=45.7597&from_lng=4.8372&to_lat=45.7640&to_lng=4.8320&time=14:30' \
  -H 'X-Api-Key: sk_live_votre_clé' | jq '.shadiest_route.properties | {avg_shade_score, segments: [.segments[] | {name, tree_pct, building_pct}]}'
const params = new URLSearchParams({
  from_lat: 45.7597, from_lng: 4.8372,
  to_lat:   45.7640, to_lng:   4.8320,
  time: '14:30',
});

const { shadiest_route, profile } = await fetch(
  `https://alombre.app/api/v1/route/v2?${params}`,
  { headers: { 'X-Api-Key': 'sk_live_votre_clé' } }
).then(r => r.json());

// Afficher la source d'ombre pour chaque segment
for (const seg of shadiest_route.properties.segments) {
  console.log(`${seg.name} — arbres: ${(seg.tree_pct * 100).toFixed(0)}% / bâtiments: ${(seg.building_pct * 100).toFixed(0)}%`);
}

console.log(`Saison : ${profile.season} — créneau : ${profile.slot} min`);
import requests

resp = requests.get(
    "https://alombre.app/api/v1/route/v2",
    params={"from_lat": 45.7597, "from_lng": 4.8372,
            "to_lat":   45.7640, "to_lng":   4.8320,
            "time": "14:30"},
    headers={"X-Api-Key": "sk_live_votre_clé"},
).json()

shadiest = resp["shadiest_route"]["properties"]
print(f"Score ombre : {shadiest['avg_shade_score']:.0%} (saison : {resp['profile']['season']})")

for seg in shadiest["segments"]:
    print(f"  {seg['name'] or '—':<30} arbres {seg['tree_pct']:.0%}  bâtiments {seg['building_pct']:.0%}")

Différences avec /v1/route

/v1/route/v1/route/v2
Source d'ombre Arbres uniquement (V1) Arbres + bâtiments (géométrie réelle)
Profils Calculé à la volée Précalculés, interpolés entre créneaux
Paramètre ville city requis Auto-détecté depuis les coordonnées
Champs segment shade_score + tree_pct, building_pct, combined_pct
Disponibilité Toutes villes Lyon, Bruxelles, La Hulpe

Rate limiting

Les limites sont appliquées par clé d'API sur une fenêtre glissante d'une heure.

TierLimiteUsage
Free 100 req / heure Développement, prototypage, démonstration
Partner 1 000 req / heure Intégration en production pour les collectivités

En cas de dépassement, l'API retourne un 429 avec le champ retry_after (secondes avant réessai).

Codes d'erreur

Code
Signification
401
Header X-Api-Key absent ou clé invalide / désactivée.
422
Paramètre manquant ou invalide. Le champ errors détaille chaque problème.
404
Aucun itinéraire trouvé entre les coordonnées fournies (zone non couverte).
429
Limite de taux dépassée. Voir retry_after dans la réponse.
500
Erreur serveur interne. Contactez le support si l'erreur persiste.
Les coordonnées doivent correspondre à une zone couverte par la base OSM importée. Actuellement : Lyon / Métropole de Lyon, Bruxelles, La Hulpe. D'autres villes peuvent être ajoutées sur demande.