Points of Interest
Cartographie collaborative des zones d’activité par contributions anonymes

Points of Interest est né comme une preuve de concept destinée à cartographier l’activité à proximité au moyen de contributions anonymes.
L’objectif n’était pas simplement de permettre aux utilisateurs de placer des marqueurs sur une carte. Il s’agissait surtout de transformer de nombreux petits signalements de localisation anonymes en une vue agrégée et en direct de l’activité locale.
Le projet étudie comment chacun peut signaler des points d’intérêt à proximité sans créer de compte, tandis que le système transforme ces contributions en cartes de chaleur, fils d’activité, statistiques sur les contributeurs et indicateurs de zones à forte activité.
Points of Interest est conçu comme une boucle de rétroaction géospatiale en temps réel. Il associe Symfony, React, TypeScript, Leaflet, OpenStreetMap, Mercure, Doctrine, PostgreSQL/PostGIS, participation anonyme, mises à jour en temps réel et conception d’interfaces géospatiales.
Le problème
Le problème central de Points of Interest n’était pas l’affichage d’une carte. Il consistait à rendre l’activité locale visible sans imposer aux utilisateurs un processus de signalement contraignant.
La plupart des applications cartographiques reposent sur des lieux fixes :
Pourtant, de nombreux signaux du monde réel sont temporaires.
Un lieu peut s’animer pendant une courte période. Une zone peut devenir fréquentée. Un emplacement peut devenir pertinent parce que plusieurs personnes le signalent au même moment.
Ce type d’information n’est pas statique. Il est collectif, récent et local.
Points of Interest étudie une question simple :
Peut-on transformer des signalements anonymes à proximité en une carte utile et en temps réel de l’activité locale ?
Cette question soulève plusieurs défis techniques :
How do users submit a signal quickly?
How do we avoid requiring authentication?
How do we prevent spam or abusive submissions?
How do we validate that a signal is actually near the user?
How do we aggregate raw points into useful hotspot data?
How do we update every connected client in real time?
How do we keep the interface understandable on top of a dense map?Une simple carte de marqueurs ne suffirait pas.
Le système devait collecter, valider, stocker et agréger les signalements, diffuser les mises à jour et les restituer dans une interface géospatiale claire.
Points of Interest est ainsi passé d’une simple démonstration cartographique à un système full-stack de cartographie en temps réel.
Orientation du produit
Points of Interest est parti d’une idée simple :
Permettre à chacun de signaler anonymement une activité à proximité et d’en visualiser le résultat sur une carte de chaleur en direct.
Cette idée a rapidement pris de l’ampleur, car le produit devait trouver un équilibre entre participation, respect de la vie privée et qualité des signalements.
Pour être utile, le système devait :
-
détecter ou demander la position actuelle de l’utilisateur ;
-
permettre à l’utilisateur de signaler depuis la carte un point situé à proximité ;
-
vérifier que le point signalé est suffisamment proche de l’utilisateur ;
-
identifier les contributeurs sans compte ;
-
limiter la fréquence des contributions par client anonyme ;
-
stocker les signalements bruts avec leur horodatage ;
-
agréger les signalements récents dans des cellules de densité ;
-
exposer des instantanés au moyen d’une API ;
-
diffuser les mises à jour en direct aux clients connectés ;
-
afficher les zones à forte activité, l’activité récente et les statistiques sur les contributeurs ;
-
conserver une interface utilisable sans authentification.
L’orientation du produit était volontairement légère.
L’utilisateur ne devrait avoir ni à créer un compte, ni à remplir un long formulaire, ni à comprendre un modèle de signalement complexe. Il lui suffit d’ouvrir l’application, d’autoriser l’accès à sa position, de toucher la carte, de confirmer le signalement et de voir immédiatement la carte partagée se mettre à jour.
Architecture du système
Points of Interest s’organise autour de cinq couches principales :
Chaque couche a une responsabilité claire.
La couche client prend en charge l’interaction et la visualisation. La couche API reçoit et expose les données des signalements. La couche de validation protège la qualité des contributions. La couche de persistance stocke les signalements. Enfin, la couche temps réel maintient tous les clients synchronisés.
Le système suit ce flux :
Cette structure maintient le projet compréhensible tout en couvrant les aspects importants d’une application collaborative en temps réel.
Couche API backend
Le backend est une API Symfony chargée de recevoir les signalements, de renvoyer les instantanés et de coordonner les mises à jour en direct.
L’API expose une ressource centrale :
/api/signalsElle prend en charge deux opérations principales :
GET /api/signals -> return a recent signal snapshot
POST /api/signals -> submit a new signalLe contrôleur reste volontairement léger. Il délègue la logique métier à des services dédiés :
Ce choix de conception est pertinent.
Le contrôleur ne devrait pas savoir comment hacher l’identité d’un utilisateur, valider une distance géospatiale, agréger des cellules de densité ou publier des mises à jour en temps réel. Son seul rôle consiste à traduire les requêtes HTTP en actions applicatives.
Point d’accès aux instantanés
Le point d’accès GET /api/signals renvoie un instantané de l’activité récente.
L’instantané comprend :
clientKey
points
density
latestByUser
totals
updatedAtLe frontend reçoit ainsi tout ce dont il a besoin pour afficher la carte, la couche de chaleur, le nombre de contributeurs, l’activité récente et l’état propre à l’utilisateur.
Le modèle d’instantané est utile, car le frontend n’a pas à reconstruire tout le domaine à partir des lignes brutes de la base de données. Il reçoit une vue préparée de l’état actuel de l’activité.
Point d’accès aux contributions
Le point d’accès POST /api/signals accepte une charge utile de signalement contenant :
signalLocation
userLocationCette distinction est importante.
La position actuelle de l’utilisateur sert à valider la proximité. La position du signalement correspond au point qu’il souhaite signaler. Ces deux coordonnées peuvent être identiques, mais ce n’est pas toujours le cas.
Par exemple, un utilisateur peut se trouver près d’un lieu et signaler sur la carte un point légèrement en avant de sa position.
Avant de le stocker, le backend vérifie que le point signalé se trouve dans la distance autorisée.
Modèle de domaine
L’entité Signal se trouve au centre du backend.
Un signalement représente une contribution anonyme envoyée par un utilisateur.
L’entité stocke :
id
userKey
userLocation
signalLocation
createdAtCe modèle est réduit, mais il recueille les informations essentielles.
Le système n’a besoin ni de compte utilisateur, ni de profil, ni de mot de passe, ni de table de session, ni d’identité sociale. Il lui faut uniquement une clé de contributeur anonyme, le point signalé, la position approximative de l’utilisateur au moment de la contribution et l’horodatage.
Le modèle de données reste ainsi léger et ciblé.
Couche de participation anonyme
L’une des décisions produit les plus importantes a été de ne pas imposer d’authentification.
Les utilisateurs peuvent participer sans créer de compte.
Au lieu de stocker une identité, le backend dérive une clé client anonyme de l’adresse IP de la requête.
L’application dispose ainsi d’un identifiant suffisant pour prendre en charge :
rate limiting
latest signal by contributor
contributor statistics
user-specific latest signalsans obliger les utilisateurs à s’inscrire.
Pour une preuve de concept, il s’agit d’un compromis pragmatique en matière de confidentialité.
Le système ne prétend pas garantir un anonymat parfait. Les identifiants dérivés d’adresses IP peuvent toujours avoir des implications pour la vie privée, notamment si les journaux ou les adresses brutes sont conservés. Pour ce prototype, cette conception maintient toutefois une expérience fluide tout en évitant une identité directement liée à un compte.
Couche de validation
Les systèmes participatifs perdent toute utilité si les contributions sont trop faciles à détourner.
Points of Interest répond à ce risque avec deux protections côté backend :
rate limiting
proximity validationLimitation de la fréquence
Chaque clé client anonyme passe par le limiteur de fréquence de Symfony avant l’acceptation d’un signalement.
L’objectif est simple :
Une seule personne ne doit pas pouvoir inonder la carte de fausses activités.
Le limiteur protège le système contre les contributions répétées tout en conservant un modèle d’interaction simple pour les utilisateurs ordinaires.
Validation de la proximité
Le backend vérifie également que le signalement se trouve suffisamment près de la position réelle de l’utilisateur.
C’est important, car la carte doit représenter la réalité à proximité, et non des signalements distants arbitraires.
Le processus de validation se présente ainsi :
La distance est calculée au moyen d’une fonction de type haversine.
C’est précisément le type de validation dont ce produit a besoin. Elle est simple, explicable et suffisamment fiable pour vérifier qu’un point signalé se trouve raisonnablement près du contributeur.
Couche d’instantané et d’agrégation
Les signalements bruts sont utiles pour le stockage, mais ne suffisent pas à la visualisation.
Le frontend a besoin d’une représentation de plus haut niveau :
Where are the points?
Where is activity dense?
Who submitted recently?
How many contributors are active?
When was the map last updated?Le générateur d’instantanés transforme les signalements récents en une réponse compacte.
L’agrégation de densité regroupe les signalements selon des coordonnées arrondies et incrémente un compteur d’intensité pour chaque cellule.
Elle produit ainsi des cellules prêtes pour la carte de chaleur :
lat
lng
intensityCette conception convient bien au prototype, car le frontend peut afficher la densité sans effectuer lui-même de coûteuses opérations de regroupement.
Le backend assure l’agrégation. Le frontend assure la visualisation.
Couche temps réel
Points of Interest utilise Mercure pour diffuser les mises à jour des signalements en direct.
Lorsqu’un signalement est stocké, le backend envoie un message. Un gestionnaire reconstruit l’instantané récent et le publie dans un sujet Mercure.
Les clients connectés s’abonnent au moyen de Server-Sent Events.
Ce mécanisme donne à l’application son caractère collaboratif.
Lorsqu’un utilisateur envoie un signalement, les autres utilisateurs connectés voient la carte de chaleur se mettre à jour sans actualiser la page.
C’est ce qui distingue une carte statique d’une carte participative en direct.
Couche cartographique frontend
Le frontend est une application React et Vite construite autour d’une carte Leaflet.
Son rôle consiste à transformer l’instantané du backend en une interface géospatiale interactive.
Les principaux éléments de l’interface sont :
map viewport
heatmap layer
user location marker
confirmation dialog
header overlay
sidebar panels
recent activity feed
hotspot list
status and error feedbackLe flux du frontend se présente ainsi :
Le frontend sépare les parties complexes dans différents hooks.
C’est un choix architectural solide, car le rendu de la carte, les données en direct, les statistiques dérivées et l’état de l’interface répondent à des préoccupations distinctes.
Carte de chaleur Leaflet
La couche cartographique utilise Leaflet et leaflet.heat pour représenter les cellules de densité sous forme de carte de chaleur.
La carte de chaleur reçoit les cellules de densité produites par le backend et normalise leurs valeurs d’intensité avant l’affichage.
Le client prend également en charge plusieurs fournisseurs de tuiles cartographiques, notamment OpenStreetMap et les tuiles de type Mapbox.
Le résultat est plus utile qu’un simple affichage de marqueurs individuels.
Les marqueurs montrent les événements. Les cartes de chaleur révèlent les tendances.
Cette distinction est au cœur du projet.
Conception des interactions
L’interaction utilisateur est volontairement réduite :
L’application ne demande ni nom, ni adresse e-mail, ni mot de passe, ni profil, ni catégorie.
C’est le bon choix pour ce prototype.
L’expérience centrale porte sur une participation sans friction. Chaque champ supplémentaire alourdirait le système et détournerait l’attention de l’idée principale.
Confirmation avant l’envoi
Un clic sur la carte n’envoie pas immédiatement un signalement.
Il ouvre d’abord une boîte de dialogue de confirmation.
Cette courte interaction est importante, car un clic sur la carte peut être accidentel. La confirmation permet à l’utilisateur de vérifier la position avant de contribuer à la carte de chaleur partagée.
Filtrage selon un rayon local
Le frontend se concentre sur l’activité à proximité.
Il filtre les cellules de densité visibles, les points et les derniers signalements des utilisateurs autour de la position actuelle de l’utilisateur.
L’expérience reste ainsi locale, au lieu de transformer la carte en un amas mondial de points sans rapport entre eux.
Pour ce prototype, la proximité constitue le produit.
Flux de données
Le flux de données complet de l’application se présente ainsi :
C’est la principale valeur d’ingénierie du projet.
Il transforme des signalements individuels en une vue spatiale partagée et en direct.
Contrat de l’API
Le contrat de l’API est volontairement compact.
Une réponse d’instantané contient :
interface Snapshot {
clientKey?: string;
points: Signal[];
density: SignalDensityCell[];
latestByUser: Signal[];
totals: {
points: number;
contributors: number;
};
updatedAt: string;
}Un signalement contient :
interface Signal {
id: string;
signalLocation: Point;
createdAt: string;
userKey: string;
}Une cellule de densité contient :
interface SignalDensityCell {
lat: number;
lng: number;
intensity: number;
}Cette structure est claire, car elle correspond directement à l’interface.
Le frontend n’a pas besoin de comprendre les lignes de la base de données. Il reçoit exactement les données nécessaires pour :
map points
heatmap cells
contributor count
recent activity
latest signal by user
last update timeCouche d’infrastructure
L’infrastructure locale utilise Docker Compose pour les services de soutien.
Le backend s’appuie sur :
Symfony 7
PHP 8.4
Doctrine ORM
PostgreSQL / PostGIS
Mercure
AdminerLe frontend s’appuie sur :
React
TypeScript
Vite
Leaflet
leaflet.heat
Zustand
i18next
Sonner
Tailwind-style UI componentsL’infrastructure peut être résumée ainsi :
Cette stack convient bien au projet.
Symfony prend en charge la structure du backend, la validation, la messagerie et la persistance. React gère l’interface interactive. Leaflet assure l’interface géospatiale. Mercure gère la diffusion en temps réel. PostgreSQL/PostGIS fournit au backend une base de stockage adaptée aux données géospatiales.
Qualité et intégration continue
Le dépôt comprend des contrôles qualité pour le backend comme pour le frontend.
Le processus qualité du backend exécute :
PHPStan
ECS coding standards
PHPUnitLe processus qualité du frontend exécute :
ESLint
Prettier
TypeScript type checkingC’est important, car il ne s’agit pas d’une simple démonstration cartographique éphémère.
Le projet est suffisamment structuré pour être maintenu, testé et étendu.
La décision importante consiste à séparer les contrôles du backend et du frontend.
Les modifications du serveur déclenchent les processus PHP. Celles du client déclenchent les processus Node. L’intégration continue reste ainsi ciblée et moins coûteuse à exécuter.
Principales décisions d’ingénierie
Construire le produit autour d’instantanés en direct
Le projet ne diffuse pas au frontend les événements individuels de bas niveau issus de la base de données.
Il diffuse plutôt des instantanés actualisés.
C’est un bon choix pour ce prototype, car l’interface a besoin de l’état actuel de la carte, et non d’un journal d’événements complexe.
Un instantané fournit au frontend une réalité simple :
Here are the current points.
Here is the current density.
Here are the current contributors.
Here is the latest update time.Préserver l’anonymat des utilisateurs
L’application évite entièrement l’authentification.
Cela réduit les frictions et facilite l’utilisation du prototype.
La clé client anonyme fournit néanmoins au système une structure suffisante pour limiter la fréquence des contributions et calculer les statistiques des contributeurs, sans introduire de gestion des comptes.
C’est le bon compromis pour une expérimentation participative autour de signalements locaux.
Valider la proximité côté backend
Le frontend peut guider l’utilisateur, mais le backend doit faire respecter la règle.
Points of Interest vérifie sur le serveur la distance entre la position de l’utilisateur et celle du signalement.
Le client ne devient donc pas la source de vérité.
Il ne faut jamais faire confiance au navigateur pour ce type de validation. Ce serait une erreur.
Séparer l’envoi de la visualisation
L’envoi et la visualisation suivent des flux distincts.
Un utilisateur envoie un signalement. Le système reconstruit l’instantané. Chaque client reçoit la vue mise à jour.
Le modèle mental reste ainsi simple :
Utiliser des cartes de chaleur, pas seulement des marqueurs
Les marqueurs individuels sont utiles, mais représentent mal la densité.
Les cartes de chaleur révèlent les tendances.
Dans un système participatif de zones à forte activité, la densité constitue le produit. La carte de chaleur n’est pas décorative : elle est l’interface principale.
Utiliser des hooks frontend dédiés
Le code React sépare les responsabilités au moyen de hooks :
useUserLocation
useHotspotFeed
useLeafletHeatmap
useFeedDerivations
useAppStoreL’application reste ainsi plus facile à comprendre.
Le cycle de vie de la carte, les abonnements Mercure, les listes dérivées de zones à forte activité, la géolocalisation et l’état de l’interface ne devraient pas tous résider dans un unique composant volumineux.
Utiliser Mercure pour les mises à jour en temps réel
Mercure convient bien à ce cas d’usage, car le frontend n’a besoin que de mises à jour du serveur vers le client.
Le client n’a pas besoin d’un protocole WebSocket bidirectionnel complet. Les Server-Sent Events suffisent.
La couche temps réel s’en trouve simplifiée.
Maintenir un contrat d’API adapté à l’interface
L’API renvoie directement les cellules de densité, les derniers signalements par utilisateur, les totaux et les horodatages de mise à jour.
Le rendu côté frontend reste ainsi simple.
Le backend prépare les données. Le frontend les présente.
Ce que j’ai appris
La principale leçon de Points of Interest est que la cartographie en temps réel ne repose pas d’abord sur les cartes.
Elle repose sur la confiance, la latence, l’agrégation et la conception des interactions.
La carte n’est que la surface. En dessous, le système doit déterminer quels signalements sont valides, à quelle fréquence un utilisateur peut contribuer, comment les points deviennent une densité, comment les mises à jour atteignent les clients et comment l’interface évite de surcharger l’utilisateur.
J’ai également appris que l’anonymat modifie l’architecture.
Sans comptes, le système doit tout de même distinguer les contributeurs, appliquer des limites et afficher « mon dernier signalement ». La clé client anonyme répond suffisamment à ces besoins pour un prototype, tout en maintenant une participation légère.
Le projet a aussi montré que les points bruts ne constituent pas le produit final.
Une liste de coordonnées n’est qu’un ensemble de données. Une carte de chaleur en fournit une interprétation. Les cellules de densité, le nombre de contributeurs, l’activité récente et les panneaux de zones à risque transforment les contributions brutes en informations rapidement compréhensibles.
Enfin, le projet a confirmé un principe full-stack simple :
Le backend doit protéger les données. Le frontend doit les rendre vivantes.
Points of Interest remplit ces deux rôles.
Conclusion
Points of Interest est une preuve de concept full-stack pour la cartographie collaborative des zones d’activité au moyen de contributions anonymes.
Sa principale valeur ne réside pas dans la possibilité de cliquer sur une carte, mais dans la boucle complète construite autour de cette action : identification anonyme, validation de la proximité, limitation de fréquence, persistance des signalements, agrégation des instantanés, diffusion en temps réel et visualisation sous forme de carte de chaleur.
Le système transforme de petits signalements individuels en une vue partagée et en direct de l’activité locale.
Voici l’idée centrale de Points of Interest :
Rendre visible l’activité collective à proximité, sans comptes, sans friction et sans processus de signalement contraignant.