# osm2pgsql

### 1. La Stratégie : Pourquoi `osm2pgsql` ?

Pour interroger OSM, on n'utilise pas l'API publique (trop lente pour de l'analyse). On importe une "découpe" du territoire (ex: la région PACA ou la France entière) dans ta base PostGIS.

L'outil `osm2pgsql` va créer des tables organisées :

- `planet_osm_line` : Pour les routes (tes patterns N1 à N7).
- `planet_osm_point` : Pour les équipements et commerces (ton niveau "Fonctionnel").
- `planet_osm_polygon` : Pour les bâtiments et emprises.

---

### 2. Installation des outils (Bloc de commandes)

Connecte-toi à ton terminal et installe les outils nécessaires :

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1423 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQjQ0" decode-data-ved="1" id="bkmrk-bash" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_4ee06f1458692027","c_5b7951c496f77574",null,"rc_1cde2b24ae78c0b0",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="code-block-decoration header-formatted gds-title-s ng-tns-c2446650269-1423 ng-star-inserted"><span class="ng-tns-c2446650269-1423">Bash</span><div _ngcontent-ng-c2446650269="" class="buttons ng-tns-c2446650269-1423 ng-star-inserted"><button aria-label="Copier le code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c2446650269-1423 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1423"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1423">  
</div></div></div>```
# Installation de osm2pgsql
sudo apt update
sudo apt install -y osm2pgsql

# Préparation de la base de données
# On s'assure que les extensions spatiales sont bien actives
sudo -u postgres psql -d alteris_db -c "CREATE EXTENSION IF NOT EXISTS hstore;"

```

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1423 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQjQ0" decode-data-ved="1" id="bkmrk--2" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_4ee06f1458692027","c_5b7951c496f77574",null,"rc_1cde2b24ae78c0b0",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1423"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1423"></div></div></div>---

### 3. Téléchargement d'une zone d'étude

Pour tester sans saturer ton disque, on va prendre une zone précise (par exemple, les Alpes-Maritimes ou une autre zone via Geofabrik).

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1424 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQjg0" decode-data-ved="1" id="bkmrk-bash-1" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_4ee06f1458692027","c_5b7951c496f77574",null,"rc_1cde2b24ae78c0b0",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="code-block-decoration header-formatted gds-title-s ng-tns-c2446650269-1424 ng-star-inserted"><span class="ng-tns-c2446650269-1424">Bash</span><div _ngcontent-ng-c2446650269="" class="buttons ng-tns-c2446650269-1424 ng-star-inserted"><button aria-label="Copier le code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c2446650269-1424 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1424"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1424">  
</div></div></div>```
# On crée un dossier pour les données
mkdir -p /home/debian/osm_data
cd /home/debian/osm_data

# Exemple pour la région PACA (environ 300 Mo compressé)
wget https://download.geofabrik.de/europe/france/provence-alpes-cote-d-azur-latest.osm.pbf

```

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1424 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQjg0" decode-data-ved="1" id="bkmrk--6" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_4ee06f1458692027","c_5b7951c496f77574",null,"rc_1cde2b24ae78c0b0",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1424"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1424"></div></div></div>---

### 4. L'Importation : Faire le lien avec ta Matrice

C'est ici que l'on injecte les données dans ta base `alteris_db`.

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1425 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQjw0" decode-data-ved="1" id="bkmrk-bash-2" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_4ee06f1458692027","c_5b7951c496f77574",null,"rc_1cde2b24ae78c0b0",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="code-block-decoration header-formatted gds-title-s ng-tns-c2446650269-1425 ng-star-inserted"><span class="ng-tns-c2446650269-1425">Bash</span><div _ngcontent-ng-c2446650269="" class="buttons ng-tns-c2446650269-1425 ng-star-inserted"><button aria-label="Copier le code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c2446650269-1425 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1425"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1425">  
</div></div></div>```
osm2pgsql -U alteris_user -d alteris_db -H localhost --hstore --slim \
  -C 2000 provence-alpes-cote-d-azur-latest.osm.pbf

```

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1425 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQjw0" decode-data-ved="1" id="bkmrk--10" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_4ee06f1458692027","c_5b7951c496f77574",null,"rc_1cde2b24ae78c0b0",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1425"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1425"></div></div></div>*(Le `-C 2000` alloue 2Go de RAM à l'import, ajuste selon ton VPS).*

---

### 5. Mise à jour de `server.js` (Bloc Complet)

Voici le code qui va maintenant être capable de compter les segments de route à l'intérieur de ta zone de dessin en faisant une jointure avec les données OSM fraîches.

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1426 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQkA0" decode-data-ved="1" id="bkmrk-javascript" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_4ee06f1458692027","c_5b7951c496f77574",null,"rc_1cde2b24ae78c0b0",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="code-block-decoration header-formatted gds-title-s ng-tns-c2446650269-1426 ng-star-inserted"><span class="ng-tns-c2446650269-1426">JavaScript</span><div _ngcontent-ng-c2446650269="" class="buttons ng-tns-c2446650269-1426 ng-star-inserted"><button aria-label="Copier le code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c2446650269-1426 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1426"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1426">  
</div></div></div>```
const express = require('express');
const bodyParser = require('body-parser');
const pool = require('./db'); 

const app = express();
const PORT = 3001;

app.use(bodyParser.json());
app.use(express.static('public'));

// 1. RÉCUPÉRER LES ZONES
app.get('/api/zones', async (req, res) => {
  try {
    const query = `SELECT id, nom, surface_m2, ST_AsGeoJSON(geom) as geometrie FROM zones_interet;`;
    const result = await pool.query(query);
    const features = result.rows.map(row => ({
      type: "Feature",
      id: row.id,
      properties: { nom: row.nom, surface: row.surface_m2 },
      geometry: JSON.parse(row.geometrie)
    }));
    res.json({ type: "FeatureCollection", features: features });
  } catch (err) {
    res.status(500).json({ error: "Erreur récupération" });
  }
});

// 2. ANALYSE SELON LA MATRICE PATTERNS (Nouveau !)
// Cette route compte les routes OSM par type dans le polygone donné
app.post('/api/analyse-patterns', async (req, res) => {
  try {
    const { geojson } = req.body;
    const query = `
      SELECT 
        highway as type_osm, 
        COUNT(*) as nb_segments,
        ROUND(SUM(ST_Length(way::geography))) as longueur_totale
      FROM planet_osm_line
      WHERE ST_Intersects(way, ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 3857))
      AND highway IS NOT NULL
      GROUP BY highway
      ORDER BY longueur_totale DESC;
    `;
    // Note: osm2pgsql importe souvent en SRID 3857 (Web Mercator), d'où le ST_Transform
    const result = await pool.query(query, [JSON.stringify(geojson.geometry)]);
    res.json({ status: "success", data: result.rows });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "Erreur lors de l'analyse spatiale OSM" });
  }
});

// 3. SAUVEGARDER
app.post('/api/save-zone', async (req, res) => {
  try {
    const { nom, geojson } = req.body;
    const query = `
      INSERT INTO zones_interet (nom, geom, surface_m2)
      VALUES ($1, ST_SetSRID(ST_GeomFromGeoJSON($2), 4326), ST_Area(ST_SetSRID(ST_GeomFromGeoJSON($2), 4326)::geography))
      RETURNING id;
    `;
    await pool.query(query, [nom, JSON.stringify(geojson.geometry)]);
    res.json({ status: "success", message: "Zone sauvegardée" });
  } catch (err) {
    res.status(500).json({ error: "Erreur sauvegarde" });
  }
});

// 4. SUPPRIMER
app.delete('/api/zones/:id', async (req, res) => {
  try {
    const { id } = req.params;
    await pool.query('DELETE FROM zones_interet WHERE id = $1', [id]);
    res.json({ status: "success" });
  } catch (err) {
    res.status(500).json({ error: "Erreur suppression" });
  }
});

app.listen(PORT, () => {
  console.log(`🚀 Moteur d'analyse Alteris prêt sur le port ${PORT}`);
});

