# Pipeline de datasets routiers mobiles NextLimit

Ce répertoire transforme un extrait régional OpenStreetMap en package mobile NextLimit. Geofabrik
est uniquement un fournisseur d'entrée pour ce pipeline hors téléphone : ni le catalogue Android,
ni un manifest publié, ni l'application ne doivent référencer ou télécharger un `.osm.pbf`.

```text
Geofabrik .osm.pbf
        -> filtrage automobile
        -> normalisation des limitations et classes
        -> graphe routier dirigé
        -> index spatial RTree
        -> SQLite mobile
        -> gzip de transport + manifest SHA-256
```

## Source brute

`sources.json` contient les URLs PBF et checksums réservés aux opérateurs du pipeline. Ce fichier
n'est pas embarqué dans l'application. Pour générer la Corse, par exemple :

```bash
curl --fail --location --continue-at - \
  --output build/osm/corse-latest.osm.pbf \
  https://download.geofabrik.de/europe/france/corse-latest.osm.pbf

python3 -m venv build/venv-osm
build/venv-osm/bin/python -m pip install -r tools/osm-dataset/requirements.txt
```

La donnée produite reste une base dérivée d'OpenStreetMap sous ODbL. L'attribution
`© OpenStreetMap contributors`, la licence et son URL sont inscrites dans `dataset_metadata`.

## Filtrage

Les classes conservées sont codées numériquement et stables :

```text
motorway, motorway_link, trunk, trunk_link,
primary, primary_link, secondary, secondary_link,
tertiary, tertiary_link, unclassified, residential,
living_street, service
```

`footway`, `path`, `steps`, `bridleway`, `cycleway`, `pedestrian`, `track`, les bâtiments, POI,
adresses, landuse et tous les objets sans rapport avec la conduite sont exclus. Les services
`parking_aisle`, `driveway`, `drive-through` et `emergency_access` sont également exclus.

La décision d'accès applique la priorité OSM suivante : `motorcar`, puis `motor_vehicle`, puis
`access`. Une autorisation automobile spécifique peut donc lever un `access=private`; une valeur
`no`, `private`, `agricultural`, `forestry`, `emergency` ou `military` interdit sinon la voie.

## Tags lus et données conservées

Le PBF est lu avec les seuls tags utiles à la décision :

```text
highway, maxspeed, maxspeed:forward, maxspeed:backward,
oneway, junction, zone:maxspeed, source:maxspeed, maxspeed:type,
access, motor_vehicle, motorcar, service, name, ref
```

Les chaînes d'entrée ne sont pas recopiées dans le package. Le pipeline produit un
`speed_limit_kmh`, un `speed_source` et une confiance par arête dirigée. La priorité est : valeur
directionnelle, valeur explicite, type réglementaire OSM reconnu, fallback pays conservateur,
sinon inconnu. `mph` est converti en km/h. Les règles FR/BE/DE sont alignées sur les règles du
moteur Android; un contexte non prouvable reste inconnu.

`name` est gardé pour le debug et l'affichage de route; `ref` n'est conservé comme libellé que si
`name` est absent. L'OSM way id reste toujours disponible pour le diagnostic. `lanes`, `surface` et
les tags d'accès bruts ne sont pas stockés car le moteur ne les consulte pas après génération.

## Format mobile

Le schéma SQLite mobile v2 contient :

- `dataset_metadata` : région, pays, source, dates, emprise, compteur et licence;
- `road_segments` : id, OSM way id, nœuds `from/to`, sens, longueur, géométrie binaire E7, classe
  numérique, limitation normalisée, source, confiance, flags oneway/roundabout et libellé;
- `road_segment_rtree` : emprise E7 de chaque segment;
- index `from_node_id`, `to_node_id` et `(osm_way_id, direction)`.

Une arête est stockée pour chaque sens autorisé. La géométrie suit ce sens. Les points OSM
intermédiaires sans embranchement sont regroupés dans une polyligne; les nœuds partagés entre ways
sont obligatoirement conservés comme extrémités afin de préserver le graphe. Une polyligne reste
bornée à 4 096 points.

Le runtime garde une lecture temporaire du schéma v1 pour les installations existantes. Tous les
nouveaux packages sont générés en v2.

## Génération régionale

L'identifiant `--region` doit provenir du catalogue `core/data/src/main/assets/offline/regions.json`.
La France est publiée région par région; aucun package France entière n'est proposé actuellement.

