Aller au contenu

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-layer

La 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émentRôle
map-layersLe cycle de vie de couches partagé par les éléments de données. Installé automatiquement.
route-lineTrace 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-layerTrace n'importe quel GeoJSON en surfaces, lignes et points dans votre --primary, avec surbrillance au survol et événements de clic.
cluster-layerRegroupe de nombreux points : effectifs, clic pour zoomer, et propriétés additionnées.
heatmap-layerDensité de points en carte de chaleur sur votre --primary, pondérée par une propriété.
arc-layerConnexions courbes entre paires de lieux, avec survol et clic. Les directions opposées s'écartent l'une de l'autre.
flow-layerFlux 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-legendUne 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 :

npx @unmap/cli add map-geojson-01

Projets React. Installe components/unmap-map-geojson.tsx ; affichez <MapGeojson apiKey={key} />.

Ouvrir la page de l'exemple

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 :

npx @unmap/cli add map-clusters-01

Projets React. Installe components/unmap-map-clusters.tsx ; affichez <MapClusters apiKey={key} />.

Ouvrir la page de l'exemple

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 :

npx @unmap/cli add map-heatmap-01

Projets React. Installe components/unmap-map-heatmap.tsx ; affichez <MapHeatmap apiKey={key} />.

Ouvrir la page de l'exemple

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 :

npx @unmap/cli add map-arcs-01

Projets React. Installe components/unmap-map-arcs.tsx ; affichez <MapArcs apiKey={key} />.

Ouvrir la page de l'exemple

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 :

npx @unmap/cli add map-flows-01

Projets React. Installe components/unmap-map-flows.tsx ; affichez <MapFlows apiKey={key} />.

Ouvrir la page de l'exemple

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. Passez defaultValue pour en désactiver dès le départ, ou value et onValueChange (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. color en fixe une et color: false la retire. Les valeurs de filtre n'ont une pastille que si vous leur donnez une color.
  • 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 filter d'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-control est 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 avec setLayoutProperty. Elle accepte value, defaultValue et onValueChange (Vue : v-model et default-value), avec les lignes identifiées par superposition, comme energy.wells. lang="fr" écrit le titre et les lignes en français (React exporte aussi les noms sous ENERGY_LABELS_FR), et colors associe à un identifiant de superposition une couleur de pastille, ou false pour 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>