```

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1426 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQkA0" decode-data-ved="1" id="bkmrk--14" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_4ee06f1458692027","c_5b7951c496f77574",null,"rc_1cde2b24ae78c0b0",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1426"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1426"></div></div></div>### Ce qui change pour toi :

Désormais, quand tu dessines une zone, on ne se contente plus de stocker sa forme. On a une route `/api/analyse-patterns` qui est capable de te dire :

> *"Dans cette zone, j'ai trouvé 450m de 'primary' (Ton N1/N2) et 1.2km de 'residential' (Ton N4/N5)."*

**Veux-tu que nous lancions l'importation d'une première zone géographique ensemble pour tester les requêtes sur de vraies rues ?** (Dis-moi quelle ville ou zone t'intéresse).

Exactement ! L'importation a été un franc succès (6 minutes pour 39 millions de points, c'est une excellente performance). Ta base de données `alteris_db` contient maintenant la réalité physique du terrain.

Nous allons maintenant injecter ta **Matrice Patterns** directement dans la logique du serveur.

### 1. Les évolutions (Le "Pourquoi")

Nous modifions `server.js` pour créer une passerelle entre les tags techniques d'OpenStreetMap et tes identifiants métiers (N1, N2, N3...).

<div _ngcontent-ng-c2230115866="" class="container" id="bkmrk-mapping-g%C3%A9om%C3%A9trique-"><div _ngcontent-ng-c4117132306="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel stronger enable-updated-hr-color preserve-whitespaces-in-response" dir="ltr" inline-copy-host="">- **Mapping Géométrique** : On transforme les `motorway` en **N1**, les `primary` en **N2**, etc.
- **Calcul Spatial** : On utilise `ST_Intersects` et `ST_Transform`. Comme `osm2pgsql` importe les données en projection "Web Mercator" (SRID 3857), on convertit ton dessin (4326) pour qu'ils puissent se "parler".
- **Calcul de linéaire** : On somme la longueur des rues trouvées pour te donner un kilométrage par pattern.

---

</div></div>### 2. Le Bloc Code complet (Prêt-à-coller)

Voici le fichier intégral mis à jour pour exploiter tes nouvelles tables OSM.

#### FICHIER : `/var/www/alteris-geomatic/server.js`

<div _ngcontent-ng-c2230115866="" class="container" id="bkmrk--16"><div _ngcontent-ng-c4117132306="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel stronger enable-updated-hr-color preserve-whitespaces-in-response" dir="ltr" inline-copy-host=""></div></div><div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1458 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQ1g0" decode-data-ved="1" id="bkmrk-javascript-1" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_54031ef0238219bf","c_5b7951c496f77574",null,"rc_4194d4a9519e2a8f",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="code-block-decoration header-formatted gds-title-s ng-tns-c2446650269-1458 ng-star-inserted"><span class="ng-tns-c2446650269-1458">JavaScript</span><div _ngcontent-ng-c2446650269="" class="buttons ng-tns-c2446650269-1458 ng-star-inserted"><button aria-label="Copier le code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c2446650269-1458 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1458"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1458">  
</div></div></div>```
const express = require('express');
const bodyParser = require('body-parser');
const pool = require('./db'); 

