Couches de données
Chaque élément de couche ajoute sa propre source et ses couches sous les étiquettes de la carte, les colore à partir de vos jetons de design et les ajoute de nouveau après un changement de style. map-layers est le cycle de vie partagé qu'ils utilisent et s'installe avec eux.
npx shadcn@latest registry add @unmap=https://registry.unmap.dev/r/{name}.json
npx shadcn@latest add @unmap/geojson-layernpx shadcn-vue@latest add https://registry.unmap.dev/r/vue/geojson-layer.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 |
|---|---|
map-layers | Le cycle de vie de couches partagé par les éléments de données. Installé automatiquement. |
route-line | Trace un itinéraire ou toute ligne : bordure du thème, variantes, progression parcourue et dégradé de couleur selon une valeur par point. Le panneau d'itinéraire l'utilise. |
geojson-layer | Trace n'importe quel GeoJSON en surfaces, lignes et points dans votre --primary, avec surbrillance au survol et événements de clic. |
cluster-layer | Regroupe de nombreux points : effectifs, clic pour zoomer, et propriétés additionnées. |
heatmap-layer | Densité de points en carte de chaleur sur votre --primary, pondérée par une propriété. |
arc-layer | Connexions courbes entre paires de lieux, avec survol et clic. Les directions opposées s'écartent l'une de l'autre. |
flow-layer | Flux origine-destination façon flowmap.blue : largeur selon le volume, flèches, totaux par lieu, filtres temporels et top-N, regroupement optionnel selon le zoom. |
layer-legend | Une case à cocher par ligne pour afficher ou masquer des couches du style, avec des filtres par valeur et des pastilles de couleur facultatifs, sur vos Field et Checkbox. Résiste à un changement de style. |
Tracer votre propre itinéraire
route-line trace n'importe quelle liste de points [lng, lat] ; un itinéraire de @unmap/routing s'y branche directement :
import { Map } from "@/components/ui/map";
import { RouteLine } from "@/components/ui/route-line";
const route = await router.route(from, to, { mode: "auto" });
<Map apiKey={key} center={from} zoom={12}>
<RouteLine coordinates={route.geometry.coordinates} />
</Map>La ligne passe sous les étiquettes de la carte, prend votre --primary (ou la couleur
d'itinéraire du thème) et revient après un changement de mode ou de thème. active={false}
trace une variante en --muted-foreground, progress={0.4} montre l'avancement d'un trajet, et
gradient={{ values }} colore la ligne selon une valeur par point sur vos --chart-1 à
--chart-5. Une variante reste sous l'itinéraire actif, peu importe l'ordre de rendu.
En Vue, le composant s'appelle UnmapRouteLine et prend les mêmes props :
<script setup lang="ts">
import { ref } from "vue";
import { Router, type RouteResult } from "@unmap/routing";
import { UnmapMap } from "@/components/ui/map";
import { UnmapRouteLine } from "@/components/ui/route-line";
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
const route = ref<RouteResult | null>(null);
new Router({ key: apiKey })
.route([-114.0719, 51.0447], [-113.9871, 51.0899], { mode: "auto" })
.then((result) => (route.value = result));
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-114.07, 51.05]" :zoom="12">
<UnmapRouteLine v-if="route" :coordinates="route.geometry.coordinates" />
</UnmapMap>
</template>Tracer vos propres données
Cinq couches tracent les données que vous apportez : GeoJSON, regroupements, carte de chaleur,
arcs et flux. En Vue, ce sont UnmapGeojsonLayer, UnmapClusterLayer, UnmapHeatmapLayer,
UnmapArcLayer et UnmapFlowLayer, avec les mêmes propriétés en kebab-case dans le template.
GeoJSON
geojson-layer trace n'importe quel GeoJSON (une FeatureCollection, une Feature, ou une URL)
en surfaces, lignes et points, filtrés selon le type de géométrie de chaque entité. Activez
interactive (ou passez onClick) et l'entité sous le pointeur est mise en surbrillance et
signalée :
import { Map } from "@/components/ui/map";
import { GeoJSONLayer } from "@/components/ui/geojson-layer";
<Map apiKey={key} center={[-114.07, 51.05]} zoom={11}>
<GeoJSONLayer data={parks} onClick={(event) => console.log(event.feature.properties)} />
</Map>Essayez-le, puis ajoutez-le comme point de départ :
Projets React. Installe components/unmap-map-geojson.tsx ; affichez <MapGeojson apiKey={key} />.
En Vue, interactive ne peut pas être déduit d'un écouteur comme onClick le permet en React ;
passez-le explicitement :
<script setup lang="ts">
import { UnmapMap } from "@/components/ui/map";
import { UnmapGeojsonLayer } from "@/components/ui/geojson-layer";
import type { MapFeatureEvent } from "@/lib/map-layers";
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
const parks = { type: "FeatureCollection", features: [] };
function onClick(event: MapFeatureEvent) {
console.log(event.feature.properties);
}
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-114.07, 51.05]" :zoom="11">
<UnmapGeojsonLayer :data="parks" interactive @click="onClick" />
</UnmapMap>
</template>Regroupements
cluster-layer prend de nombreuses entités Point et les regroupe avec le clustering natif de
MapLibre (des Point seulement : un MultiPoint n'est pas regroupé). Un clic sur un groupe
zoome, et sum additionne une propriété numérique sur chaque groupe :
import { Map } from "@/components/ui/map";
import { ClusterLayer } from "@/components/ui/cluster-layer";
<Map apiKey={key} center={[-114.07, 51.05]} zoom={11}>
<ClusterLayer data={clinics} sum={["beds"]} />
</Map>Cliquez sur un groupe et la carte zoome jusqu'à ce qu'il se sépare :
Projets React. Installe components/unmap-map-clusters.tsx ; affichez <MapClusters apiKey={key} />.
La même chose en Vue :
<script setup lang="ts">
import { UnmapMap } from "@/components/ui/map";
import { UnmapClusterLayer } from "@/components/ui/cluster-layer";
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
const clinics = { type: "FeatureCollection", features: [] };
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-114.07, 51.05]" :zoom="11">
<UnmapClusterLayer :data="clinics" :sum="['beds']" />
</UnmapMap>
</template>Carte de chaleur
heatmap-layer trace la densité de points plutôt que des points individuels, sur votre
--primary sauf si vous passez colors. weight pondère chaque point par une propriété
numérique. Les couches GeoJSON, de regroupement et de chaleur passent sous les étiquettes de la
carte, prennent vos jetons et reviennent après un changement de mode ou de thème, comme
route-line :
import { Map } from "@/components/ui/map";
import { HeatmapLayer } from "@/components/ui/heatmap-layer";
<Map apiKey={key} center={[-114.07, 51.05]} zoom={11}>
<HeatmapLayer data={collisions} weight="injuries" />
</Map>Changez la pondération et regardez les zones chaudes se déplacer :
Projets React. Installe components/unmap-map-heatmap.tsx ; affichez <MapHeatmap apiKey={key} />.
La même chose en Vue :
<script setup lang="ts">
import { UnmapMap } from "@/components/ui/map";
import { UnmapHeatmapLayer } from "@/components/ui/heatmap-layer";
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
const collisions = { type: "FeatureCollection", features: [] };
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-114.07, 51.05]" :zoom="11">
<UnmapHeatmapLayer :data="collisions" weight="injuries" />
</UnmapMap>
</template>Arcs
arc-layer trace des connexions courbes entre paires de lieux : vols, routes commerciales,
recommandations. Les directions opposées s'écartent l'une de l'autre, si bien qu'un aller-retour
ne se superpose jamais. Chaque arc est { id, from, to }, avec des extrémités
[longitude, latitude], et onClick ou onHover signale l'arc sous le pointeur :
import { Map } from "@/components/ui/map";
import { ArcLayer } from "@/components/ui/arc-layer";
const flights = [
{ id: "yyc-yul", from: [-114.01, 51.13], to: [-73.74, 45.47] },
{ id: "yul-yyc", from: [-73.74, 45.47], to: [-114.01, 51.13] },
];
<Map apiKey={key} center={[-94, 50]} zoom={3}>
<ArcLayer data={flights} onClick={(event) => console.log(event.feature.properties)} />
</Map>Cliquez sur un arc ou choisissez une ville pour lire la liaison et sa distance :
Projets React. Installe components/unmap-map-arcs.tsx ; affichez <MapArcs apiKey={key} />.
En Vue, activez interactive pour que @click et @hover se déclenchent :
<script setup lang="ts">
import { UnmapMap } from "@/components/ui/map";
import { UnmapArcLayer } from "@/components/ui/arc-layer";
import type { MapFeatureEvent } from "@/lib/map-layers";
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
const flights: { id: string; from: [number, number]; to: [number, number] }[] = [
{ id: "yyc-yul", from: [-114.01, 51.13], to: [-73.74, 45.47] },
{ id: "yul-yyc", from: [-73.74, 45.47], to: [-114.01, 51.13] },
];
function onClick(event: MapFeatureEvent) {
console.log(event.feature.properties);
}
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-94, 50]" :zoom="3">
<UnmapArcLayer :data="flights" interactive @click="onClick" />
</UnmapMap>
</template>Flux
flow-layer trace des flux origine-destination façon flowmap.blue : largeur selon le volume,
flèches à mi-ligne qui montrent la direction, et un cercle à chaque lieu dont la taille suit tout
ce qui y entre et en sort. locations est { id, name?, lat, lon }[] et flows est
{ origin, dest, count, time? }[], où origin et dest sont des identifiants de lieux ; topN
ne garde que les plus grandes paires et cluster regroupe les lieux proches à mesure qu'on dézoome :
import { Map } from "@/components/ui/map";
import { FlowLayer } from "@/components/ui/flow-layer";
<Map apiKey={key} center={[-96, 62]} zoom={3}>
<FlowLayer locations={provinces} flows={migration} topN={50} />
</Map>Les flux d'une même paire sont additionnés. Un flux d'un lieu vers lui-même, ou entre deux lieux
aux mêmes coordonnées, n'est jamais tracé. timeRange compare les nombres, et les chaînes
numériques comme "2020", en tant que nombres ; sinon, il lit les chaînes comme des dates et les
nombres comme des millisecondes depuis l'époque Unix. Un flux dont la date ne s'interprète pas,
comme une étiquette 2020/2021, est toujours conservé, tout comme un flux sans date.
La largeur des lignes et la taille des cercles suivent l'étendue de ce qui est tracé : le plus
grand flux tracé est donc toujours le plus large. Pour comparer des années, passez le même
countDomain et le même totalDomain (count-domain et total-domain en Vue) à chaque année,
par exemple [0, plusGrandFlux] : une largeur représente alors le même nombre d'une année à
l'autre, et un nombre hors de l'intervalle est ramené à sa borne.
Les totaux par lieu et les regroupements ne comptent que les paires tracées, après minCount et
topN : les petits flux que vous écartez ne s'additionnent pas dans un regroupement. Un
regroupement indique places, le nombre de lieux qu'il contient, et n'a pas de nom : c'est à vous
d'en formuler le libellé. La couche se reconstruit chaque fois que locations ou flows est un
nouveau tableau : pour les grandes tables, passez des tableaux stables (un état ou useMemo en
React, un ref ou un computed en Vue).
Survolez ou touchez une province pour voir ses entrées et ses sorties :
Projets React. Installe components/unmap-map-flows.tsx ; affichez <MapFlows apiKey={key} />.
En Vue, flow-layer prend aussi interactive, et signale, selon le cas, le flux ou le lieu sous
le pointeur via @hover :
<script setup lang="ts">
import { UnmapMap } from "@/components/ui/map";
import { UnmapFlowLayer } from "@/components/ui/flow-layer";
import type { MapFeatureEvent } from "@/lib/map-layers";
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
const provinces: { id: string; lat: number; lon: number }[] = [];
const migration: { origin: string; dest: string; count: number }[] = [];
function onHover(event: MapFeatureEvent | null) {
console.log(event?.feature.properties);
}
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-96, 62]" :zoom="3">
<UnmapFlowLayer :locations="provinces" :flows="migration" :top-n="50" interactive @hover="onHover" />
</UnmapMap>
</template>Légende des couches
layer-legend affiche une case à cocher par ligne pour afficher ou masquer des couches du style,
sur vos composants shadcn Field et Checkbox. Chaque ligne nomme les couches qu'elle contrôle. Un
identifiant qui se termine par * désigne toutes les couches qui commencent par le reste :
stations-* couvre toutes les couches que dessine un <GeoJSONLayer id="stations">, et
overlay_energy.wells_* les couches de cercles et d'étiquettes que la passerelle ajoute pour la
superposition des puits. Une ligne dotée d'un filter reçoit une case par valeur d'une propriété
des entités, et décocher une valeur masque les entités qui la portent :
import { Map } from "@/components/ui/map";
import { GeoJSONLayer } from "@/components/ui/geojson-layer";
import { LayerLegend } from "@/components/ui/layer-legend";
<Map apiKey={key} center={[-114.07, 51.05]} zoom={11}>
<GeoJSONLayer id="stations" data={stations} />
<LayerLegend
title="Recharge"
items={[
{
id: "stations",
label: "Bornes",
layers: ["stations-*"],
filter: {
property: "status",
values: [
{ value: "open", label: "Ouvertes" },
{ value: "planned", label: "Prévues" },
],
},
},
]}
/>
</Map>- L'état est
{ hidden, excluded }: les identifiants des lignes désactivées et, par ligne, les valeurs désactivées. PassezdefaultValuepour en désactiver dès le départ, ouvalueetonValueChange(Vue :v-model) pour le contrôler. - La légende gère la visibilité des couches qu'elle nomme : une ligne cochée les rend visibles.
- Une couche appartient à une seule ligne pour toutes les légendes de la carte. Dans une légende, la première ligne qui la nomme décide. La première légende qui s'applique à une couche (en général la première montée) en gère la visibilité et le filtre jusqu'à ce qu'elle soit démontée ou cesse de la nommer. D'ici là, les cases d'une autre légende pour cette couche ne changent rien ; ensuite, cette légende prend le relais à son prochain passage, la prochaine fois que le style de la carte change, et applique ses propres valeurs au filtre d'origine de la couche à la place de celles de l'ancienne légende.
- Chaque ligne affiche une pastille de la couleur de sa première couche quand cette couleur est
une valeur simple.
coloren fixe une etcolor: falsela retire. Les valeurs de filtre n'ont une pastille que si vous leur donnez unecolor. - Un changement de style (mode, style, thème ou
lang) conserve ce qui a été désactivé, car la légende réapplique son état après chaque changement. Une couche ajoutée plus tard, par une superposition ou un autre élément, reprend l'état dès qu'elle apparaît, et un identifiant absent du style est ignoré. - Retirer le
filterd'une ligne redonne à ses couches leur propre filtre, et une valeur décochée que la ligne ne liste plus est abandonnée : aucune entité ne reste masquée sans case pour la réafficher. La mémoire du filtre que la légende a écrit est conservée par carte : une légende dans un Popover ou un onglet qui se démonte puis se remonte sait donc encore ce qu'elle a modifié. - Démonter une légende laisse la carte telle quelle. Passez
value(Vue :v-model) et gardez l'état dans votre propre composant pour que les cases restent d'accord après un remontage. energy-layer-controlest cette légende préremplie avec les superpositions énergie. Elle affiche un simple fieldset : enveloppez-la dans votre Card ou votre Popover quand elle flotte sur la carte. La légende gère la visibilité de ces couches : pilotez-la avec les cases, pas avecsetLayoutProperty. Elle acceptevalue,defaultValueetonValueChange(Vue :v-modeletdefault-value), avec les lignes identifiées par superposition, commeenergy.wells.lang="fr"écrit le titre et les lignes en français (React exporte aussi les noms sousENERGY_LABELS_FR), etcolorsassocie à un identifiant de superposition une couleur de pastille, oufalsepour masquer la pastille de cette ligne ; une ligne omise garde la couleur de sa couche.
<script setup lang="ts">
import { ref } from "vue";
import { UnmapMap } from "@/components/ui/map";
import { UnmapGeojsonLayer } from "@/components/ui/geojson-layer";
import { UnmapLayerLegend } from "@/components/ui/layer-legend";
const apiKey = import.meta.env.VITE_UNMAP_KEY;
const stations = { type: "FeatureCollection", features: [] };
const items = [
{
id: "stations",
label: "Bornes",
layers: ["stations-*"],
filter: {
property: "status",
values: [
{ value: "open", label: "Ouvertes" },
{ value: "planned", label: "Prévues" },
],
},
},
];
const legend = ref({ hidden: [] as string[], excluded: { stations: ["planned"] } });
</script>
<template>
<UnmapMap :api-key="apiKey" :center="[-114.07, 51.05]" :zoom="11">
<UnmapGeojsonLayer id="stations" :data="stations" />
<UnmapLayerLegend v-model="legend" title="Recharge" :items="items" />
</UnmapMap>
</template>