Aller au contenu
  1. Posts/

Retrouver son jour de collecte des déchets par adresse : un pipeline SIG interne (FME, PostGIS, WFS)

Fabien ALLAMANCHE
Auteur
Fabien ALLAMANCHE
Géomaticien @ Vienne Condrieu Agglomération
Sommaire

Introduction
#

Une réorganisation de la collecte des déchets, ça n’a l’air de rien. Et pourtant, dès qu’on baisse la fréquence de collecte sur certains secteurs sans que ce soit homogène à l’échelle d’une commune, il faut être capable de dire à chaque habitant, précisément, quel jour on ramasse chez lui.

Le « jour de collecte », ce n’est d’ailleurs pas une information unique par adresse : chaque flux — ordures ménagères, emballages/papiers, verre, textiles — a son propre jour (ou ses jours) de passage, son propre mode de collecte (porte-à-porte, points d’apports volontaires…) et ses propres consignes de tri. Une adresse peut donc avoir jusqu’à quatre réponses différentes à la question « quand passe-t-on chez moi ? ».

C’est le point de départ de ce billet : comment un besoin de communication assez simple en apparence — « dites-moi mon jour de collecte » — s’est transformé en un petit pipeline SIG (FME, PostgreSQL/PostGIS, flux WFS) construit en interne, du modèle de données jusqu’à la diffusion.

Ce billet n’est pas un tutoriel pas-à-pas. C’est un retour d’expérience sur l’émergence d’une solution, la construction du modèle de données, et ce que l’agilité d’une stack SIG interne permet quand le besoin réel se précise en cours de route.

Table des matières
#

La genèse du besoin
#

Le déclencheur, c’est une réorganisation de la collecte des déchets portée par le service Environnement, avec une baisse de fréquence sur certains secteurs. Le problème, c’est que cette fréquence varie à l’intérieur d’une même commune : le centre d’une commune peut être collecté chaque semaine, un quartier périphérique une fois tous les 15 jours. Une simple affiche « à partir du <date>, la collecte passe à <fréquence> » ne suffit pas — il faut pouvoir répondre « et chez moi, précisément ? ».

Le service Environnement s’était inspiré du gabarit « Grand Public » de GEO, la plateforme SIG de l’éditeur Business Geografic, qui fait tourner le SIG web de Vienne Condrieu Agglomération — sur le modèle d’une cartographie similaire déjà mise en place par Loire Forez Agglomération. Un bon produit, qu’on n’exclut pas de regarder à nouveau un jour selon l’évolution des besoins — mais qui, à ce stade, nous a surtout servi de déclencheur pour clarifier plus précisément la façon dont on voulait restituer un jour de collecte par adresse.

Une fois ce besoin réel reclarifié avec le service Environnement, l’objectif retenu tient en une phrase : permettre à un habitant de retrouver ses jours de collecte via les canaux déjà en place — le site institutionnel, et l’écosystème SIG interne — plutôt que via un outil cartographique supplémentaire et isolé.

Construire le modèle conceptuel de données
#

La première étape, avant tout script ou tout job FME, a été de construire le MCD (modèle conceptuel de données) avec le service Environnement : qu’est-ce qu’un « scénario de collecte » ? Quelle granularité ? Quelles informations doit-il porter pour être exploitable par un moteur de recherche par adresse ?

(Parler de « MCD » pour deux tables — les scénarios, et les scénarios croisés par adresse — c’est un peu prendre un bazooka pour écraser une mouche. Mais la démarche compte autant que l’échelle : c’est en posant les bonnes questions sur deux tables qu’on évite d’en regretter dix.)

On a convergé vers une couche polygonale simple :

  • un polygone = un secteur × une commune × un flux (ordures ménagères, emballages/papiers, verre, textiles) ;
  • pour chaque polygone : mode de collecte, jour(s) de collecte, consignes de tri, lien vers le calendrier.

Concrètement, ça donne cette table, volontairement plate (pas de normalisation en tables séparées pour les flux ou les jours — la volumétrie ne le justifie pas) :