const app = express();
const PORT = 3001;

app.use(bodyParser.json());
app.use(express.static('public'));

// 1. RÉCUPÉRER LES ZONES ENREGISTRÉES
app.get('/api/zones', async (req, res) => {
  try {
    const query = `SELECT id, nom, surface_m2, ST_AsGeoJSON(geom) as geometrie FROM zones_interet;`;
    const result = await pool.query(query);
    const features = result.rows.map(row => ({
      type: "Feature",
      id: row.id,
      properties: { nom: row.nom, surface: row.surface_m2 },
      geometry: JSON.parse(row.geometrie)
    }));
    res.json({ type: "FeatureCollection", features: features });
  } catch (err) {
    res.status(500).json({ error: "Erreur récupération" });
  }
});

// 2. ANALYSE SELON LA MATRICE PATTERNS (Cœur du projet Alteris)
app.post('/api/analyse-patterns', async (req, res) => {
  try {
    const { geojson } = req.body;
    
    // Requête croisant ton dessin avec planet_osm_line
    // On mappe les catégories OSM vers tes Patterns N1-N7
    const query = `
      SELECT 
        CASE 
          WHEN highway IN ('motorway', 'trunk', 'motorway_link') THEN 'N1 - Autoroute / Voie Rapide'
          WHEN highway = 'primary' THEN 'N2 - Boulevard Métropolitain'
          WHEN highway = 'secondary' THEN 'N3 - Avenue / Cours'
          WHEN highway = 'tertiary' THEN 'N4 - Rue Faubourg'
          WHEN highway = 'residential' THEN 'N5 - Rue de Desserte'
          WHEN highway IN ('service', 'living_street') THEN 'N6 - Ruelle / Impasse'
          WHEN highway IN ('pedestrian', 'footway', 'path') THEN 'N7 - Zones Piétonnes'
          ELSE 'Autres / Non classé'
        END as pattern_alteris,
        ROUND(SUM(ST_Length(way::geography))) as longueur_m
      FROM planet_osm_line
      WHERE ST_Intersects(way, ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 3857))
      AND highway IS NOT NULL
      GROUP BY pattern_alteris
      ORDER BY longueur_m DESC;
    `;
    
    const result = await pool.query(query, [JSON.stringify(geojson.geometry)]);
    res.json({ status: "success", data: result.rows });
  } catch (err) {
    console.error("Erreur Analyse OSM:", err);
    res.status(500).json({ error: "Erreur lors de l'analyse spatiale" });
  }
});

