Outils cartographiques
Chaque outil agit sur la carte qui le contient et repose sur vos propres primitives shadcn. Un outil qui appelle l'API, comme le profil d'altitude, fait une requête facturée par appel.
npx shadcn@latest registry add @unmap=https://registry.unmap.dev/r/{name}.json
npx shadcn@latest add @unmap/measurenpx shadcn-vue@latest add https://registry.unmap.dev/r/vue/measure.jsonLa ligne React enregistre le registre une fois par projet, ce qui permet ensuite de nommer les éléments @unmap/<item> ; shadcn-vue n'a pas de registry add et prend directement l'URL de l'élément. Les fichiers arrivent dans components/ui/ à côté des vôtres ; rien n'est remplacé sans confirmation.
Parcourir tous les éléments sur registry.unmap.dev| Élément | Rôle |
|---|---|
measure | Cliquez sur la carte pour mesurer une distance le long du grand cercle, avec une étiquette en direct sur la carte et les boutons Annuler et Effacer sur votre Button. Mètres et kilomètres, en anglais ou en français. |
feature-inspector | Les entités sous un clic, ou dans la vue, listées dans vos Card et Table. Cliquez sur une ligne pour faire ressortir l'entité et lire ses propriétés. |
time-slider | Votre Slider qui pilote une fenêtre temporelle sur des couches de la carte, avec lecture, pause, pas à pas et boucle. Fonctionne à côté d'une layer-legend sur la même couche. |
draw | Dessine et modifie des points, des lignes, des polygones et des cercles sur vos ToggleGroup, Button et Tooltip. Les formes reviennent en GeoJSON et résistent à un changement de style. |
compare-swipe | Deux cartes dans un même cadre, avec un séparateur à faire glisser et une caméra partagée : avant et après, ou un style contre un autre. |
elevation-profile | L'altitude du sol le long d'un itinéraire, d'une ligne tracée ou de vos propres coordonnées, sur votre Chart, avec la distance, le dénivelé positif et négatif, le point le plus bas et le plus haut. Survolez le graphique ou la ligne et l'autre suit. |
Mesurer des distances
measure ajoute un point là où le lecteur clique et mesure la ligne qui les relie le long du grand
cercle, selon la forme de la Terre : de Vancouver à Halifax, on lit environ 4 430 km. Tant que le
pointeur est sur la carte, une ligne pointillée le rejoint et l'étiquette sur la carte affiche le
total. La ligne est tracée par-dessus la carte dans votre --primary, avec les boutons Annuler et
Effacer sur votre Button shadcn :
import { Map } from "@/components/ui/map";
import { Measure } from "@/components/ui/measure";
<Map apiKey={key} center={[-114.07, 51.05]} zoom={11}>
<Measure lang="fr" onChange={({ points, metres }) => console.log(points.length, metres)} />
</Map>Essayez-le, puis ajoutez-le comme point de départ :
Projets React. Installe components/unmap-map-measure.tsx ; affichez <MapMeasure apiKey={key} />.
- Sous 1 km, les distances sont en mètres ; au-delà, en kilomètres avec deux, une ou aucune
décimale selon la grandeur.
lang="fr"les écrit à la française,1,25 kmet4 430 km, avec une espace insécable entre les milliers et avant l'unité, et nomme les boutons en français.formatremplace la mise en forme. - Un double-clic ou un double-toucher ajoute un seul point, et ne zoome pas : le zoom au double-clic de la carte est suspendu pendant la mesure. Annuler retire le dernier point et Effacer les retire tous. Quand la carte a le focus, Échap masque la ligne pointillée jusqu'à ce que le pointeur bouge, et Retour arrière annule le dernier point.
- Une ligne qui franchit l'antiméridien (180 degrés de longitude) reste une seule ligne continue.
onChange(Vue :@change) reçoit les points et leur longueur en mètres après chaque changement.active={false}(Vue ::active="false") arrête l'ajout de points et garde la mesure sur la carte.- La mesure résiste à un changement de style et quitte la carte quand le composant est démonté. Ses couches sont marquées comme celles d'un outil, si bien que l'inspecteur d'entités ne les liste jamais. La mesure se fait dans le navigateur et n'envoie aucune requête.
- L'inspecteur et l'outil de mesure répondent tous deux aux clics sur la carte. N'en exécutez qu'un à
la fois et désactivez l'autre avec
enabled={false}ouactive={false}. - Le composant affiche une simple rangée : placez-le dans votre propre Card s'il flotte sur la carte.
<script setup lang="ts">
import { ref } from "vue";
import { UnmapMap } from "@/components/ui/map";
import { UnmapMeasure } from "@/components/ui/measure";
const apiKey = import.meta.env.VITE_UNMAP_KEY;
const metres = ref(0);
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-114.07, 51.05]" :zoom="11">
<UnmapMeasure lang="fr" @change="metres = $event.metres" />
</UnmapMap>
</template>Inspecter les entités
feature-inspector liste ce que la carte a dessiné sous un clic, dans vos Card et Table shadcn :
chaque entité à quelques pixels près, la plus haute d'abord, avec la couche qui l'a dessinée. La
plus haute est sélectionnée : ses propriétés sont listées et sa forme est surlignée sur la carte
dans votre --primary. Cliquez sur une ligne pour choisir une autre entité, ou sur la ligne
sélectionnée pour la désélectionner. La liste et les propriétés forment deux tableaux, chacun nommé
par la région qui l'entoure, si bien qu'un lecteur d'écran les annonce :
import { Map } from "@/components/ui/map";
import { FeatureInspector } from "@/components/ui/feature-inspector";
<Map apiKey={key} center={[-75.69, 45.42]} zoom={14}>
<FeatureInspector lang="fr" className="absolute top-2 left-2 w-80" onSelect={(feature) => console.log(feature)} />
</Map>Essayez-le, puis ajoutez-le comme point de départ :
Projets React. Installe components/unmap-map-inspect.tsx ; affichez <MapInspect apiKey={key} />.
- Par défaut, toutes les couches sont interrogées : les routes, les lieux et l'eau du fond de
carte, et vos propres données.
layerslimite la recherche à certains identifiants de couches du style ; un identifiant qui se termine par*désigne toutes les couches qui commencent par le reste, et un identifiant absent du style est ignoré. Les couches des outils, comme une mesure ou le surlignage lui-même, ne sont jamais listées. scope="view"liste plutôt ce qui est dessiné dans toute la vue, de nouveau après chaque déplacement, jusqu'àlimitlignes (50 par défaut). La sélection est conservée d'un rafraîchissement à l'autre.- Une entité coupée entre plusieurs tuiles n'est listée qu'une fois. Les entités sans identifiant qui portent les mêmes propriétés dans la même couche se fondent en une seule ligne.
- Avec un identifiant, le surlignage couvre l'entité entière dans toutes les tuiles chargées. Les
polygones sont remplis, non tracés, si bien qu'aucune jointure de tuile ne les traverse ; les
morceaux sont dessinés par tuile et peuvent se chevaucher légèrement au bord des tuiles. Sans
identifiant, ou depuis une source qui promeut une propriété en identifiant (
promoteId), le surlignage couvre le morceau dessiné là où vous avez cliqué. - L'inspecteur lit ce que la carte a déjà dessiné : il n'envoie aucune requête.
enabled={false}(Vue ::enabled="false") ignore les clics et vide la liste, la sélection et le surlignage.lang="fr"écrit la carte en français et nomme les entités parname:frquand elles en ont un. Le surlignage résiste à un changement de style et disparaît au démontage.onSelect(Vue :@select) reçoit l'entité sélectionnée, ounull. - Le surlignage est calculé à la sélection : il reste dessiné si un curseur ou une légende filtre ensuite cette entité. Cliquez de nouveau pour l'actualiser.
- L'inspecteur et l'outil
measurerépondent tous deux aux clics sur la carte. N'en exécutez qu'un à la fois.
<script setup lang="ts">
import { UnmapMap } from "@/components/ui/map";
import { UnmapFeatureInspector } from "@/components/ui/feature-inspector";
const apiKey = import.meta.env.VITE_UNMAP_KEY;
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-75.69, 45.42]" :zoom="14">
<UnmapFeatureInspector lang="fr" class="absolute top-2 left-2 w-80" scope="view" :limit="20" />
</UnmapMap>
</template>Curseur temporel
time-slider place votre Slider shadcn sur un nombre porté par vos entités, une année ou un
instant en millisecondes, et n'affiche que les entités de la fenêtre choisie, avec des boutons de
lecture, de pause et de pas à pas :
import { Map } from "@/components/ui/map";
import { GeoJSONLayer } from "@/components/ui/geojson-layer";
import { TimeSlider } from "@/components/ui/time-slider";
<Map apiKey={key} center={[-114.07, 51.05]} zoom={6}>
<GeoJSONLayer id="wells" data={wells} />
<TimeSlider lang="fr" layers={["wells-*"]} property="year" min={1950} max={2025} interval={500} loop />
</Map>Essayez-le, puis ajoutez-le comme point de départ :
Projets React. Installe components/unmap-map-timeline.tsx ; affichez <MapTimeline apiKey={key} />.
- Une valeur seule affiche un pas :
2001avecstep={1}affiche les entités dontyearest d'au moins 2001 et inférieur à 2002.cumulativeaffiche plutôt tout ce qui précède la fin du pas. Une paire, commedefaultValue={[1990, 2000]}, affiche les entités comprises entre les deux, bornes incluses, et garde sa largeur pendant la lecture. - La lecture avance d'un pas toutes les
intervalmillisecondes (1 000 par défaut) et s'arrête à la fin ;loopla fait recommencer. Appuyer sur lecture à la fin repart du début. - L'état est non contrôlé avec
defaultValue, ou contrôlé avecvalueetonValueChange(Vue :v-model).formatécrit l'étiquette, par exemple une date à partir de millisecondes. - La fenêtre s'ajoute au filtre propre de chaque couche, dans la même syntaxe, à côté des valeurs
qu'une
layer-legenddécoche sur la même couche : elles partagent un même registre de clauses de filtre par carte, si bien qu'aucune n'annule l'autre et que les clauses ne s'imbriquent jamais. Les entités sans la propriété sont masquées tant que le curseur s'applique. Il s'applique de nouveau après un changement de style et quand une couche nommée est ajoutée plus tard. - Au démontage, la fenêtre est retirée : aucune entité ne reste masquée sans curseur pour la faire revenir. Le filtrage se fait dans le navigateur : un pas n'est pas une requête.
- Les valeurs sont des nombres, une année ou des millisecondes depuis 1970. Sur un filtre en syntaxe d'expression (ou sans filtre), une année stockée en texte se compare quand même ; sur un filtre en syntaxe héritée, elle ne correspond jamais. Les dates ISO en chaîne ne sont pas prises en charge.
- Une valeur contrôlée doit être renvoyée depuis
onValueChange(Vue :update:modelValue, doncv-model), sinon rien ne bouge et la lecture n'avance jamais. - Dans Vue, transmettez l'étiquette à chaque poignée dans votre
Slider.vue, par exemple<SliderThumb :aria-labelledby="$attrs['aria-labelledby']" />, car reka-ui ne nomme pas les poignées à partir de la racine.
<script setup lang="ts">
import { ref } from "vue";
import { UnmapMap } from "@/components/ui/map";
import { UnmapGeojsonLayer } from "@/components/ui/geojson-layer";
import { UnmapTimeSlider } from "@/components/ui/time-slider";
const apiKey = import.meta.env.VITE_UNMAP_KEY;
const wells = { type: "FeatureCollection", features: [] };
const year = ref<number | [number, number]>(1990);
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-114.07, 51.05]" :zoom="6">
<UnmapGeojsonLayer id="wells" :data="wells" />
<UnmapTimeSlider v-model="year" lang="fr" :layers="['wells-*']" property="year" :min="1950" :max="2025" cumulative />
</UnmapMap>
</template>Dessiner sur la carte
draw ajoute à la carte une barre d'outils de dessin : points, lignes, polygones et cercles, et un
outil de sélection pour les déplacer, les remodeler et les supprimer. Le dessin repose sur
terra-draw et la barre d'outils utilise vos propres ToggleGroup,
Button et Tooltip. Ce que vous dessinez revient sous forme de FeatureCollection GeoJSON.
import { useState } from "react";
import { Map } from "@/components/ui/map";
import { Draw, EMPTY_DRAWING } from "@/components/ui/draw";
export function Sketch() {
const [shapes, setShapes] = useState(EMPTY_DRAWING);
return (
<Map apiKey={key} style="base" center={[-114.07, 51.05]} zoom={13}>
<Draw value={shapes} onValueChange={setShapes} />
</Map>
);
}Essayez-le, puis ajoutez-le comme point de départ :
Projets React. Installe components/unmap-map-draw.tsx ; affichez <MapDraw apiKey={key} />.
La même chose en Vue :
<script setup lang="ts">
import { ref } from "vue";
import { UnmapMap } from "@/components/ui/map";
import { UnmapDraw } from "@/components/ui/draw";
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
const shapes = ref({ type: "FeatureCollection" as const, features: [] });
</script>
<template>
<UnmapMap :api-key="apiKey" map-style="base" :center="[-114.07, 51.05]" :zoom="13">
<UnmapDraw v-model="shapes" />
</UnmapMap>
</template>- Les types (
DrawValue,DrawFeature) accompagnent le composant. En React,import type { DrawValue } from "@/components/ui/draw"; en Vue, ils sont exportés par le fichier du composant, doncimport type { DrawValue } from "@/components/ui/draw/UnmapDraw.vue"type votre propre état. - Aucun outil n'est actif au départ : la carte se déplace comme d'habitude. Choisissez un outil
pour dessiner, et choisissez-le de nouveau pour le reposer.
modeschoisit les outils de dessin (point,linestring,polygon,circle) ; la sélection et la suppression sont toujours là. - Terminez une ligne avec un clic sur son dernier point, et un polygone avec un clic sur son premier point. Avec l'outil de sélection, faites glisser une forme pour la déplacer, un sommet pour la remodeler, un point milieu pour ajouter un sommet, et faites un clic droit sur un sommet pour le retirer. Le bouton de suppression retire la forme sélectionnée.
- Les touches fonctionnent quand le canevas de la carte a le focus : cliquez d'abord sur la carte. Pendant le dessin, Échap abandonne la forme en cours et Entrée termine une ligne, un polygone ou un cercle. Avec l'outil de sélection, Échap désélectionne, et Suppr ou Retour arrière retire la forme sélectionnée (les portables Mac n'ont pas de touche Suppr).
onValueChange(Vue :v-model) se déclenche quand une forme est terminée ou supprimée, et en direct, à chaque étape, pendant qu'on fait glisser une forme ou qu'on modifie un sommet, pas seulement à la fin du geste. Sélectionner une forme ne le déclenche pas. Appliquez un anti-rebond avant toute opération coûteuse, comme une sauvegarde.- La valeur est semi-contrôlée. Transmettre un nouvel objet au contenu différent remplace les
formes. Laisser
valuetel quel, le même objet, ne ramène pas le dessin en arrière : pour annuler un changement, transmettez un nouvel objet. L'ordre des clés de l'objet n'a pas d'importance : une valeur passée par un schéma ou une base de données compte toujours comme l'écho de ce qu'on vous a communiqué.defaultValuepart de formes sans les contrôler. - Chaque entité garde
properties.mode, l'outil qui l'a dessinée, dont le dessin a besoin pour la modifier de nouveau ; un cercle est un polygone avecradiusKilometers. Vos propres propriétés et identifiants sont conservés. - Une valeur que vous transmettez peut omettre
mode: il est déduit de la géométrie. Les positions sont réduites à la longitude et à la latitude : l'altitude d'une position 3D issue d'un GPS ou de KML est retirée au chargement, et les coordonnées sont arrondies à 9 décimales. Une géométrie que le dessin ne sait pas modifier, comme unMultiPolygon, est ignorée avec un avertissement dans la console, et elle est absente de la valeur qui vous revient : gardez ces formes dans votre propre état. - Les formes prennent votre
--primary, bordées de votre--background;coloren fixe une autre. Elles passent au-dessus des étiquettes de la carte volontairement, pour que les poignées restent à portée. En Vue, l'outil actif est marqué pararia-pressed, dans vos jetons d'accent. - Un changement de mode, de style ou de thème garde chaque forme terminée et l'outil choisi ; une
forme en cours de dessin est abandonnée. Pour deux dessins sur une même carte, donnez à chacun
son propre
id. - Draw répond aussi aux clics sur la carte : n'utilisez qu'un seul outil à la fois parmi
draw,measureetfeature-inspector. - Tant que
drawest monté, le menu contextuel du navigateur est désactivé sur le canevas de la carte, parce que le dessin se sert du clic droit pour retirer un sommet. - Dessiner ne fait aucune requête : les formes restent dans la page jusqu'à ce que vous les envoyiez quelque part.
- Le poids est d'environ 35 Ko compressés (gzip) pour terra-draw, réduit aux cinq modes par le
tree-shaking, et d'environ 3 Ko pour son adaptateur MapLibre. Il se charge avec le composant :
gardez
drawpour les pages qui en ont besoin. lang="fr"affiche la barre d'outils en français, etpositionaccepte n'importe quel coin.
Comparer deux cartes
compare-swipe place deux cartes dans un même cadre avec un séparateur entre elles : avant et
après, clair et sombre, ou un style contre un autre. Les deux cartes partagent une seule caméra :
déplacer, zoomer, tourner ou incliner l'une entraîne l'autre.
import { CompareSwipe } from "@/components/ui/compare-swipe";
<CompareSwipe
apiKey={key}
center={[-123.12, 49.28]}
zoom={12}
before={{ style: "base", mode: "light" }}
after={{ style: "base", mode: "dark" }}
className="h-96"
/>Essayez-le, puis ajoutez-le comme point de départ :
Projets React. Installe components/unmap-map-compare.tsx ; affichez <MapCompare apiKey={key} />.
La même chose en Vue :
<script setup lang="ts">
import { ref } from "vue";
import { UnmapCompareSwipe } from "@/components/ui/compare-swipe";
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
const position = ref(50);
</script>
<template>
<UnmapCompareSwipe
v-model:position="position"
:api-key="apiKey"
:center="[-123.12, 49.28]"
:zoom="12"
:before="{ mapStyle: 'base', mode: 'light' }"
:after="{ mapStyle: 'base', mode: 'dark' }"
class="h-96"
/>
</template>Une couche qui appartient à un seul côté va dans les children de ce côté, avec le mapId de ce côté :
<CompareSwipe
apiKey={key}
center={[-123.12, 49.28]}
zoom={12}
after={{ children: <GeoJSONLayer mapId="after" id="parks" data={parks} /> }}
/>befores'affiche à gauche du séparateur etafterà droite ;orientation="horizontal"placebeforeau-dessus etafteren dessous.beforeetafterprennent les propriétés de la carte pour ce côté : style, mode, thème, superpositions, langue. La clé, la caméra (center,zoom,bearing,pitch) et les limites de la caméra se fixent une seule fois, sur la comparaison. Réglez les limites avecmaxBounds,minZoometmaxZoomà cet endroit, pour que les deux cartes les partagent.- Une couche d'un seul côté va dans les
childrende ce côté (Vue : l'emplacement#beforeou#after), et elle a besoin de l'identifiant de la carte de ce côté :mapId="before"oumapId="after"(Vue :map-id). Sans lui, la couche cherche la carte par défaut, qui n'est aucune des deux.beforeIdetafterIdrenomment les deux cartes, et la couche prend le nouveau nom. - Le séparateur est un séparateur de fenêtre : faites-le glisser, ou donnez-lui le focus et
utilisez les flèches (5 % par pression,
steppour changer), Début et Fin.positionetonPositionChange(Vue :v-model:position) le contrôlent, etdefaultPositionfixe son point de départ. - Pour dessiner d'un seul côté, montez
drawdans leschildrende ce côté, avec lemapIdde ce côté. Du côtéafter, placez-le dans un coin de droite (position="bottom-right"; les boutons de zoom occupent le coin supérieur droit), pour que le séparateur ne passe pas sur la barre d'outils en se déplaçant. onViewportChange(Vue :@viewport-change) signale la caméra partagée une fois par mouvement.- Les boutons de zoom et de boussole s'affichent sur la carte qui occupe le coin supérieur droit :
la carte
after, ou la cartebeforeen mode horizontal. Donnez à l'autre côté ses proprescontrolspour les y afficher aussi. - Le cadre fait au moins 320 px de haut. Réglez sa hauteur avec
className(h-96), ou aveccontainerStyleen React etstyleen Vue, qui peuvent aussi fixerminHeight. - Le centre, le zoom, l'orientation et l'inclinaison sont synchronisés ; le roulis ne l'est pas.
- Pincer avec un doigt de chaque côté du séparateur n'est pas pris en charge, parce que chaque carte ne voit qu'un doigt et qu'elles tirent en sens contraire. Pincez d'un seul côté. Un nouveau geste sur un côté arrête l'élan de l'autre côté, si bien que les deux ne se contrarient jamais.
- Chaque carte charge son propre style et ses propres tuiles, et chaque tuile est une requête : une comparaison fait environ deux fois les requêtes d'une seule carte, et elle est facturée à la requête comme le reste.
lang="fr"nomme le séparateur en français pour les lecteurs d'écran.
Profil d'altitude
elevation-profile trace l'altitude du sol le long d'une ligne sur votre Chart shadcn : un
itinéraire de @unmap/routing ou du panneau d'itinéraire, une ligne tracée à la main, ou vos
propres coordonnées. Il échantillonne la ligne tous les interval mètres le long du grand cercle,
demande les hauteurs à l'API d'altitude unmap, et affiche la distance, le dénivelé positif et
négatif, le point le plus bas et le plus haut. Survolez le graphique et un repère suit la ligne
sur la carte ; survolez la ligne et le graphique suit.
L'exemple ci-dessous calcule une sortie à vélo près de Canmore et fait flotter le profil dans votre
Card au bas de la carte dès qu'il y a un itinéraire. Sur un téléphone, les deux cartes occupent
toute la largeur ; à partir de sm, le formulaire d'itinéraire se place en haut à gauche et le
profil en bas à droite, sans cacher l'attribution de la carte.
"use client";
import { useState } from "react";
import type { LngLat } from "@unmap/routing";
import { Card, CardContent } from "@/components/ui/card";
import { Map } from "@/components/ui/map";
import { RoutingPanel } from "@/components/ui/routing-panel";
import { ElevationProfile } from "@/components/ui/elevation-profile";
export function RideProfile() {
const apiKey = process.env.NEXT_PUBLIC_UNMAP_KEY!;
const [route, setRoute] = useState<LngLat[] | null>(null);
return (
<div className="h-[640px]">
<Map apiKey={apiKey} style="outdoor" center={[-115.57, 51.18]} zoom={10}>
<Card className="absolute inset-x-4 top-4 z-10 sm:inset-x-auto sm:top-6 sm:left-6 sm:w-80">
<CardContent>
<RoutingPanel apiKey={apiKey} mode="bicycle" onRoute={(next) => setRoute(next.coordinates)} />
</CardContent>
</Card>
{route ? (
<Card className="absolute inset-x-4 bottom-10 z-10 sm:inset-x-auto sm:right-6 sm:w-[28rem] motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-bottom-4 motion-safe:duration-200">
<CardContent>
<ElevationProfile apiKey={apiKey} line={route} />
</CardContent>
</Card>
) : null}
</Map>
</div>
);
}Essayez-le, puis ajoutez-le comme point de départ :
Projets React. Installe components/unmap-map-elevation.tsx ; affichez <MapElevation apiKey={key} />.
lineaccepte des paires[lng, lat], une LineString GeoJSON, une Feature qui en contient une, ou une FeatureCollection comme lavaluede l'outil de dessin, dont la ligne la plus récente est profilée (les points et les polygones à côté sont ignorés). Toute autre valeur, ou moins de deux points, affiche une invite et n'envoie rien.- Chaque requête compte pour un appel et porte jusqu'à 500 points. L'espacement par défaut place
au plus 500 échantillons sur la ligne et ne descend jamais sous 30 m, une limite d'espacement
du composant indépendante de la résolution native du MNA : un profil coûte donc un appel. Un
intervalplus petit coûte un appel par tranche de 500 échantillons, et une ligne n'est jamais échantillonnée plus de 5 000 fois, soit 10 appels au plus. Chaque requête est facturée, y compris celle qu'on annule. Les nouvelles tentatives automatiques du client sont désactivées : une requête en échec n'est jamais renvoyée à votre insu. Le bouton Réessayer est le seul moyen de la renvoyer, et chaque clic compte pour un appel. - Une ligne qui change sans cesse, comme un sommet qu'on déplace, n'est envoyée qu'une fois immobile pendant 300 ms : un déplacement ne déclenche pas une requête à chaque mouvement. Une nouvelle ligne annule la requête en cours, et la même ligne passée de nouveau, même dans un nouveau tableau, n'envoie rien.
- Pour afficher de nouveau une ligne sans la payer une deuxième fois, passez
cache, par exemple unenew Map()que vous conservez (Vue ::cache). Une ligne dont les échantillons s'y trouvent affiche son profil tout de suite, sans requête ni attente, et chaque profil obtenu y est ajouté. Videz-le quand la clé change. - Les hauteurs sont en mètres dans le datum du MNA actif. Avec la publication mondiale 6, les datums et modèles de terrain/surface sont mixtes. Là où un échantillon tombe hors de la zone couverte, le graphique laisse un vide au lieu de tracer zéro, et le dénivelé ne compte que les échantillons qui ont une hauteur. Une ligne sans aucune couverture affiche l'état « aucune donnée d'altitude ».
- Le dénivelé positif et négatif est la somme brute des montées et des descentes entre échantillons consécutifs : il peut sembler élevé sur un terrain accidenté. Une défaillance affiche une alerte à la place du graphique : une clé refusée par l'API, une limite de débit, une ligne trop grande pour être envoyée, un service d'altitude qui n'a pas répondu, ou une requête rejetée par l'API. Une limite de débit ou une requête sans réponse offre un bouton Réessayer ; les autres cas n'en offrent pas, car renvoyer la même requête n'y changerait rien.
- Le dénivelé positif est le chiffre principal, avec la distance, le dénivelé négatif, le point le plus bas et le plus haut à côté. Pendant le chargement d'une nouvelle ligne, le profil précédent reste affiché, estompé, et le premier chargement réserve la place du résultat : le panneau ne saute pas. Une ligne de source sous le graphique renvoie aux mentions et licences des fournisseurs du MNA actif ; laissez-la visible. Les lecteurs d'écran ignorent le graphique, lisent les statistiques et annoncent la distance et le dénivelé positif à l'arrivée d'un profil.
lang="fr"passe les libellés et les nombres en français.onProfilereçoit les échantillons et les statistiques une fois chargés (nulls'il n'y en a pas), par exemple pour colorer uneroute-lineselon l'altitude avec songradient; un échantillon hors de la zone couverte a une altitudenull, alors ignorez-le ou comblez-le.onHoverreçoit l'échantillon sous le pointeur.- Il n'ajoute qu'une ligne invisible de 16 px à survoler, et le repère, au-dessus de la carte :
tracez la ligne elle-même avec
route-line, le panneau d'itinéraire ou votre outil de dessin. Les deux reviennent après un changement de style. Le repère prend votre--primary(ou la couleur d'itinéraire du thème) ; le graphique prend--chart-1. Le graphique est votrechartshadcn, soit Recharts en React : installer l'élément apporterecharts3, alors un projet dont lechart.tsxest encore sur Recharts 2 doit d'abord mettre son graphique à jour. - Le panneau d'itinéraire transmet chaque itinéraire qu'il trace à
onRoute(Vue :@route), et c'est ainsi que l'exemple ci-dessus alimente le profil.
En Vue, le graphique est le chart de shadcn-vue, qui dessine avec Unovis : ajouter l'élément
installe donc aussi les paquets d'Unovis. @profile et @hover sont les événements de
onProfile et onHover.
<script setup lang="ts">
import { ref } from "vue";
import type { LngLat } from "@unmap/routing";
import { Card, CardContent } from "@/components/ui/card";
import { UnmapMap } from "@/components/ui/map";
import { UnmapRoutingPanel } from "@/components/ui/routing-panel";
import { UnmapElevationProfile } from "@/components/ui/elevation-profile";
const apiKey = import.meta.env.VITE_UNMAP_KEY;
const route = ref<LngLat[] | null>(null);
</script>
<template>
<div class="h-[640px]">
<UnmapMap :api-key="apiKey" map-style="outdoor" :center="[-115.57, 51.18]" :zoom="10">
<Card class="absolute inset-x-4 top-4 z-10 sm:inset-x-auto sm:top-6 sm:left-6 sm:w-80">
<CardContent>
<UnmapRoutingPanel :api-key="apiKey" mode="bicycle" @route="route = $event.coordinates" />
</CardContent>
</Card>
<Card
v-if="route"
class="absolute inset-x-4 bottom-10 z-10 sm:inset-x-auto sm:right-6 sm:w-[28rem] motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-bottom-4 motion-safe:duration-200"
>
<CardContent>
<UnmapElevationProfile :api-key="apiKey" :line="route" />
</CardContent>
</Card>
</UnmapMap>
</div>
</template>