CREATE TABLE environnement.vca_environnement_collecte_scenario (
    gid serial4 NOT NULL, -- PK.
    id_collecte varchar(37) DEFAULT uuid_generate_v4()::character varying(37) NOT NULL, -- ID.
    the_geom public.geometry(polygon, 2154) NULL, -- Géométrie
    code_insee varchar NULL, -- Code INSEE
    type_flux varchar NULL, -- Type de flux
    mode_collecte varchar NULL, -- Mode de collecte
    jour_collecte_01 varchar NULL, -- Jour 01
    jour_collecte_02 varchar NULL, -- Jour 02
    jour_collecte_03 varchar NULL, -- Jour 03
    lien_calendrier varchar NULL, -- Lien vers calendrier
    conteneur_enterre_conseille varchar NULL, -- Conteneur enterré conseillé
    consigne text NULL, -- Consignes
    com text NULL, -- Commentaire
    designation varchar NULL, -- Désignation
    CONSTRAINT vca_environnement_collecte_scenario_pkey PRIMARY KEY (gid)
);
CREATE INDEX vca_environnement_collecte_scenario_the_geom_sidx
    ON environnement.vca_environnement_collecte_scenario USING gist (the_geom);

-- Column comments
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.gid IS 'PK.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.id_collecte IS 'ID.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.the_geom IS 'Géométrie';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.code_insee IS 'Code INSEE';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.type_flux IS 'Type de flux';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.mode_collecte IS 'Mode de collecte';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.jour_collecte_01 IS 'Jour 01';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.jour_collecte_02 IS 'Jour 02';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.jour_collecte_03 IS 'Jour 03';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.lien_calendrier IS 'Lien vers calendrier';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.conteneur_enterre_conseille IS 'Conteneur enterré conseillé';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.consigne IS 'Consignes';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.com IS 'Commentaire';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario.designation IS 'Désignation';

Deux choix simples mais qui font gagner du temps par la suite : jusqu’à trois jours de collecte en colonnes séparées (jour_collecte_01/02/03) plutôt qu’un tableau ou une table enfant — ça suffit largement pour le cas d’usage et ça reste trivial à agréger dans la requête de croisement — et un index spatial GiST sur the_geom, indispensable dès qu’on va faire un ST_Contains sur ~32 700 points d’adresse.

Cette couche a été numérisée et est maintenue manuellement par le service Environnement avec le service SIG — pas de bureau d’études extérieur sur cette partie. Elle doit beaucoup à l’expertise métier de la personne porteuse du besoin côté service Environnement, en lien direct avec le prestataire du marché de collecte pour caler précisément les secteurs et les fréquences. Elle est aujourd’hui à un peu moins de 300 entités, inventaire quasiment finalisé à la rédaction de cette note technique.

Le second pilier du modèle, c’est le référentiel adresse : la Base Adresse Nationale, importée via export open data des communes membres. Le croisement des deux — une intersection spatiale simple entre point d’adresse et polygone de scénario — donne, pour chaque adresse, son ou ses scénarios applicables.

