Ne récupérer que les communes de plus de 2 000 habitants avec cartiflette
Ce tutoriel illustre un besoin fréquent : ne garder qu’une partie des géométries, soit selon leurs attributs, soit par des filtres géographiques.
AstucePourquoi filtrer avec
cartiflette ?
- Moins de données téléchargées :
cartiflettene rapatrie que les parties du fichier publié utiles à la requête, et le filtre est appliqué pendant la lecture ; - Une seule ligne de code : la condition s’ajoute aux paramètres habituels de
carti_download; - Le même filtre partout : l’API de
cartifletteaccepte le même paramètrewhere, utilisable depuisR,JavaScriptou un navigateur.
Avec cartiflette, ce filtre se fait au moment du téléchargement, grâce à l’argument where, une condition écrite en SQL. Seules les communes retenues sont récupérées, avec tous leurs attributs, prêtes à être cartographiées :
Commençons d’abord par ne garder que les communes de plus de 2 000 habitants de la région Occitanie. Cela nous donnera, in fine, cette carte:
Nous verrons ensuite comment récupérer des données par le biais de requêtes géographiques.
1 Récupérer toute la région, la démarche habituelle
La démarche habituelle consiste à demander les communes (borders="COMMUNE") d’une région (filter_by="REGION"), identifiée par son code Insee : 76 pour l’Occitanie.
from cartiflette import carti_download
occitanie = carti_download(
values="76",
borders="COMMUNE",
filter_by="REGION",
year=2026,
)On obtient les 4 446 communes de la région, avec leur population (POPULATION), leur département, leur bassin de vie, etc.
| INSEE_COM | NOM | POPULATION | LIBELLE_DEPARTEMENT | |
|---|---|---|---|---|
| 0 | 09007 | Allières | 76 | Ariège |
| 1 | 09009 | Alzen | 262 | Ariège |
| 2 | 09013 | Arabaux | 79 | Ariège |
NoteLa même carte en
Python
ax = occitanie.plot(color="#c6d7e8", edgecolor="white", linewidth=0.1)
occitanie.dissolve("INSEE_DEP").boundary.plot(ax=ax, color="black", linewidth=0.4)
ax.set_axis_off()
Il serait possible de ne garder les communes de plus de 2 000 habitants qu’ensuite, avec Pandas (occitanie.loc[occitanie["POPULATION"] > 2000]). Mais il aurait fallu télécharger toutes les communes, et la plupart d’entre elles sont aussitôt écartées.
2 Ne récupérer que les communes de plus de 2 000 habitants
L’argument where applique ce filtre dès la lecture :
grandes_communes = carti_download(
values="76",
borders="COMMUNE",
filter_by="REGION",
year=2026,
where="POPULATION > 2000",
)- 1
-
Une condition SQL, comme dans une clause
WHERE, sur n’importe quelle colonne :"POPULATION > 2000 AND INSEE_DEP = '31'","NOM ILIKE 'saint%'", etc.
Il ne reste que 542 communes, qui regroupent 73 % de la population de la région.
| INSEE_COM | NOM | POPULATION | |
|---|---|---|---|
| 251 | 31555 | Toulouse | 514819 |
| 353 | 34172 | Montpellier | 310240 |
| 115 | 30189 | Nîmes | 151839 |
| 446 | 66136 | Perpignan | 121616 |
| 292 | 34032 | Béziers | 81545 |
3 Faire la carte
Les communes de toute la région, récupérées à la première étape, servent de fond de carte, et les limites des départements s’en déduisent. Les communes de plus de 2 000 habitants sont colorées selon leur population, sur une échelle logarithmique : Toulouse compte 250 fois plus d’habitants que les plus petites d’entre elles.
NoteLa même carte en
Python
import matplotlib.pyplot as plt
departements = occitanie.dissolve("INSEE_DEP")
fig, ax = plt.subplots(figsize=(8, 7))
occitanie.plot(ax=ax, color="#eeeeee", edgecolor="white", linewidth=0.1)
grandes_communes.plot(
ax=ax,
column="POPULATION",
scheme="NaturalBreaks",
k=5,
cmap="YlOrRd",
edgecolor="white",
linewidth=0.1,
legend=True,
legend_kwds={
"title": "Population",
"loc": "upper center",
"bbox_to_anchor": (0.5, 0.02),
"ncol": 3,
"fmt": "{:.0f}",
"frameon": False,
},
)
departements.boundary.plot(ax=ax, color="black", linewidth=0.4)
ax.set_axis_off()
ax.set_title("Communes de plus de 2 000 habitants en Occitanie")
plt.figtext(
0.5,
0.02,
"Source : IGN, ADMIN EXPRESS COG CARTO 2026",
horizontalalignment="center",
fontsize=8,
style="italic",
)
plt.show()
AstuceComment faire avec
Javascript ?
Les cartes de cette page sont faites avec Observable Plot, par le biais de Quarto. Le code source de cette page est disponible ici.
Les données passent de Python à Javascript avec la fonction ojs_define. Elle ne sait pas transmettre un GeoDataFrame, mais elle transmet un dictionnaire : celui d’un GeoJSON, ou d’un TopoJSON pour la région entière. Ce dernier format ne décrit qu’une fois chaque frontière entre deux communes : il pèse deux fois moins lourd, et il permet de tracer les limites des départements sans autre fichier.
import json
import shapely
import topojson as tp
colonnes = ["INSEE_COM", "NOM", "POPULATION", "INSEE_DEP", "LIBELLE_DEPARTEMENT", "geometry"]
def pour_javascript(gdf):
"""Polygones orientés comme l'attend Javascript (d3), seulement les colonnes utiles."""
gdf = gdf[colonnes].copy()
gdf["geometry"] = shapely.orient_polygons(gdf.geometry.values, exterior_cw=True)
return gdf
ojs_define(
occitanie_topo=json.loads(
tp.Topology(pour_javascript(occitanie), prequantize=1e5, toposimplify=False).to_json()
),
grandes_communes_geo=json.loads(pour_javascript(grandes_communes).to_json(drop_id=True)),
)- 1
-
Le contour extérieur des polygones est décrit dans le sens inverse des aiguilles d’une montre, comme le demande la norme GeoJSON.
d3, sur lequel reposeObservable Plot, attend le sens inverse : sans cette ligne, chaque commune serait remplie comme « tout le globe sauf la commune ».
Côté Javascript, on reconstitue les communes et les limites des départements à partir du TopoJSON. La conversion en TopoJSON inverse le sens de quelques communes en plusieurs morceaux : redresser remet à l’endroit tout polygone qui couvrirait plus d’un hémisphère. La projection est une conique conforme proche du Lambert 93, la projection officielle de la France métropolitaine :
function redresser(collection) {
const polygone = (anneaux) =>
d3.geoArea({type: "Polygon", coordinates: anneaux}) > 2 * Math.PI
? anneaux.map((anneau) => anneau.slice().reverse())
: anneaux
for (const d of collection.features) {
const g = d.geometry
if (g.type === "Polygon") g.coordinates = polygone(g.coordinates)
if (g.type === "MultiPolygon") g.coordinates = g.coordinates.map(polygone)
}
return collection
}
communes = redresser(topojson.feature(occitanie_topo, occitanie_topo.objects.data))
limites_departements = topojson.mesh(
occitanie_topo, occitanie_topo.objects.data,
(a, b) => a.properties.INSEE_DEP !== b.properties.INSEE_DEP
)
contour_region = topojson.mesh(
occitanie_topo, occitanie_topo.objects.data, (a, b) => a === b
)
projection = ({type: "conic-conformal", parallels: [44, 49], rotate: [-3, 0], domain: communes})
infobulle = (d) => `${d.properties.NOM} (${d.properties.LIBELLE_DEPARTEMENT})
${d.properties.POPULATION.toLocaleString("fr-FR")} habitants`La carte de toute la région montre chaque commune, avec les limites des départements :
function carteOccitanie() {
return Plot.plot({
projection,
width: 640,
marks: [
Plot.geo(communes, Plot.centroid({
fill: "#c6d7e8",
stroke: "white",
strokeWidth: 0.2,
title: infobulle,
tip: true
})),
Plot.geo(limites_departements, {stroke: "black", strokeWidth: 0.5}),
Plot.geo(contour_region, {stroke: "black", strokeWidth: 0.8})
]
})
}La carte des communes de plus de 2 000 habitants les superpose au fond de carte de la région :
function carteGrandesCommunes() {
return Plot.plot({
projection,
width: 640,
color: {
type: "log",
scheme: "ylorrd",
legend: true,
label: "Population",
ticks: [2000, 5000, 20000, 100000, 500000],
tickFormat: (d) => d.toLocaleString("fr-FR"),
marginLeft: 16,
width: 360
},
marks: [
Plot.geo(communes, {fill: "#eeeeee", stroke: "white", strokeWidth: 0.2}),
Plot.geo(grandes_communes_geo, Plot.centroid({
fill: (d) => d.properties.POPULATION,
stroke: "white",
strokeWidth: 0.2,
title: infobulle,
tip: true
})),
Plot.geo(limites_departements, {stroke: "black", strokeWidth: 0.5}),
Plot.geo(contour_region, {stroke: "black", strokeWidth: 0.8})
]
})
}4 Aller plus loin : filtrer selon la géométrie
where accepte aussi les fonctions spatiales de DuckDB, sur la colonne geometry (coordonnées en longitude, latitude). Par exemple, les communes qui touchent un rectangle, ici la Camargue, à cheval sur l’Occitanie et la Provence-Alpes-Côte d’Azur :
camargue = carti_download(
values="France",
borders="COMMUNE",
filter_by="FRANCE_ENTIERE",
year=2026,
where="ST_Intersects(geometry, ST_MakeEnvelope(4.1, 43.3, 4.9, 43.75))",
)- 1
-
ST_MakeEnvelope(longitude min, latitude min, longitude max, latitude max). Les communes à cheval sur le bord du rectangle sont gardées entières.
NoteLa même carte en
Python
import geopandas as gpd
from shapely.geometry import box
ax = camargue.plot(
column="LIBELLE_DEPARTEMENT",
categorical=True,
edgecolor="white",
linewidth=0.2,
legend=True,
legend_kwds={"loc": "upper left", "bbox_to_anchor": (1, 1), "frameon": False},
)
gpd.GeoSeries([box(4.1, 43.3, 4.9, 43.75)]).boundary.plot(
ax=ax, color="black", linestyle="--", linewidth=1
)
ax.set_axis_off()- 1
- Le rectangle du filtre, en pointillés.

