TypeScript 87.7%
CSS 12.3%
1# Ka Maps — framework cartographique de Groupe Ka23**Auteur : Simon-Pierre Boucher — contact@spboucher.ai**45Ka Maps est le moteur géographique partagé des applications Groupe Ka.6Implémenté une fois ici, consommé par **Lou-Ka**, **Immo-Ka** et7**Vrai-Prix** — chaque app n'apporte que son **thème** et son8**adaptateur de données**.910```11Lou-Ka / Immo-Ka / Vrai-Prix (frontends)12 │ thème + adaptateur + configuration13 ▼14 Ka Maps ← ce dépôt (~/Desktop/ka-maps)15 │16 ▼17 Mapbox GL JS v3 — style Mapbox Standard (3D)18```1920## Empaquetage (décision d'architecture)2122Trois dépôts séparés → paquet local `@groupe-ka/ka-maps` :2324| App | Consommation | Pourquoi |25|---|---|---|26| Lou-Ka (`lou-ka/frontend`) | `file:../../ka-maps` (lien) + `resolve.dedupe` Vite | itération à chaud |27| Immo-Ka (`agent-courtage/frontend`) | idem | idem |28| Vrai-Prix (Next 16) | `file:../../ka-maps/groupe-ka-ka-maps-0.1.0.tgz` | Turbopack ne résout pas les liens hors racine |2930Après toute modification du framework :31`npm run build` (les apps Vite la voient immédiatement) puis32`npm run pack:tarball` + `npm install` dans vrai-prix.3334⚠️ Les apps Vite doivent déduper `react`, `react-dom`, `mapbox-gl`35(`resolve.dedupe`) et pointer `paths` tsconfig vers **leurs**36`@types/react` — sinon double React (crash hooks) et double moteur GL.3738## Architecture des sources3940```41src/42 types/ MapProperty, KaDataAdapter, BBox, KaMapState,43 GeographicMarketSummary, KaLensStats, HeatmapMetric…44 core/ KaMap (moteur), KaEventHub (événements centralisés)45 layers/ propertyLayer (pastilles + grappes), registry (couches par app)46 services/ BoundsQueryScheduler (debounce, AbortController, cache LRU,47 livraison monotone — jamais une réponse périmée)48 styles/ kaBaseStyle (Mapbox Standard + réglages immobiliers), ka-maps.css49 theming/ KaMapTheme (jetons par app), palettes50 utils/ format (prix fr-CA), geo (bbox, haversine, GeoJSON),51 url (caméra partageable), lens (Ka Lens)52 react/ KaMapView, useKaMap, SearchAreaControl, PropertyPreview,53 ResultCount, LoadingIndicator, LocateControl, Tilt3DControl,54 KaBrandBadge55```5657## Le contrat d'intégration (ajouter une app Groupe Ka)58591. **Adaptateur** — comment vos données deviennent des `MapProperty` :6061```ts62const monAdapter: KaDataAdapter = {63 id: "mon-app-items",64 appSource: "mon-app",65 async fetchInBounds({ bbox, zoom, filters, signal }) {66 const res = await fetch(`/api/…?bbox=${bboxToString(bbox)}`, { signal });67 return { properties: (await res.json()).map(toMapProperty), totalCount };68 },69};70```71722. **Thème** — `KaMapTheme` : accent, familles de pastilles73 (`sale`/`rent`/`valuation`/`highlight` × normal/sélection), grappe.743. **Montage** :7576```tsx77<KaMapView theme={theme} adapter={adapter} mapboxToken={TOKEN}78 filters={filters} pitch={50} cluster={{ maxZoom: 15 }}>79 <KaBrandBadge /> <Tilt3DControl /> <SearchAreaControl />80 <LoadingIndicator /> <ResultCount />81 <PropertyPreview render={(p) => <MaCarte p={p} />} />82</KaMapView>83```84854. **Jetons CSS** — sur `.ka-map` : `--ka-accent`, `--ka-surface`,86 `--ka-ink`, `--ka-line`, `--ka-radius`, `--ka-shadow`, `--ka-font`.8788## Rendu des propriétés8990- **Aucun marqueur DOM** : source GeoJSON + couches symbole/cercle GPU —91 tenue à 100 000+ points.92- **Pastilles de prix** : images canvas 9-slice étirables par famille ×93 état (le 9-slice n'est pas supporté sur les icônes SDF). `icon-image`94 est une propriété *layout* (feature-state interdit) : la sélection est95 réinjectée par `setLayoutProperty`.96- **Grappes** : clustering natif, compte + **valeur moyenne indicative**97 (`≈`) via `clusterProperties` (somme/nombre) — `valueClamp` borne la98 contribution de chaque point (un prix aberrant ne pollue pas la bulle).99 La médiane exacte n'est pas réductible par supercluster ; elle est100 disponible côté Ka Lens.101- **États** : `hovered`/`selected`/`dimmed` par feature-state (peinture).102103## Pièges connus du moteur (payés une fois, documentés ici)104105- **Polices** : les couches symbole doivent utiliser des polices du106 serveur de glyphes Mapbox (`DIN Pro …`, `Arial Unicode MS …`). Une107 police inconnue fait échouer le parsing des tuiles → **source vide,108 silencieuse**.109- **Style à imports (Standard)** : installer les couches sur `load`,110 config basemap ensuite ; auto-réparation (vérification + reconstruction111 avec id de source rotatif) intégrée à `KaMap`.112- **`clusterMaxZoom` entier** obligatoire (sinon `Invalid array length`113 dans le worker) — arrondi par `KaMap`.114- **`promoteId: undefined`** rejeté par la validation Mapbox.115- `getClusterExpansionZoom` est à **callback** (pas une promesse).116117## Fond de carte118119`KA_STYLE_URL` = `mapbox://styles/mapbox/standard` +120`applyKaBasemapConfig` : thème `faded` (les prix dominent), POI/transit121masqués, `lightPreset` day/dusk = Ka Light/Ka Dark (`setMode`, sans122rechargement de style). 3D native (bâtiments, repères) ; inclinaison par123défaut 50°, contrôle `Tilt3DControl` (2D/3D). Jeton public `pk.…` fourni124par l'app (`mapboxToken`) — jamais de secret serveur dans le navigateur.125Attribution Mapbox/OSM repliée en ⓘ (jamais retirée), logo conservé.126127## Synchronisation carte ↔ résultats128129`BoundsQueryScheduler` : `moveend` → debounce → adaptateur → rendu.130Mode `manual` : le viewport divergent affiche « Rechercher dans cette131zone » (tolérance 15 %) ; mode `auto` : requête à chaque déplacement132posé. Annulation `AbortController`, cache LRU (clé bbox+zoom+filtres),133livraison strictement monotone.134135## Couches, agrégats, Ka Lens (fondations)136137- `layers/registry.ts` : vocabulaire complet (PROPERTY/MARKET/LAND/138 LIFESTYLE/INVESTMENT) ; `buildLayerRegistry` marque ce que chaque app139 supporte — rien d'autre n'est exposé en production.140- `GeographicMarketSummary` : contrat des agrégats par géographie141 (province → quartier) pour Ka Market Pulse — les API restent à142 implémenter par app (aucune valeur fictive).143- `utils/lens.ts` : `computeLensStats` (médianes, mix de types, parts de144 baisses/90 j+) sur les propriétés réellement chargées ;145 `propertiesInBBox` / `propertiesInPolygon` (ray casting) pour la146 sélection Ka Lens ; `KaMap.setDimmedExcept` atténue le reste.147- `HeatmapMetric` : contrat de la future infra heatmap.148149## Évolution serveur prévue (architecturé, non déployé)150151```152SQLite (bbox + index (lat,lng)) ← aujourd'hui, les 3 apps153PostgreSQL + PostGIS (GIST, ST_Intersects) ← quand la volumétrie l'exige154Martin → tuiles vectorielles MVT → CDN ← /api/map/…/{z}/{x}/{y}.pbf155```156157Le client est prêt : remplacer la source GeoJSON par une source158`vector` + `source-layer` ne touche ni les couches ni les apps.159160## Commandes161162`npm run build` · `npm run typecheck` · `npm test` (30 tests : formats163fr-CA, bbox/URL, scheduler — debounce/annulation/cache/monotonie —, Ka164Lens) · `npm run pack:tarball`.165