Un détail technique de la BAL a directement compliqué ce croisement : dans bal.vca_bal_adresse, un même numéro de voie peut être porté par plusieurs points BAL distincts — un par entrée, par bâtiment, voire par segment de voie — la nuance étant portée par le champ position. Sur nos ~32 700 points BAL, la répartition est très largement dominée par entrée (27 265 points), suivie de bâtiment (3 333) et segment (1 343) ; le reste (parcelle, logement, cage d'escalier, délivrance postale, service technique) reste marginal. Rapporté au couple numéro/voie/commune, ça donne 32 616 identités d’adresse « textuelles » uniques pour 32 668 points géométriques — un écart faible en proportion, mais suffisant pour produire des lignes en double dès qu’on croise chaque point individuellement avec les scénarios : 47 identités portées par 2 points BAL, une par 3, une par 4. Deux points géométriquement différents mais rattachés au même numéro/voie, ce n’est déjà pas une adresse « unique » au sens strict avant même de croiser quoi que ce soit avec les scénarios de collecte.

Le point le plus intéressant du MCD n’est donc pas le croisement lui-même, mais la façon de restituer un résultat à une seule ligne par adresse. Une première version du croisement produisait plusieurs lignes par adresse — la multiplicité des points BAL par position s’ajoutant à la multiplicité des flux — jusqu’à 7 lignes pour une seule adresse dans certains cas. Ça bloquait l’import côté prestataire du site web, qui attendait une ligne = une adresse. La correction a consisté à pivoter le résultat : une colonne par flux et par information (mode_collecte_om, jour_collecte_om, consigne_om, mode_collecte_verre…), agrégées par STRING_AGG conditionnel — et à regrouper les points BAL en amont par identité de voie (GROUP BY numero, voie_nom, commune_nom...), la géométrie exposée devenant le centroïde des points regroupés.

Info : ce genre d’ajustement — passer d’un modèle « une ligne par scénario » à « une ligne par adresse, colonnes pivotées » — est un cas d’école du décalage entre un MCD qui semble juste sur le papier et ce qu’attend réellement le système consommateur en aval.

Le pipeline en pratique
#

Concrètement, le pipeline s’articule en trois étapes.

graph TB
    subgraph "Sources"
        BAL["Base Adresse Nationale
export open data des communes"] SCENARIO["Scénarios de collecte
couche polygonale numérisée
service Environnement + SIG"] end subgraph "Traitement — PostgreSQL/PostGIS" FME["Job FME
import BAL/BAN"] ADRESSE[("Table adresses
~30 000 points")] CROISEMENT[("Table croisée
1 ligne / adresse
colonnes pivotées par flux")] end subgraph "Diffusion" WFS["Flux WFS GeoServer
GeoJSON"] GEO["Recherche GEO interne
module maison"] end BAL -->|FME| FME --> ADRESSE SCENARIO -->|intersection spatiale ST_Contains| CROISEMENT ADRESSE -->|intersection spatiale ST_Contains| CROISEMENT CROISEMENT --> WFS CROISEMENT --> GEO style BAL fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000 style SCENARIO fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000 style FME fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000 style ADRESSE fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000 style CROISEMENT fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000 style WFS fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000 style GEO fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000
  1. Import BAL via un job FME, avec un paramètre publié permettant de basculer commune par commune sur l’export BAN quand aucun export BAL n’est disponible localement.
  2. Croisement spatial en SQL/PostGIS, matérialisé dans une table dédiée — c’est la requête pivotée décrite plus haut.
  3. Diffusion : un flux WFS GeoServer (GeoJSON, projeté en Lambert-93) côté site web, et un accès direct à la table pour la recherche interne.

Pour ne pas rester sur votre faim si vous voulez reproduire le principe chez vous, voici la requête de croisement dans son intégralité, telle qu’elle tourne réellement, COMMENT ON compris :

DROP TABLE IF EXISTS environnement.vca_environnement_collecte_scenario_adresse;
CREATE TABLE environnement.vca_environnement_collecte_scenario_adresse AS
SELECT
    ROW_NUMBER() OVER (
        ORDER BY
            a.commune_insee,
            a.numero || COALESCE(NULLIF(UPPER(TRIM(a.suffixe)), ''), '') || ' ' || a.voie_nom || ' ' || c.code_postal || ' ' || a.commune_nom
    ) AS gid,
    uuid_generate_v4()::character varying(37) AS uuid_unique,
    MIN(a.uid_adresse)                          AS uid_adresse,
    MIN(a.cle_interop)                          AS cle_interop,
    ST_Centroid(ST_Union(a.the_geom))           AS the_geom,
    a.numero || COALESCE(NULLIF(UPPER(TRIM(a.suffixe)), ''), '') || ' ' || a.voie_nom || ' ' || c.code_postal || ' ' || a.commune_nom
                                                AS adresse_complete,
    a.commune_nom,
    a.commune_insee::VARCHAR                    AS code_insee,
    c.code_postal,

    -- EMBALLAGES PAPIERS
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'EMBALLAGES PAPIERS' THEN s.mode_collecte END, ' / ')
        AS mode_collecte_emballages_papiers,
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'EMBALLAGES PAPIERS' THEN
        CONCAT_WS(' / ',
            NULLIF(TRIM(s.jour_collecte_01), ''),
            NULLIF(TRIM(s.jour_collecte_02), ''),
            NULLIF(TRIM(s.jour_collecte_03), ''))
    END, ' | ')
        AS jour_collecte_emballages_papiers,
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'EMBALLAGES PAPIERS' THEN s.consigne END, '<br/>')
        AS consigne_emballages_papiers,

    -- ORDURES MÉNAGÈRES
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'ORDURES MÉNAGÈRES' THEN s.mode_collecte END, ' / ')
        AS mode_collecte_om,
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'ORDURES MÉNAGÈRES' THEN
        CONCAT_WS(' / ',
            NULLIF(TRIM(s.jour_collecte_01), ''),
            NULLIF(TRIM(s.jour_collecte_02), ''),
            NULLIF(TRIM(s.jour_collecte_03), ''))
    END, ' | ')
        AS jour_collecte_om,
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'ORDURES MÉNAGÈRES' THEN s.consigne END, '<br/>')
        AS consigne_om,

    -- VERRE
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'VERRE' THEN s.mode_collecte END, ' / ')
        AS mode_collecte_verre,
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'VERRE' THEN s.consigne END, '<br/>')
        AS consigne_verre,

    -- TEXTILES
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'TEXTILES' THEN s.mode_collecte END, ' / ')
        AS mode_collecte_textile,
    STRING_AGG(DISTINCT CASE WHEN s.type_flux = 'TEXTILES' THEN s.consigne END, '<br/>')
        AS consigne_textile,

    MAX(s.lien_calendrier)                      AS lien_calendrier,
    AVG(a.long)                                 AS long,
    AVG(a.lat)                                  AS lat

