// ----------------------------------------------------------------------------- // Lou-Ka — Agrégateur de logements à louer (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // fiche/MapInner.tsx : partie Mapbox de la carte de la fiche (chargée // paresseusement) — KaSpotlightMap (Ka Maps) + lignes de métro + couche des // lieux filtrés (cercles colorés par catégorie + étiquettes) + caméra qui // englobe les lieux affichés. // ----------------------------------------------------------------------------- import { useEffect, useMemo } from "react"; import "mapbox-gl/dist/mapbox-gl.css"; import "@groupe-ka/ka-maps/styles.css"; import type { MapProperty } from "@groupe-ka/ka-maps"; import { KaBrandBadge, KaSpotlightMap, useKaMap } from "@groupe-ka/ka-maps/react"; import type mapboxgl from "mapbox-gl"; import type { Listing } from "../api"; import MetroLignes from "../components/MetroLignes"; import { louKaMapTheme } from "../kamaps/theme"; import { MAPBOX_TOKEN } from "../kamaps/config"; import type { Categorie, Lieu } from "./InteractiveMap"; const SRC = "lk-lieux"; const ORANGE = "#ff6a00"; function LieuxLayer({ lieux, categories, center }: { lieux: Lieu[]; categories: Categorie[]; center: [number, number] }) { const ka = useKaMap(); const couleurs = useMemo(() => { const m: unknown[] = ["match", ["get", "cat"]]; for (const c of categories) m.push(c.key, c.color); m.push("#666"); return m; }, [categories]); useEffect(() => { if (!ka) return; const map = (ka as unknown as { map: mapboxgl.Map }).map; if (!map) return; const data = { type: "FeatureCollection" as const, features: lieux.map((x) => ({ type: "Feature" as const, properties: { cat: x.cat, name: x.name }, geometry: { type: "Point" as const, coordinates: [x.lng, x.lat] }, })), }; const ensure = () => { const src = map.getSource(SRC) as mapboxgl.GeoJSONSource | undefined; if (src) { src.setData(data); return; } map.addSource(SRC, { type: "geojson", data }); map.addLayer({ id: `${SRC}-halo`, type: "circle", source: SRC, paint: { "circle-radius": 9, "circle-color": "#ffffff", "circle-opacity": 0.95 }, }); map.addLayer({ id: `${SRC}-dot`, type: "circle", source: SRC, paint: { "circle-radius": 6, "circle-color": couleurs as mapboxgl.ExpressionSpecification }, }); map.addLayer({ id: `${SRC}-lbl`, type: "symbol", source: SRC, layout: { "text-field": ["get", "name"], "text-size": 11, "text-offset": [0, 1.1], "text-anchor": "top", "text-font": ["DIN Pro Medium", "Arial Unicode MS Regular"], "text-optional": true, }, paint: { "text-color": "#141814", "text-halo-color": "#ffffff", "text-halo-width": 1.4 }, }); }; const apply = () => { try { ensure(); } catch { /* style pas prêt */ } }; if (map.isStyleLoaded()) apply(); map.on("style.load", apply); map.on("load", apply); // caméra : englober les lieux + l'immeuble ; sans lieu → retour sur l'immeuble if (lieux.length > 0) { let w = center[0], e = center[0], s = center[1], n = center[1]; const proches = lieux.filter((x) => x.dist_m <= 2000); for (const x of proches.length ? proches : lieux) { w = Math.min(w, x.lng); e = Math.max(e, x.lng); s = Math.min(s, x.lat); n = Math.max(n, x.lat); } map.fitBounds([[w, s], [e, n]], { padding: { top: 60, bottom: 40, left: 40, right: 40 }, pitch: 30, maxZoom: 16, duration: 700 }); } else { map.easeTo({ center, zoom: 17, pitch: 62, duration: 700 }); } return () => { map.off("style.load", apply); map.off("load", apply); }; }, [ka, lieux, couleurs, center]); useEffect(() => () => { if (!ka) return; const map = (ka as unknown as { map: mapboxgl.Map }).map; try { for (const id of [`${SRC}-lbl`, `${SRC}-dot`, `${SRC}-halo`]) if (map.getLayer(id)) map.removeLayer(id); if (map.getSource(SRC)) map.removeSource(SRC); } catch { /* carte détruite */ } }, [ka]); return null; } export default function MapInner({ l, lieux, categories }: { l: Listing; lieux: Lieu[]; categories: Categorie[] }) { const property = useMemo(() => ({ id: l.uid, appSource: "lou-ka", latitude: l.lat as number, longitude: l.lng as number, kind: "listing", listingType: "rent", price: l.price ?? undefined, propertyType: l.unit_type || undefined, address: l.address || l.title || undefined, city: l.city || undefined, thumbnailUrl: l.images?.[0], }), [l.uid, l.lat, l.lng, l.price, l.unit_type, l.address, l.title, l.city, l.images]); const center = useMemo<[number, number]>(() => [l.lng as number, l.lat as number], [l.lat, l.lng]); return ( ); }