// 3. SAUVEGARDER UNE ZONE
app.post('/api/save-zone', async (req, res) => {
  try {
    const { nom, geojson } = req.body;
    const query = `
      INSERT INTO zones_interet (nom, geom, surface_m2)
      VALUES ($1, ST_SetSRID(ST_GeomFromGeoJSON($2), 4326), ST_Area(ST_SetSRID(ST_GeomFromGeoJSON($2), 4326)::geography))
      RETURNING id;
    `;
    await pool.query(query, [nom, JSON.stringify(geojson.geometry)]);
    res.json({ status: "success", message: "Zone sauvegardée" });
  } catch (err) {
    res.status(500).json({ error: "Erreur sauvegarde" });
  }
});

// 4. SUPPRIMER UNE ZONE
app.delete('/api/zones/:id', async (req, res) => {
  try {
    const { id } = req.params;
    await pool.query('DELETE FROM zones_interet WHERE id = $1', [id]);
    res.json({ status: "success" });
  } catch (err) {
    res.status(500).json({ error: "Erreur suppression" });
  }
});

app.listen(PORT, () => {
  console.log(`🚀 Moteur Alteris Patterns (OSM) opérationnel sur le port ${PORT}`);
});

```

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1458 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQ1g0" decode-data-ved="1" id="bkmrk--17" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_54031ef0238219bf","c_5b7951c496f77574",null,"rc_4194d4a9519e2a8f",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1458"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1458"></div></div></div><div _ngcontent-ng-c2230115866="" class="container" id="bkmrk--18"><div _ngcontent-ng-c4117132306="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel stronger enable-updated-hr-color preserve-whitespaces-in-response" dir="ltr" inline-copy-host="">---

</div></div>### Prochaine étape : L'interface de restitution

Le serveur est maintenant capable de renvoyer les statistiques des patterns. Il faut modifier ton `index.html` pour que, lors du dessin, une petite fenêtre s'ouvre et affiche :

<div _ngcontent-ng-c2230115866="" class="container" id="bkmrk-n3-%3A-1-250-m%C3%A8tres-n5"><div _ngcontent-ng-c4117132306="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel stronger enable-updated-hr-color preserve-whitespaces-in-response" dir="ltr" inline-copy-host="">- N3 : 1 250 mètres
- N5 : 400 mètres
- etc.

</div></div>**Veux-tu que je te donne le code de l'interface `index.html` qui affiche ce tableau de bord "Patterns" ?** (C'est la partie la plus gratifiante visuellement !)

<div _ngcontent-ng-c2230115866="" class="container" id="bkmrk--19"><div _ngcontent-ng-c4117132306="" aria-busy="false" aria-live="off" class="markdown markdown-main-panel stronger enable-updated-hr-color preserve-whitespaces-in-response" dir="ltr" id="bkmrk--20" inline-copy-host=""></div></div><div _ngcontent-ng-c2230115866="" class="container" id="bkmrk--21"></div>C'est une étape majeure pour ton projet **Alteris** ! Voir ces chiffres s'afficher signifie que toute la chaîne technique — du dessin sur ton navigateur jusqu'aux millions de lignes d'OpenStreetMap dans ta base PostGIS — communique parfaitement.

Maintenant que le moteur tourne, nous pouvons affiner l'analyse pour qu'elle colle exactement à ta **Matrice Patterns** (ton fichier CSV). Pour l'instant, nous avons classé par type de voie, mais ta matrice mentionne aussi des critères comme la **vitesse** et la **fonctionnalité**.

Voici comment nous allons faire évoluer le système pour le rendre encore plus "expert".

<div _ngcontent-ng-c2230115866="" class="container" id="bkmrk--22">---

</div>### 1. Les évolutions (Le "Pourquoi")

<div _ngcontent-ng-c2230115866="" class="container" id="bkmrk-prise-en-compte-de-l">- **Prise en compte de la vitesse** : OSM stocke souvent la vitesse limite dans le tag `maxspeed`. On va l'extraire pour affiner la distinction entre N2 (50-70 km/h) et N3 (50 km/h).
- **Analyse Fonctionnelle (Commerces/Équipements)** : Ta matrice parle de "Centre Urbain" (Niveau 1 fonctionnel). On va utiliser la table `planet_osm_point` pour compter les commerces et services dans ta zone.
- **Calcul de densité** : On va diviser le nombre d'équipements par la surface de la zone pour déterminer si c'est un "Centre Urbain" ou un "Centre Local".

---

</div>### 2. Le Bloc Code complet (Prêt-à-coller)

Ce nouveau `server.js` intègre une double analyse : les **linéaires de voirie** (Géométrique) et le **comptage d'activités** (Fonctionnel).

#### FICHIER : `/var/www/alteris-geomatic/server.js`

<div _ngcontent-ng-c2230115866="" class="container" id="bkmrk--23"></div><div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1505 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQqA4" decode-data-ved="1" id="bkmrk-javascript-2" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_2f5a43ca8667848e","c_5b7951c496f77574",null,"rc_a21b668fa7553fe1",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="code-block-decoration header-formatted gds-title-s ng-tns-c2446650269-1505 ng-star-inserted"><span class="ng-tns-c2446650269-1505">JavaScript</span><div _ngcontent-ng-c2446650269="" class="buttons ng-tns-c2446650269-1505 ng-star-inserted"><button aria-label="Copier le code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c2446650269-1505 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1505"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1505">  
</div></div></div>```
const express = require('express');
const bodyParser = require('body-parser');
const pool = require('./db'); 