FROM bal.vca_bal_adresse a
INNER JOIN environnement.vca_environnement_collecte_scenario s
    ON ST_Contains(s.the_geom, a.the_geom)
INNER JOIN cadastre.vca_dgi_commune c
    ON c.insee::VARCHAR = a.commune_insee::VARCHAR
GROUP BY
    a.numero,
    a.suffixe,
    a.voie_nom,
    a.commune_nom,
    a.commune_insee,
    c.code_postal
ORDER BY
    a.commune_insee,
    a.numero || COALESCE(NULLIF(UPPER(TRIM(a.suffixe)), ''), '') || ' ' || a.voie_nom || ' ' || c.code_postal || ' ' || a.commune_nom;

ALTER TABLE environnement.vca_environnement_collecte_scenario_adresse
    ADD PRIMARY KEY (gid);

-- Commentaires
COMMENT ON TABLE environnement.vca_environnement_collecte_scenario_adresse
    IS 'Adresses BAL avec scénarios de collecte pivotés par type de flux.';

COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.gid
    IS 'Clé primaire.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.uuid_unique
    IS 'Clé unique de l''adresse.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.uid_adresse
    IS 'Identifiant unique national d''adresse.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.cle_interop
    IS 'Clé nationale d''interopérabilité.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.the_geom
    IS 'Géométrie du point adresse (Lambert 93).';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.adresse_complete
    IS 'Adresse complète (numéro + suffixe + voie + code postal + commune).';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.commune_nom
    IS 'Nom de la commune.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.code_insee
    IS 'Code INSEE de la commune.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.code_postal
    IS 'Code postal de la commune.';

COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.mode_collecte_emballages_papiers
    IS 'Mode de collecte pour le flux Emballages / Papiers.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.jour_collecte_emballages_papiers
    IS 'Jour(s) de collecte pour le flux Emballages / Papiers (concaténés, séparateur " | " si plusieurs scénarios).';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.consigne_emballages_papiers
    IS 'Consignes de tri pour le flux Emballages / Papiers (HTML, séparateur <br/> si plusieurs scénarios).';

COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.mode_collecte_om
    IS 'Mode de collecte pour le flux Ordures Ménagères.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.jour_collecte_om
    IS 'Jour(s) de collecte pour le flux Ordures Ménagères (concaténés, séparateur " | " si plusieurs scénarios).';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.consigne_om
    IS 'Consignes de tri pour le flux Ordures Ménagères (HTML, séparateur <br/> si plusieurs scénarios).';

COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.mode_collecte_verre
    IS 'Mode de collecte pour le flux Verre.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.consigne_verre
    IS 'Consignes de tri pour le flux Verre (HTML, séparateur <br/> si plusieurs scénarios).';

COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.mode_collecte_textile
    IS 'Mode de collecte pour le flux Textiles.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.consigne_textile
    IS 'Consignes de tri pour le flux Textiles (HTML, séparateur <br/> si plusieurs scénarios).';

COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.lien_calendrier
    IS 'Lien vers le calendrier de collecte de la commune.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.long
    IS 'Longitude WGS84 de l''adresse BAL.';
COMMENT ON COLUMN environnement.vca_environnement_collecte_scenario_adresse.lat
    IS 'Latitude WGS84 de l''adresse BAL.';

Le mécanisme tient en deux idées : un INNER JOIN ... ON ST_Contains(...) pour rattacher chaque point d’adresse à son (ou ses) polygone(s) de scénario, et un STRING_AGG(DISTINCT CASE WHEN type_flux = '...' THEN ... END, séparateur) par flux pour transformer des lignes en colonnes. C’est la même logique qu’un PIVOT/CROSSTAB, écrite à la main pour rester lisible sans extension supplémentaire.

Attention : ce INNER JOIN exclut silencieusement toute adresse hors de tout polygone de scénario. Sur un premier jet, préférez un LEFT JOIN avec un indicateur explicite (sans_scenario) pour repérer les trous de couverture avant de basculer en production.

Rien d’exotique techniquement — PostGIS sait faire une intersection spatiale et un pivot depuis longtemps. Ce qui a demandé du temps, c’est la convergence sur le bon modèle avec les parties prenantes, pas la mécanique SQL.

Deux canaux, deux logiques de développement
#

C’est le point qui illustre le mieux la différence entre les deux moitiés du projet.

