Aller au contenu

Carte et marqueurs

map monte une carte MapLibre sur la passerelle unmap et la partage avec chaque composant placé à l'intérieur. Les marqueurs, les infobulles et les contrôles s'y placent et suivent vos jetons de design. Composants couvre l'installation, les deux voies et le style.

npx shadcn@latest registry add @unmap=https://registry.unmap.dev/r/{name}.json
npx shadcn@latest add @unmap/map

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-contextLe magasin de carte partagé dont dépendent les autres éléments. Installé automatiquement.
map-themesDes préréglages de thème nommés (u00 et suivants) pour la propriété theme de la carte. Composez le vôtre sur unmap.dev/create.
mapLe composant d'affichage de la carte. Monte un conteneur, crée une carte MapLibre sur la passerelle unmap et la partage avec ses enfants. Restyle les infobulles et les contrôles MapLibre sur vos jetons.
marker-popupDes marqueurs, des infobulles, des bulles de survol et des étiquettes déclaratifs positionnés sur la carte. Les épingles peuvent être déplacées.
map-controlsDes boutons de zoom, de boussole, de localisation, de plein écran, de globe et de relief 3D sur vos propres ButtonGroup, Button et Tooltip. S'associe à controls={false} sur la carte.

Marqueurs et interaction

Un <Marker> peut être déplacé, étiqueté et doté d'une bulle de survol. draggable permet au lecteur de déplacer l'épingle et onDragEnd (Vue : @dragend) reçoit l'endroit où elle a été déposée. L'épingle y reste, et lngLat la déplace de nouveau dès que ses coordonnées changent : renvoyez donc la position déposée dans votre état si vous en gardez un. Pour refuser un dépôt, donnez à lngLat une valeur différente : la même valeur n'est pas un changement et laisse l'épingle là où elle a été déposée. <MarkerLabel> écrit un texte à côté de l'épingle (position vaut right, left, top ou bottom), <MarkerTooltip> affiche une petite bulle tant que le pointeur la survole, et <Popup trigger="hover"> ouvre une infobulle complète de la même façon.

import { Map } from "@/components/ui/map";
import { Marker, MarkerLabel, MarkerTooltip, Popup } from "@/components/ui/marker-popup";
 
<Map apiKey={key} center={[-114.07, 51.05]} zoom={11} maxBounds="canada" minZoom={3} hash>
  <Marker lngLat={site} draggable onDragEnd={setSite}>
    <MarkerLabel>Site A</MarkerLabel>
    <MarkerTooltip>Glisser pour déplacer</MarkerTooltip>
    <Popup>Détails du site A</Popup>
  </Marker>
</Map>

Essayez-le, puis ajoutez-le comme point de départ :

npx @unmap/cli add map-markers-01

Projets React. Installe components/unmap-map-markers.tsx ; affichez <MapMarkers 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 { UnmapMarker, UnmapMarkerLabel, UnmapMarkerTooltip, UnmapPopup } from "@/components/ui/marker-popup";
 
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
const site: [number, number] = [-114.07, 51.05];
function onDragEnd(lngLat: [number, number]) {
  console.log(lngLat);
}
</script>
 
<template>
  <UnmapMap :api-key="apiKey" :center="[-114.07, 51.05]" :zoom="11" max-bounds="canada" :min-zoom="3" hash>
    <UnmapMarker :lng-lat="site" draggable @dragend="onDragEnd">
      <UnmapMarkerLabel>Site A</UnmapMarkerLabel>
      <UnmapMarkerTooltip>Glisser pour déplacer</UnmapMarkerTooltip>
      <UnmapPopup>Détails du site A</UnmapPopup>
    </UnmapMarker>
  </UnmapMap>