const app = express();
const PORT = 3001;

app.use(bodyParser.json());
app.use(express.static('public'));

// 1. RÉCUPÉRER LES ZONES
app.get('/api/zones', async (req, res) => {
  try {
    const query = `SELECT id, nom, surface_m2, ST_AsGeoJSON(geom) as geometrie FROM zones_interet;`;
    const result = await pool.query(query);
    const features = result.rows.map(row => ({
      type: "Feature",
      id: row.id,
      properties: { nom: row.nom, surface: row.surface_m2 },
      geometry: JSON.parse(row.geometrie)
    }));
    res.json({ type: "FeatureCollection", features: features });
  } catch (err) {
    res.status(500).json({ error: "Erreur récupération" });
  }
});

// 2. ANALYSE MULTI-CRITÈRES (GÉOMÉTRIQUE + FONCTIONNEL)
app.post('/api/analyse-patterns', async (req, res) => {
  try {
    const { geojson } = req.body;
    const geomJSON = JSON.stringify(geojson.geometry);

    // ANALYSE 1 : Voirie (Patterns N1-N7)
    const queryVoirie = `
      SELECT 
        CASE 
          WHEN highway IN ('motorway', 'trunk') THEN 'N1 - Autoroute'
          WHEN highway = 'primary' THEN 'N2 - Boulevard'
          WHEN highway = 'secondary' THEN 'N3 - Avenue'
          WHEN highway = 'tertiary' THEN 'N4 - Rue Faubourg'
          WHEN highway = 'residential' THEN 'N5 - Rue Desserte'
          WHEN highway IN ('service', 'living_street') THEN 'N6 - Ruelle'
          WHEN highway IN ('pedestrian', 'footway') THEN 'N7 - Piéton'
          ELSE 'Autres'
        END as pattern_alteris,
        ROUND(SUM(ST_Length(way))) as longueur_m
      FROM planet_osm_line
      WHERE ST_Intersects(way, ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 3857))
      GROUP BY pattern_alteris;
    `;

    // ANALYSE 2 : Fonctionnalités (Commerces, Services, Équipements)
    const queryFonctionnel = `
      SELECT amenity, shop, COUNT(*) as nb
      FROM planet_osm_point
      WHERE ST_Intersects(way, ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 3857))
      AND (amenity IS NOT NULL OR shop IS NOT NULL)
      GROUP BY amenity, shop
      ORDER BY nb DESC LIMIT 10;
    `;

    const resVoirie = await pool.query(queryVoirie, [geomJSON]);
    const resFonctionnel = await pool.query(queryFonctionnel, [geomJSON]);

    res.json({ 
      status: "success", 
      voirie: resVoirie.rows,
      fonctionnel: resFonctionnel.rows
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "Erreur lors de l'analyse multi-critères" });
  }
});