Côté site institutionnel, la donnée est consommée par un moteur de recherche développé par le prestataire CMS du site web, à partir du flux WFS qu’on expose. On sait quoi on leur donne (le schéma de la table, le flux GeoJSON), mais on ne sait pas précisément comment leur moteur indexe et restitue la donnée côté front — c’est une boîte noire de leur côté, avec laquelle on échange par specs et retours d’anomalie.

Résultat de recherche par adresse sur le site institutionnel de VCA
Le moteur de recherche du site institutionnel, développé par le prestataire CMS, restitue le jour de collecte à partir d’une adresse saisie par l’habitant.

Côté écosystème SIG interne, un module — geo-garbage-collector — a été développé en interne pour interroger directement la même table de croisement, sans dépendre du prestataire. C’est le module qu’on maîtrise de bout en bout : le schéma de données, la logique de recherche, les évolutions.

Interface du module geo-garbage-collector interrogeant la table de croisement
Le module geo-garbage-collector, développé en interne, interroge directement la table pivotée dans l’écosystème GEO.
Architecture : avoir deux consommateurs indépendants de la même table pivotée (le WFS externe et le module interne) a un effet positif inattendu — toute correction de modèle bénéficie aux deux canaux en même temps, sans synchronisation manuelle.

L’agilité comme atout
#

Maîtriser le pipeline de bout en bout — modèle de données, requête SQL, diffusion — a permis d’absorber plusieurs ajustements en cours de route sans dépendance externe pour les faire évoluer. Deux exemples concrets :

  • Le problème des lignes multiples par adresse, remonté par le prestataire CMS après mise en production. La correction (passage au pivot une-ligne-par-adresse) a été un cycle de développement interne classique, mené au rythme du retour utilisateur.
  • Un problème d’indexation côté prestataire lié au volume (~30 000 fiches indexées en une seule passe, saturant la mémoire du serveur d’indexation) : identifié, communiqué, suivi — sans qu’on ait à toucher à notre pipeline, juste à documenter et attendre le correctif côté indexation par lots.

L’agilité, ici, ce n’est pas « aller vite » au sens startup du terme. C’est pouvoir itérer sur le modèle de données et la requête au rythme des retours utilisateurs et des retours du prestataire, sans cycle de release externe à attendre pour chaque ajustement structurel.

Ce qu’il reste à construire
#

Tout n’est pas terminé, et c’est normal pour un projet qui vit encore. Les points identifiés à ce stade :

  • Couverture spatiale non tracée : une adresse hors polygone de scénario est aujourd’hui silencieusement exclue du résultat — un LEFT JOIN avec indicateur explicite et un rapport de couverture par commune sont prévus.
  • Pas de contrôle qualité outillé sur la couche des scénarios (recouvrements de polygones, trous de couverture) — de la saisie manuelle sans filet automatique pour l’instant.
  • Rafraîchissement manuel du pipeline (FME + SQL), sans orchestration ni alerte en cas d’échec, alors que le flux est déjà consommé en production. À terme, l’étape FME (import BAL/BAN) pourrait être remplacée par un script Linux autonome, plus facile à orchestrer et à planifier qu’un job FME — à voir selon la charge que ça représente face au bénéfice réel.
  • Pas de test de non-régression sur le volume de lignes ni l’absence de doublons par adresse — le seul filet de sécurité à ce jour a été un signalement manuel du prestataire.
  • Point d’apport volontaire et borne textile les plus proches : compléter la restitution par adresse avec le point d’apport volontaire le plus proche, et de même pour les bornes de collecte de textile — les deux couches sont déjà présentes et à jour sous GEO, en base PostgreSQL/PostGIS, il ne reste qu’à brancher un calcul de proximité dessus.

Conclusion
#

Ce projet est un bon exemple de ce que « rester agile » veut dire au quotidien dans un service SIG : pas une posture, mais la capacité concrète à ajuster un modèle de données en quelques jours quand le besoin se précise.

Ce qu’on en retient :

  • Clarifier le besoin réel de restitution avant de choisir un outil ou une méthode — même un bon produit générique peut ne pas correspondre à un besoin très spécifique.
  • Le temps investi dans le MCD (granularité du scénario, pivot une-ligne-par-adresse) a plus payé que n’importe quelle optimisation SQL.
  • Développer le module de recherche interne en parallèle du canal prestataire donne un filet de sécurité et une maîtrise complète d’au moins un des deux canaux.
Astuce : si vous portez un projet similaire, commencez par le MCD avec le métier, pas par le choix de l’outil. L’outil (FME, PostGIS, un script Python) se choisit une fois le modèle stabilisé, pas avant.

Image de couverture : Pawel Czerwinski sur Unsplash.

Articles connexes