> ## Documentation Index
> Fetch the complete documentation index at: https://doc.fluximmo.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Recherche géographique avancée

> geoBoundingBox pour cartes interactives, geoDistance pour rayons, multi-zones OR pour multi-territoires.

## Goal

Exploiter les deux modes géométriques de l'API Fluximmo (`geoBoundingBox`, `geoDistance`) ainsi que la combinaison multi-zones (logique `OR`) pour couvrir trois cas réels : carte interactive avec pan/zoom, rayon autour d'un point géocodé, et chasse multi-territoires.

## Scénario

Vous construisez une expérience de recherche géo plus riche qu'un simple filtre `postalCode` ou `department`. Selon le contexte produit, vous devez :

* répondre dynamiquement à un viewport carte (Cas A) ;
* limiter la recherche à un rayon métrique autour d'un POI géocodé (Cas B) ;
* agréger plusieurs territoires administratifs et géométriques en une seule requête (Cas C).

Ce playbook donne pour chacun la requête type et les contraintes à respecter.

<Tip>
  Tous les payloads ci-dessous se passent dans le wrapper standard `{ "size": N, "search": { "filterProperty": { "location": [...] } } }` du DTO `/v2/protected/properties/search`. Le même schéma de filtres est réutilisé dans le payload des alertes ADVERTS. Voir [Recherche géographique](/concepts/recherche-geographique).
</Tip>

## Étapes