AstuceComment faire avec
Javascript ?
Les communes de la Camargue sont transmises comme celles de l’Occitanie :
ojs_define(camargue_geo=json.loads(pour_javascript(camargue).to_json(drop_id=True)))Le rectangle du filtre est tracé comme une ligne (LineString) plutôt que comme un polygone, ce qui évite la question du sens de parcours :
rectangle = ({
type: "LineString",
coordinates: [[4.1, 43.3], [4.9, 43.3], [4.9, 43.75], [4.1, 43.75], [4.1, 43.3]]
})
function carteCamargue() {
return Plot.plot({
projection: {
type: "conic-conformal", parallels: [44, 49], rotate: [-3, 0],
domain: {type: "GeometryCollection", geometries: [rectangle, ...camargue_geo.features.map((d) => d.geometry)]}
},
width: 640,
color: {legend: true, scheme: "set2"},
marks: [
Plot.geo(camargue_geo, Plot.centroid({
fill: (d) => d.properties.LIBELLE_DEPARTEMENT,
stroke: "white",
strokeWidth: 0.4,
title: infobulle,
tip: true
})),
Plot.geo(rectangle, {stroke: "black", strokeDasharray: "4,3", strokeWidth: 1.2})
]
})
}D’autres exemples (distance à un point, commune contenant un point) sont dans la documentation du client Python.