</template>
  • Le survol, c'est le pointeur sur l'épingle ou le focus clavier sur elle ; un appui ne compte pas. Les écrans tactiles n'ont pas de survol : une bulle de survol n'y apparaît jamais, et une épingle qui a une bulle mais pas d'infobulle ne peut recevoir le focus que si elle est draggable (une épingle par défaut). Gardez donc tout ce dont le lecteur a besoin dans une étiquette ou une infobulle. Une infobulle trigger="hover" s'ouvre quand même d'un toucher, et un clic ou la touche Entrée la bascule sur tout écran. Elle se ferme quand le pointeur ou le focus quitte l'épingle, donc son contenu est inatteignable : gardez-la en lecture seule, sans bouton de fermeture (désactivé par défaut), et utilisez le déclencheur par clic, celui par défaut, pour tout contenu interactif. La bulle de survol suit l'épingle quand elle bouge, s'efface tant que l'infobulle de l'épingle est ouverte, et n'apparaît jamais à côté d'une infobulle trigger="hover", qui s'ouvre au même survol.
  • interactive={false} (Vue : :interactive="false") coupe le déplacement, le zoom, la rotation et le clavier, pour une carte qui sert d'image. Les épingles, même déplaçables, les infobulles et les boutons de zoom continuent de fonctionner. Le réactiver respecte ce que l'option maplibre brute a désactivé, comme dragRotate: false. cooperativeGestures (Vue : cooperative-gestures) demande Ctrl ou Cmd avec la molette, et deux doigts sur un téléphone, pour qu'une carte dans une longue page ne capture pas le défilement.
  • maxBounds garde la caméra dans [[ouest, sud], [est, nord]], ou dans tout le Canada avec une marge quand il vaut "canada". minZoom et maxZoom bornent le zoom. Chacune de ces props se rabat sur l'option maplibre brute quand elle n'est pas définie. Une limite de zoom que MapLibre refuserait (un minZoom inférieur à -2 ou supérieur au zoom maximal, un maxZoom inférieur à -2, ou une valeur qui n'est pas un nombre fini) est abandonnée avec un avertissement dans la console plutôt que de lever une erreur, et si minZoom dépasse maxZoom, les deux sont abandonnés. Un maxZoom au-delà de 22 est conservé tel quel. Quand une limite déplace la caméra, onViewportChange (Vue : @viewport-change) indique où elle est allée.
  • hash garde la caméra dans l'URL de la page (#zoom/lat/lng) : un rechargement ou un lien partagé ouvre la même vue, et une position déjà présente dans l'URL l'emporte sur center et zoom. Donnez-lui un nom (hash="map") pour conserver le reste du fragment, ou pour mettre deux cartes dans une même URL. Le désactiver, ou retirer la carte, enlève la partie de la carte dans l'URL. Une carte recréée par une nouvelle apiKey repart de la caméra des props.
  • Chacune de ces props change sur place, sans reconstruire la carte. Le style compact des bulles de survol vient avec l'élément map. Les bulles de survol portent la classe unmap-tooltip et les étiquettes un attribut data-unmap-marker-label, comme points d'accroche pour votre propre CSS.

Contrôles de la carte

map-controls ajoute sur la carte des boutons de zoom, de boussole, de localisation, de plein écran, de globe et de relief 3D, construits sur vos ButtonGroup, Button et Tooltip, avec les icônes de votre bibliothèque d'icônes. Désactivez les boutons propres de la carte avec controls={false} pour que les deux jeux ne s'empilent pas dans le même coin. L'attribution reste : la licence d'OpenStreetMap l'exige.

import { Map } from "@/components/ui/map";
import { MapControls } from "@/components/ui/map-controls";
 
<Map apiKey={key} style="outdoor" center={[-115.57, 51.18]} zoom={11} controls={false}>
  <MapControls position="top-right" locate fullscreen globe terrain />
</Map>
<script setup lang="ts">
import { UnmapMap } from "@/components/ui/map";
import { UnmapMapControls } from "@/components/ui/map-controls";
 
const apiKey = import.meta.env.VITE_UNMAP_KEY!;
</script>
 
<template>
  <UnmapMap :api-key="apiKey" map-style="outdoor" :center="[-115.57, 51.18]" :zoom="11" :controls="false">
    <UnmapMapControls position="top-right" locate fullscreen globe terrain />
  </UnmapMap>
</template>
  • Le zoom et la boussole sont activés par défaut ; locate, fullscreen, globe et terrain s'activent sur demande, et zoom={false} ou compass={false} retire un bouton par défaut. position accepte n'importe quel coin. Le zoom avant et le zoom arrière se désactivent aux limites de zoom de la carte.
  • L'aiguille de la boussole suit l'orientation, et un clic remet la carte face au nord et à plat.
  • locate demande au navigateur la position du lecteur, s'y rend et la marque d'un point dans votre --primary. Quand le navigateur refuse, ne trouve pas de position ou n'offre aucune localisation, une Alert sous les boutons l'indique. Passez locateAlert={false} et gérez onLocateError (Vue : @locate-error) pour afficher votre propre message. onLocate (Vue : @locate) reçoit la position. La géolocalisation exige HTTPS.
  • fullscreen met la carte et ses contrôles en plein écran. Là où la page ne peut pas passer en plein écran, comme un iframe sans autorisation ou Safari sur iPhone, la carte remplit plutôt la fenêtre, et Échap l'en fait sortir. Tout ce qui s'ouvre dans un portail, comme ces bulles de survol, les suggestions du géocodeur ou un menu déroulant, reste hors de la carte en plein écran dans les deux cas : le plein écran natif n'affiche que l'élément de la carte, et la carte qui remplit la fenêtre couvre le reste de la page avec le z-index de 99999 de MapLibre. Chaque bouton garde son nom accessible. Pour garder un portail avec la carte, dirigez son container (Reka UI : to) vers l'élément de la carte, map.getContainer(), dans votre primitive.
  • globe bascule entre la carte plane et le globe. Le bouton montre ce que la carte affiche : il s'accorde avec la propriété projection, et le choix survit à un changement de mode, de style ou de thème.
  • terrain incline la carte au-dessus d'un relief 3D. Il n'apparaît que sur un style qui porte l'altitude, comme outdoor; l'étendue du MNA suit la publication active dans /coverage.json. Il reste actif d'un style à l'autre et revient au prochain style qui porte l'altitude. Le relief charge des tuiles d'altitude, et chacune est une requête, comme toute tuile.
  • lang="fr" nomme les boutons et rédige les messages en français. Les bascules portent aria-pressed, et chaque bouton a un nom et une bulle de survol.