```bash
build/venv-osm/bin/python tools/osm-dataset/build_dataset.py \
  build/osm/corse-latest.osm.pbf \
  build/osm/FR-COR.sqlite \
  --dataset-id FR-COR-2026.08.09 \
  --country FR \
  --region FR-COR \
  --source-date 2026-08-06T20:21:36Z \
  --force
```

Le premier passage compte les objets, filtre les ways et calcule sur disque les nœuds de jonction.
Le second construit le graphe, normalise les vitesses, encode les géométries et remplit le RTree.
`integrity_check`, `rtreecheck` et les index sont validés avant la bascule atomique du fichier.

## Compression et publication

Le SQLite brut est utilisé localement pour des requêtes rapides. Le téléchargement est un
`.sqlite.gz`; Android vérifie la taille et le SHA-256 du gzip, contrôle l'espace, l'extrait une fois
vers un candidat SQLite, valide la base puis la bascule atomiquement. Il n'y a aucune décompression
pendant le map matching.

```bash
build/venv-osm/bin/python tools/osm-dataset/publish_dataset.py \
  build/osm/FR-COR.sqlite \
  build/public-osm \
  --version 2026.08.09 \
  --region-name Corse \
  --base-url https://road-data.example.invalid/ \
  --compare-compression
```

La publication produit :

- `packages/FR-COR-2026.08.09.sqlite.gz`;
- `regions/FR-COR/manifest.json`;
- `catalog.json`, contenant uniquement les régions effectivement publiées;
- `reports/FR-COR-2026.08.09.json`;
- éventuellement un diff gzip avec `--previous` et `--previous-version`.

`sizeBytes` et `checksumSha256` décrivent exactement le fichier gzip téléchargé.
`installedSizeBytes` décrit le SQLite après extraction. `publish_dataset.py` refuse une base URL
Geofabrik et vérifie récursivement que le catalogue généré n'en contient aucune.

## Mesure réelle — Corse

Mesure du 9 août 2026 sur `corse-latest.osm.pbf`, avec le même environnement et le schéma v2 :

| Mesure | Résultat |
|---|---:|
| PBF source | 34 034 546 octets |
| Objets OSM totaux | 4 796 856 |
| Ways `highway` | 77 072 |
| Ways automobiles conservés | 29 697 |
| Ways highway filtrés | 47 375 |
| Arêtes dirigées highway source | 2 350 981 |
| Arêtes filtrées | 1 521 394 |
| Segments mobiles finaux | 86 041 |
| Arêtes géométriques fusionnées | 743 546 |
| SQLite brut | 25 194 496 octets |
| Package gzip niveau 9 | 10 473 649 octets |
| Comparaison zstd niveau 19 | 8 834 383 octets |
| Réduction gzip par rapport au PBF | 69,23 % |
| Temps de génération | 42,693 s |
| Temps gzip | 2,430 s |
| Temps zstd | 9,572 s |

L'ancien schéma non fusionné produisait 1 565 833 segments, un SQLite de 376 852 480 octets et un
gzip de 102 162 605 octets sur le même PBF. Le v2 réduit donc le SQLite de 93,31 % et le
téléchargement gzip de 89,75 % par rapport à cet ancien pipeline.

Zstd économise encore 1,64 MB par rapport à gzip, mais demande ici plus de quatre fois le temps de
compression et nécessiterait un décodeur supplémentaire côté Android. Gzip est donc retenu pour le
transport; zstd reste mesuré par le pipeline, sans dépendance mobile.

Les artefacts et rapports de cette mesure sont dans `build/osm` et `build/public-osm-test`; ils sont
des sorties reproductibles ignorées par Git, pas des sources applicatives. Le rapport synthétique
durable est conservé dans `reports/FR-COR-2026.08.09.json`.

La même mesure Île-de-France reste à exécuter en CI : le transfert du PBF dense a été interrompu
après une estimation supérieure à 90 minutes dans l'environnement courant. Aucun résultat partiel
n'est présenté comme une mesure. La commande et la source `FR-IDF` sont déjà prises en charge.

## Tests ciblés

```bash
cd tools/osm-dataset
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest \
  test_dataset_schema.py test_build_dataset.py test_dataset_diff.py test_publish_dataset.py
```

Ils couvrent les highways autorisés/interdits, les restrictions moteur, les limites directionnelles,
la connectivité aux jonctions, le schéma/RTree, les diffs, les tailles/checksums du package final et
l'interdiction des URLs Geofabrik dans le catalogue mobile.