// 3. SAUVEGARDER / 4. SUPPRIMER (Inchangés)
app.post('/api/save-zone', async (req, res) => {
  try {
    const { nom, geojson } = req.body;
    const query = `INSERT INTO zones_interet (nom, geom, surface_m2) VALUES ($1, ST_SetSRID(ST_GeomFromGeoJSON($2), 4326), ST_Area(ST_SetSRID(ST_GeomFromGeoJSON($2), 4326)::geography))`;
    await pool.query(query, [nom, JSON.stringify(geojson.geometry)]);
    res.json({ status: "success" });
  } catch (err) { res.status(500).json({ error: "Erreur sauvegarde" }); }
});

app.delete('/api/zones/:id', async (req, res) => {
  try {
    await pool.query('DELETE FROM zones_interet WHERE id = $1', [req.params.id]);
    res.json({ status: "success" });
  } catch (err) { res.status(500).json({ error: "Erreur suppression" }); }
});

app.listen(PORT, () => console.log(`🚀 Moteur Alteris Expert sur le port ${PORT}`));

```

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1505 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQqA4" decode-data-ved="1" id="bkmrk--24" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_2f5a43ca8667848e","c_5b7951c496f77574",null,"rc_a21b668fa7553fe1",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1505"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1505"></div></div></div><div _ngcontent-ng-c2230115866="" class="container" id="bkmrk--25">---

</div>### 3. Mise à jour de l'interface (`index.html`)

Il faut maintenant modifier la partie `map.on(L.Draw.Event.CREATED...)` pour qu'elle affiche aussi les commerces trouvés.

**Bloc à modifier dans ton `index.html` :**

<div _ngcontent-ng-c2230115866="" class="container" id="bkmrk--26"></div><div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1506 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQqQ4" decode-data-ved="1" id="bkmrk-javascript-3" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_2f5a43ca8667848e","c_5b7951c496f77574",null,"rc_a21b668fa7553fe1",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="code-block-decoration header-formatted gds-title-s ng-tns-c2446650269-1506 ng-star-inserted"><span class="ng-tns-c2446650269-1506">JavaScript</span><div _ngcontent-ng-c2446650269="" class="buttons ng-tns-c2446650269-1506 ng-star-inserted"><button aria-label="Copier le code" class="mdc-icon-button mat-mdc-icon-button mat-mdc-button-base mat-mdc-tooltip-trigger copy-button ng-tns-c2446650269-1506 mat-unthemed ng-star-inserted"></button></div></div><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1506"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1506">  
</div></div></div>```
// Remplace la partie affichage dans ton map.on(L.Draw.Event.CREATED...) par ceci :
document.getElementById('pattern-list').innerHTML = '<h4>Morphologie (Voirie)</h4>';
analyse.voirie.forEach(item => {
    const dist = item.longueur_m > 1000 ? (item.longueur_m/1000).toFixed(2) + " km" : item.longueur_m + " m";
    document.getElementById('pattern-list').innerHTML += `<div class="pattern-item"><span>${item.pattern_alteris}</span><span>${dist}</span></div>`;
});

