Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 98.9%
Python 0.6%
1// -----------------------------------------------------------------------------2// Lou-Ka — Agrégateur de logements à louer (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// fiche/MapInner.tsx : partie Mapbox de la carte de la fiche (chargée5// paresseusement) — KaSpotlightMap (Ka Maps) + lignes de métro + couche des6// lieux filtrés (cercles colorés par catégorie + étiquettes) + caméra qui7// englobe les lieux affichés.8// -----------------------------------------------------------------------------9import { useEffect, useMemo } from "react";10import "mapbox-gl/dist/mapbox-gl.css";11import "@groupe-ka/ka-maps/styles.css";12import type { MapProperty } from "@groupe-ka/ka-maps";13import { KaBrandBadge, KaSpotlightMap, useKaMap } from "@groupe-ka/ka-maps/react";14import type mapboxgl from "mapbox-gl";15import type { Listing } from "../api";16import MetroLignes from "../components/MetroLignes";17import { louKaMapTheme } from "../kamaps/theme";18import { MAPBOX_TOKEN } from "../kamaps/config";19import type { Categorie, Lieu } from "./InteractiveMap";2021const SRC = "lk-lieux";22const ORANGE = "#ff6a00";2324function LieuxLayer({ lieux, categories, center }: { lieux: Lieu[]; categories: Categorie[]; center: [number, number] }) {25 const ka = useKaMap();26 const couleurs = useMemo(() => {27 const m: unknown[] = ["match", ["get", "cat"]];28 for (const c of categories) m.push(c.key, c.color);29 m.push("#666");30 return m;31 }, [categories]);3233 useEffect(() => {34 if (!ka) return;35 const map = (ka as unknown as { map: mapboxgl.Map }).map;36 if (!map) return;37 const data = {38 type: "FeatureCollection" as const,39 features: lieux.map((x) => ({40 type: "Feature" as const, properties: { cat: x.cat, name: x.name },41 geometry: { type: "Point" as const, coordinates: [x.lng, x.lat] },42 })),43 };44 const ensure = () => {45 const src = map.getSource(SRC) as mapboxgl.GeoJSONSource | undefined;46 if (src) { src.setData(data); return; }47 map.addSource(SRC, { type: "geojson", data });48 map.addLayer({49 id: `${SRC}-halo`, type: "circle", source: SRC,50 paint: { "circle-radius": 9, "circle-color": "#ffffff", "circle-opacity": 0.95 },51 });52 map.addLayer({53 id: `${SRC}-dot`, type: "circle", source: SRC,54 paint: { "circle-radius": 6, "circle-color": couleurs as mapboxgl.ExpressionSpecification },55 });56 map.addLayer({57 id: `${SRC}-lbl`, type: "symbol", source: SRC,58 layout: {59 "text-field": ["get", "name"], "text-size": 11, "text-offset": [0, 1.1], "text-anchor": "top",60 "text-font": ["DIN Pro Medium", "Arial Unicode MS Regular"], "text-optional": true,61 },62 paint: { "text-color": "#141814", "text-halo-color": "#ffffff", "text-halo-width": 1.4 },63 });64 };65 const apply = () => { try { ensure(); } catch { /* style pas prêt */ } };66 if (map.isStyleLoaded()) apply();67 map.on("style.load", apply);68 map.on("load", apply);69 // caméra : englober les lieux + l'immeuble ; sans lieu → retour sur l'immeuble70 if (lieux.length > 0) {71 let w = center[0], e = center[0], s = center[1], n = center[1];72 const proches = lieux.filter((x) => x.dist_m <= 2000);73 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); }74 map.fitBounds([[w, s], [e, n]], { padding: { top: 60, bottom: 40, left: 40, right: 40 }, pitch: 30, maxZoom: 16, duration: 700 });75 } else {76 map.easeTo({ center, zoom: 17, pitch: 62, duration: 700 });77 }78 return () => { map.off("style.load", apply); map.off("load", apply); };79 }, [ka, lieux, couleurs, center]);8081 useEffect(() => () => {82 if (!ka) return;83 const map = (ka as unknown as { map: mapboxgl.Map }).map;84 try {85 for (const id of [`${SRC}-lbl`, `${SRC}-dot`, `${SRC}-halo`]) if (map.getLayer(id)) map.removeLayer(id);86 if (map.getSource(SRC)) map.removeSource(SRC);87 } catch { /* carte détruite */ }88 }, [ka]);89 return null;90}9192export default function MapInner({ l, lieux, categories }: { l: Listing; lieux: Lieu[]; categories: Categorie[] }) {93 const property = useMemo<MapProperty>(() => ({94 id: l.uid, appSource: "lou-ka", latitude: l.lat as number, longitude: l.lng as number,95 kind: "listing", listingType: "rent", price: l.price ?? undefined,96 propertyType: l.unit_type || undefined, address: l.address || l.title || undefined,97 city: l.city || undefined, thumbnailUrl: l.images?.[0],98 }), [l.uid, l.lat, l.lng, l.price, l.unit_type, l.address, l.title, l.city, l.images]);99 const center = useMemo<[number, number]>(() => [l.lng as number, l.lat as number], [l.lat, l.lng]);100101 return (102 <KaSpotlightMap theme={louKaMapTheme} mapboxToken={MAPBOX_TOKEN} property={property} buildingColor={ORANGE}>103 <KaBrandBadge />104 <MetroLignes />105 <LieuxLayer lieux={lieux} categories={categories} center={center} />106 </KaSpotlightMap>107 );108}109