|
1 |
+# Ka Maps — framework cartographique de Groupe Ka |
|
2 |
+ |
|
3 |
+**Auteur : Simon-Pierre Boucher — contact@spboucher.ai** |
|
4 |
+ |
|
5 |
+Ka Maps est le moteur géographique partagé des applications Groupe Ka. |
|
6 |
+Implémenté une fois ici, consommé par **Lou-Ka**, **Immo-Ka** et |
|
7 |
+**Vrai-Prix** — chaque app n'apporte que son **thème** et son |
|
8 |
+**adaptateur de données**. |
|
9 |
+ |
|
10 |
+``` |
|
11 |
+Lou-Ka / Immo-Ka / Vrai-Prix (frontends) |
|
12 |
+ │ thème + adaptateur + configuration |
|
13 |
+ ▼ |
|
14 |
+ Ka Maps ← ce dépôt (~/Desktop/ka-maps) |
|
15 |
+ │ |
|
16 |
+ ▼ |
|
17 |
+ Mapbox GL JS v3 — style Mapbox Standard (3D) |
|
18 |
+``` |
|
19 |
+ |
|
20 |
+## Empaquetage (décision d'architecture) |
|
21 |
+ |
|
22 |
+Trois dépôts séparés → paquet local `@groupe-ka/ka-maps` : |
|
23 |
+ |
|
24 |
+| 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 | |
|
29 |
+ |
|
30 |
+Après toute modification du framework : |
|
31 |
+`npm run build` (les apps Vite la voient immédiatement) puis |
|
32 |
+`npm run pack:tarball` + `npm install` dans vrai-prix. |
|
33 |
+ |
|
34 |
+⚠️ 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. |
|
37 |
+ |
|
38 |
+## Architecture des sources |
|
39 |
+ |
|
40 |
+``` |
|
41 |
+src/ |
|
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.css |
|
49 |
+ theming/ KaMapTheme (jetons par app), palettes |
|
50 |
+ 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 |
+ KaBrandBadge |
|
55 |
+``` |
|
56 |
+ |
|
57 |
+## Le contrat d'intégration (ajouter une app Groupe Ka) |
|
58 |
+ |
|
59 |
+1. **Adaptateur** — comment vos données deviennent des `MapProperty` : |
|
60 |
+ |
|
61 |
+```ts |
|
62 |
+const 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 |
+``` |
|
71 |
+ |
|
72 |
+2. **Thème** — `KaMapTheme` : accent, familles de pastilles |
|
73 |
+ (`sale`/`rent`/`valuation`/`highlight` × normal/sélection), grappe. |
|
74 |
+3. **Montage** : |
|
75 |
+ |
|
76 |
+```tsx |
|
77 |
+<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 |
+``` |
|
84 |
+ |
|
85 |
+4. **Jetons CSS** — sur `.ka-map` : `--ka-accent`, `--ka-surface`, |
|
86 |
+ `--ka-ink`, `--ka-line`, `--ka-radius`, `--ka-shadow`, `--ka-font`. |
|
87 |
+ |
|
88 |
+## Rendu des propriétés |
|
89 |
+ |
|
90 |
+- **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 est |
|
95 |
+ réinjectée par `setLayoutProperty`. |
|
96 |
+- **Grappes** : clustering natif, compte + **valeur moyenne indicative** |
|
97 |
+ (`≈`) via `clusterProperties` (somme/nombre) — `valueClamp` borne la |
|
98 |
+ 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 est |
|
100 |
+ disponible côté Ka Lens. |
|
101 |
+- **États** : `hovered`/`selected`/`dimmed` par feature-state (peinture). |
|
102 |
+ |
|
103 |
+## Pièges connus du moteur (payés une fois, documentés ici) |
|
104 |
+ |
|
105 |
+- **Polices** : les couches symbole doivent utiliser des polices du |
|
106 |
+ serveur de glyphes Mapbox (`DIN Pro …`, `Arial Unicode MS …`). Une |
|
107 |
+ 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 + reconstruction |
|
111 |
+ 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). |
|
116 |
+ |
|
117 |
+## Fond de carte |
|
118 |
+ |
|
119 |
+`KA_STYLE_URL` = `mapbox://styles/mapbox/standard` + |
|
120 |
+`applyKaBasemapConfig` : thème `faded` (les prix dominent), POI/transit |
|
121 |
+masqués, `lightPreset` day/dusk = Ka Light/Ka Dark (`setMode`, sans |
|
122 |
+rechargement de style). 3D native (bâtiments, repères) ; inclinaison par |
|
123 |
+défaut 50°, contrôle `Tilt3DControl` (2D/3D). Jeton public `pk.…` fourni |
|
124 |
+par l'app (`mapboxToken`) — jamais de secret serveur dans le navigateur. |
|
125 |
+Attribution Mapbox/OSM repliée en ⓘ (jamais retirée), logo conservé. |
|
126 |
+ |
|
127 |
+## Synchronisation carte ↔ résultats |
|
128 |
+ |
|
129 |
+`BoundsQueryScheduler` : `moveend` → debounce → adaptateur → rendu. |
|
130 |
+Mode `manual` : le viewport divergent affiche « Rechercher dans cette |
|
131 |
+zone » (tolérance 15 %) ; mode `auto` : requête à chaque déplacement |
|
132 |
+posé. Annulation `AbortController`, cache LRU (clé bbox+zoom+filtres), |
|
133 |
+livraison strictement monotone. |
|
134 |
+ |
|
135 |
+## Couches, agrégats, Ka Lens (fondations) |
|
136 |
+ |
|
137 |
+- `layers/registry.ts` : vocabulaire complet (PROPERTY/MARKET/LAND/ |
|
138 |
+ LIFESTYLE/INVESTMENT) ; `buildLayerRegistry` marque ce que chaque app |
|
139 |
+ supporte — rien d'autre n'est exposé en production. |
|
140 |
+- `GeographicMarketSummary` : contrat des agrégats par géographie |
|
141 |
+ (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 de |
|
144 |
+ baisses/90 j+) sur les propriétés réellement chargées ; |
|
145 |
+ `propertiesInBBox` / `propertiesInPolygon` (ray casting) pour la |
|
146 |
+ sélection Ka Lens ; `KaMap.setDimmedExcept` atténue le reste. |
|
147 |
+- `HeatmapMetric` : contrat de la future infra heatmap. |
|
148 |
+ |
|
149 |
+## Évolution serveur prévue (architecturé, non déployé) |
|
150 |
+ |
|
151 |
+``` |
|
152 |
+SQLite (bbox + index (lat,lng)) ← aujourd'hui, les 3 apps |
|
153 |
+PostgreSQL + PostGIS (GIST, ST_Intersects) ← quand la volumétrie l'exige |
|
154 |
+Martin → tuiles vectorielles MVT → CDN ← /api/map/…/{z}/{x}/{y}.pbf |
|
155 |
+``` |
|
156 |
+ |
|
157 |
+Le client est prêt : remplacer la source GeoJSON par une source |
|
158 |
+`vector` + `source-layer` ne touche ni les couches ni les apps. |
|
159 |
+ |
|
160 |
+## Commandes |
|
161 |
+ |
|
162 |
+`npm run build` · `npm run typecheck` · `npm test` (30 tests : formats |
|
163 |
+fr-CA, bbox/URL, scheduler — debounce/annulation/cache/monotonie —, Ka |
|
164 |
+Lens) · `npm run pack:tarball`. |