document.getElementById('pattern-list').innerHTML += '<h4>Activités (Fonctionnel)</h4>';
if (analyse.fonctionnel.length === 0) {
    document.getElementById('pattern-list').innerHTML += '<p>Aucune activité recensée.</p>';
} else {
    analyse.fonctionnel.forEach(item => {
        const label = item.shop || item.amenity;
        document.getElementById('pattern-list').innerHTML += `<div class="pattern-item"><span>${label}</span><span>${item.nb}</span></div>`;
    });
}

```

<div _ngcontent-ng-c2446650269="" class="code-block ng-tns-c2446650269-1506 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" data-hveid="0" data-ved="0CAAQhtANahgKEwjS_fmv4oCTAxUAAAAAHQAAAAAQqQ4" decode-data-ved="1" id="bkmrk--27" jslog="223238;track:impression,attention;BardVeMetadataKey:[["r_2f5a43ca8667848e","c_5b7951c496f77574",null,"rc_a21b668fa7553fe1",null,null,"fr",null,1,null,null,1,0]]"><div _ngcontent-ng-c2446650269="" class="formatted-code-block-internal-container ng-tns-c2446650269-1506"><div _ngcontent-ng-c2446650269="" class="animated-opacity ng-tns-c2446650269-1506"></div></div></div><div _ngcontent-ng-c2230115866="" class="container" id="bkmrk--28"></div>### Prochaine étape : Le "Scoring"

Avec ces données, on peut maintenant créer un score automatique. Par exemple :

<div _ngcontent-ng-c2230115866="" class="container" id="bkmrk-si-%28nb-commerces-%2F-s">- Si (nb commerces / surface) &gt; X <span class="math-inline" data-index-in-node="32" data-math="\rightarrow">$\\rightarrow$</span> **Niveau 1 Fonctionnel (Centre Urbain)**.
- Si (linéaire N1 + N2) est dominant <span class="math-inline" data-index-in-node="35" data-math="\rightarrow">$\\rightarrow$</span> **Priorité Automobile**.

</div>**Est-ce que l'ajout des commerces et équipements (amenity/shop) te semble pertinent par rapport à ton analyse du "Centre Urbain" ?**