<Steps>
  <Step title="Cas A — Carte interactive avec geoBoundingBox">
    **Objectif** : à chaque pan/zoom utilisateur, requêter Fluximmo avec la nouvelle bounding box du viewport.

    **Contraintes critiques** :

    * `topLeft.lat` doit être **strictement supérieure** à `bottomRight.lat`
    * `topLeft.lon` doit être **strictement inférieure** à `bottomRight.lon`
    * une bbox inversée renvoie 0 résultat (pas d'erreur HTTP)

    **Optimisations UI** :

    * debounce de 300–500 ms côté front pour éviter de spammer pendant le pan
    * adapter `size` au niveau de zoom (zoom large → 50, zoom rue → 200)
    * annuler la requête en vol si une nouvelle est déclenchée

    ```bash theme={null}
    curl -X POST 'https://api.fluximmo.io/v2/protected/properties/search' \
      -H 'x-api-key: $FLUXIMMO_API_KEY' \
      -H 'Content-Type: application/json' \
      -d '{
        "size": 50,
        "sortBy": "FIRST_SEEN_AT",
        "orderBy": "DESC",
        "search": {
          "filterProperty": {
            "location": [
              {
                "locationCoordinate": {
                  "location": {
                    "geoBoundingBox": {
                      "topLeft":     { "lat": 48.9021, "lon": 2.2241 },
                      "bottomRight": { "lat": 48.8156, "lon": 2.4699 }
                    }
                  }
                }
              }
            ],
            "meta": { "isTotallyOffline": false }
          }
        }
      }'
    ```

    ```text theme={null}
    # Pseudocode garde-fous
    search_in_viewport(top_left, bottom_right, size = 50):
        assert top_left.lat > bottom_right.lat   # sinon 0 résultat
        assert top_left.lon < bottom_right.lon
        return POST /properties/search { size, search: { filterProperty: { location: [...] } } }
    ```
  </Step>

  <Step title="Cas B — Rayon autour d'un point géocodé avec geoDistance">
    **Objectif** : un investisseur cible "20 km autour de la gare TGV de Bordeaux Saint-Jean".

    **Workflow** :

    1. **Géocoder** l'adresse via `/geocoding/search` (BAN/BANO) — voir [Géocoder pour search](/playbooks/geocoder-pour-search).
    2. **Injecter** les coordonnées dans un filtre `geoDistance` (`pin` + `distanceKm`).

    ```bash theme={null}
    curl -X POST 'https://api.fluximmo.io/v2/protected/properties/search' \
      -H 'x-api-key: $FLUXIMMO_API_KEY' \
      -H 'Content-Type: application/json' \
      -d '{
        "size": 100,
        "sortBy": "FIRST_SEEN_AT",
        "orderBy": "DESC",
        "search": {
          "filterProperty": {
            "location": [
              {
                "locationCoordinate": {
                  "location": {
                    "geoDistance": {
                      "pin":        { "lat": 44.8262, "lon": -0.5564 },
                      "distanceKm": 20
                    }
                  }
                }
              }
            ],
            "meta": { "isTotallyOffline": false }
          }
        }
      }'
    ```
  </Step>

  <Step title="Cas C — Multi-zones OR (chasseur multi-territoires)">
    **Objectif** : une agence opère sur Paris + petite couronne (75, 92, 93, 94). Plutôt que 4 requêtes séquentielles, on combine les 4 territoires dans un seul tableau `location`.

    **Sémantique** : tous les éléments du tableau `location` sont combinés en `OR` côté moteur. Vous pouvez **mixer librement** les modes : un `department`, un `geoDistance`, un `geoBoundingBox` dans le même tableau.

    **Avantage** : 1 requête + 1 facture quota au lieu de N. Pagination cohérente sur l'union.

    ```bash theme={null}
    curl -X POST 'https://api.fluximmo.io/v2/protected/properties/search' \
      -H 'x-api-key: $FLUXIMMO_API_KEY' \
      -H 'Content-Type: application/json' \
      -d '{
        "size": 100,
        "sortBy": "FIRST_SEEN_AT",
        "orderBy": "DESC",
        "search": {
          "filterProperty": {
            "location": [
              { "department": "75" },
              { "department": "92" },
              { "department": "93" },
              { "department": "94" }
            ],
            "meta": { "isTotallyOffline": false }
          }
        }
      }'
    ```

    Mix possible : `[{ "department": "69" }, { "locationCoordinate": { "location": { "geoDistance": { "pin": { "lat": 45.7640, "lon": 4.8357 }, "distanceKm": 5 } } } }, { "postalCode": "69100" }]` (Lyon dpt + 5 km autour du centre + Villeurbanne).
  </Step>
</Steps>

## Architecture / flow

Arbre de décision pour choisir le bon mode géo selon le besoin produit.

```mermaid theme={null}
flowchart TD
  Start[Besoin geo] --> Q1{Forme<br/>administrative ?}
  Q1 -- Oui --> Admin[postalCode<br/>inseeCode<br/>department]
  Q1 -- Non --> Q2{Carte<br/>interactive ?}
  Q2 -- Oui --> BBox[geoBoundingBox<br/>Cas A]
  Q2 -- Non --> Q3{Rayon autour<br/>d un point ?}
  Q3 -- Oui --> Dist[geoDistance<br/>Cas B]
  Q3 -- Non --> Q4{Plusieurs<br/>territoires ?}
  Q4 -- Oui --> Multi[Tableau location<br/>OR - Cas C]
  Q4 -- Non --> Admin
  Multi -.mixe.-> BBox
  Multi -.mixe.-> Dist
  Multi -.mixe.-> Admin
```

## Pièges fréquents

<Warning>
  **Bounding box inversée** : si `topLeft.lat <= bottomRight.lat` ou `topLeft.lon >= bottomRight.lon`, l'API renvoie `0` résultat **sans erreur**. Validez toujours côté client avant l'appel.
</Warning>

<Warning>
  **`distanceKm <= 0`** : un rayon nul ou négatif retourne une erreur de validation. Définissez un minimum applicatif (ex. 0.5 km).
</Warning>

<Warning>
  **Champ `city`** : le filtre `city` (texte libre) n'est plus supporté en V2. Géocodez l'adresse puis utilisez `geoDistance`, ou passez par `postalCode` / `inseeCode`. Voir [Filtres communs](/concepts/filtres-communs).
</Warning>

<Warning>
  **Wrapper DTO oublié** : tous les filtres `location` doivent être imbriqués dans `{ "search": { "filterProperty": { "location": [...] } } }`. Un payload plat est rejeté en 400.
</Warning>

## Pour aller plus loin

* [Concepts — Recherche géographique](/concepts/recherche-geographique)
* [Concepts — Filtres communs](/concepts/filtres-communs)
* [API — POST /properties/search](/api-v2-reference/properties-search/search-properties)
* [Playbook — Géocoder pour search](/playbooks/geocoder-pour-search)
* [Playbook — Search properties analytique](/playbooks/search-properties-analytique)

<Snippet file="/snippets/cta-cle-test-gratuite.mdx" />
