Regions, departments, communes and postal codes of France, with one GPS point per postal code and the history of changes.
Dataset of the regions, departments, communes and postal codes of France (metropolitan France, DROM and COM), with one GPS point per postal code and the history of changes (mergers, code changes, creations, deletions).
It is built from official open sources, updated automatically, and designed so that an application can attach its addresses to it through a foreign key.
| Table | Content |
|---|---|
regions |
The regions (code, name). |
departments |
The departments and the overseas collectivities (code, name, region). |
communes |
The communes and the municipal arrondissements, with their INSEE code, their department and the centre of the commune. |
cities |
One row per commune and per postal code, for example "37200 Tours", with its GPS point. |
commune_successions |
For an old INSEE code, the code that succeeds it, the nature of the change and its date. |
reference_changes |
The changes detected between two updates (regions, departments, postal codes). |
Every table carries a validity period (valid_from, valid_to): a row is never deleted, its validity is closed. Identifiers are therefore stable over time.
regions ──< departments ──< communes ──< cities <── addresses (table of your application)
Tours (37261) has three postal codes, and each one has its own point:
| Postal code | Latitude | Longitude | Addresses used |
|---|---|---|---|
| 37000 | 47.3858 | 0.6886 | 18,746 |
| 37100 | 47.4164 | 0.6930 | 10,489 |
| 37200 | 47.3661 | 0.7044 | 1,007 |
The point of a postal code is the median of the address positions of the Base Adresse Nationale (BAN) for that postal code in that commune. The coordinate_source column always tells where the point comes from:
ban: median of the BAN addresses (the vast majority of cities).nominatim: search of the postal code and the commune on Nominatim, for cities with no address in the BAN.commune_centre: centre of the commune, as the last fallback.
address_count gives the number of addresses used: a postal code with very few addresses is less reliable.
| Source | Usage | Licence |
|---|---|---|
| INSEE, Code officiel géographique | Regions, departments, communes, history since 1943 and commune events. | Licence Ouverte |
| La Poste, base officielle des codes postaux | Link between a commune and its postal codes. | Licence Ouverte |
| geo.api.gouv.fr | Centre of each commune. | Licence Ouverte |
| Base Adresse Nationale | GPS point of each postal code. | Licence Ouverte |
| Nominatim (OpenStreetMap) | Fallback for cities with no address in the BAN. | ODbL |
An application stores its addresses with a foreign key to cities:
$city = City::current()
->where('postal_code', '37200')
->whereRelation('commune', 'insee_code', '37261')
->firstOrFail();
$address = Address::create([
'line' => '3 rue Jules Massenet',
'city_id' => $city->id,
]);
$address->city
->commune
->department
->region
->name; // Centre-Val de LoireFor the addresses.city_id foreign key to work, the cities table must live in the same database as addresses.
When a commune merges or changes its code, its old codes keep pointing to it through commune_successions.
Example: Saint-Florent-des-Bois merged in 2016 into "Rives de l'Yon", which took over the code 85213, while Chaillé-sous-les-Ormeaux (85043) was absorbed.
make resolve CODE=85043 POSTAL_CODE=85310
# 2016-01-01 absorbed: 85043 -> 85213
# 2016-01-01 code_reused: 85213 -> 85213
# Current communes: 85213
# city #33125 85310 RIVES DE L YON (46.590263, -1.333875)The result follows succession chains (A to B to C), reports a commune that disappeared without a successor, lists every successor of a split commune and warns when the original postal code is no longer in use. The same information is in the exported files (commune_successions, and replaced_by_city_id in cities) so you can migrate your data without going through this repository.
- For communes, departments and regions, the history goes back to 1943 (the overseas COM have none before the first import).
- INSEE publishes no event for postal codes: their tracking relies on the comparison between two updates and starts at the first import kept.
Requirements: Docker and Make.
make start # starts PHP 8.4, MySQL 8.4, Redis and Horizon, installs the dependencies, migrates the database
make update # updates the dataset (official files, then GPS points), on the queue
make update-sync # same, in this terminal with a progress bar
make update-force # same, importing every file again even if it did not change
make status # state of the dataset and of the last update
make export # exports to CSV, JSON and SQL in storage/app/exports
make build-dataset # update then export in one go (build-dataset-force imports every file again)
make stop # stops the project and removes its containers and volumesmake update downloads the official files and only re-imports the ones that changed. The GPS points are computed by parallel jobs, one per department, then a check verifies that every city has a point, that every department was processed and that no job failed. A full update takes about 15 minutes.
The matching php artisan commands:
dataset:update(--sync,--force,--skip-coordinates)dataset:statusdataset:resolvedataset:export.
The exports are attached to the releases of the repository and published on data.gouv.fr. They contain one file per table, in CSV, JSON and SQL.
make quality # Pint, PHPStan (max level), Rector, PHPInsights, Markdownlint
make test # PHPUnitThe repository uses zairakai/laravel-dev-tools (quality tools, git hooks, Makefile).
Commits follow Conventional Commits with the ticket number (type(scope): #123 subject) and the repository only accepts merge commits on rebased branches.
The architecture is described in docs/architecture.md and how to publish a version in docs/release.md.
See also CONTRIBUTING.md, SECURITY.md and the code of conduct.
MIT for the code. The data remain subject to the licences of their sources (see above).