diff --git a/content/git/exogit.qmd b/content/git/exogit.qmd
index d3b3a5a91..ae92623aa 100644
--- a/content/git/exogit.qmd
+++ b/content/git/exogit.qmd
@@ -145,11 +145,11 @@ les fonctionalités coeur de ces deux interfaces qui sont en fait quasi-identiqu
Les deux premières étapes se font sur `Github`.
-
-
-{{% box status="exercise" title="Exercice" icon="fab fa-github" %}}
-
-**Exercice 1 : Créer un compte Github**
+::: {.cell .markdown}
+```{=html}
+
-
Exercice 2: Les projections, représentations et approximations
+
Exercice 2 : Les projections, représentations et approximations
```
Voici un code utilisant encore
@@ -362,6 +368,7 @@ download_vectorfile_url_all(
```{python}
#| output: false
+# Question 1 : Tester différentes projections
france_2154 = france.to_crs(2154)
france_healpix = france.to_crs("+proj=healpix +lon_0=0 +a=1")
france_5070 = france.to_crs(5070)
@@ -395,6 +402,7 @@ ax4.set_axis_off()
```
```{python}
+# Question 2
france = france.to_crs(3395)
france["superficie_4326"] = france.area
france = france.to_crs(2154)
@@ -492,7 +500,7 @@ url = "https://opendata.paris.fr/explore/dataset/velib-emplacement-des-stations/
Dans le prochain exercice, nous proposons de créer rapidement une
-carte comprenant trois couches:
+carte comprenant trois couches :
- Les localisations de stations sous forme de points ;
- Les bordures des communes et arrondissements pour contextualiser ;
@@ -520,7 +528,7 @@ idf = download_vectorfile_url_all(
source="EXPRESS-COG-CARTO-TERRITOIRE",
year=2022)
-petite_couronne_departements = idf.loc[idf['INSEE_DEP'].isin(["75","92","93","94"])]
+petite_couronne_departements = idf.loc[idf['INSEE_DEP'].isin(["75","92","93","94"])].to_crs(2154)
```
::: {.cell .markdown}
@@ -588,6 +596,7 @@ La couche de base obtenue à l'issue de la question 4.
```{python}
+# 4. petite couronne
base = petite_couronne.boundary.plot(edgecolor = "black", linewidth = 0.5)
base
```
@@ -601,6 +610,7 @@ Puis en y ajoutant les limites départementales (question 5).
```{python}
+# 5. Ajout de la couche des départements
base = petite_couronne.boundary.plot(edgecolor = "black", linewidth = 0.5)
petite_couronne_departements.boundary.plot(ax = base, edgecolor = "blue", linewidth = 0.7)
base
@@ -615,6 +625,7 @@ Puis les stations (question 6).
```{python}
+# 6. Ajout des stations
base = petite_couronne.boundary.plot(edgecolor = "black", linewidth = 0.5)
petite_couronne_departements.boundary.plot(ax = base, edgecolor = "blue", linewidth = 0.7)
principales_stations.plot(ax= base, column = "capacity", markersize = "capacity", color = "red", alpha = 0.4)
@@ -626,6 +637,7 @@ La carte finale, après mise en forme:
```{python}
+#7. sans axe et avec titre
base = petite_couronne.boundary.plot(edgecolor = "black", linewidth = 0.5)
petite_couronne_departements.boundary.plot(ax = base, edgecolor = "blue", linewidth = 0.7)
principales_stations.plot(ax= base, column = "capacity", markersize = "capacity", color = "red", alpha = 0.4)
@@ -657,9 +669,9 @@ sont stockées dans un _dataframe_ nommé `stations`
- les données administratives
sont dans un _dataframe_ nommé `petite_couronne`.
-1. Faire une jointure spatiale pour enrichir les données de stations en y ajoutant des informations de `petite_couronne`. Appeler cet objet `stations_info`
+1. Faire une jointure spatiale pour enrichir les données de stations en y ajoutant des informations de `petite_couronne`. Appeler cet objet `stations_info`.
2. Créer les objets `stations_19e` et `arrondissement_19e` pour stocker, respectivement,
-les stations appartenant au 19e et les limites de l'arrondissement
+les stations appartenant au 19e et les limites de l'arrondissement.
2. Représenter la carte des stations du 19e arrondissement avec le code suivant:
```python
@@ -682,18 +694,17 @@ base
```{python}
#1. Jointure spatiale entre stations et data_paris
-stations = stations.to_crs(2154)
-petite_couronne = petite_couronne.to_crs(2154)
-
stations_info = gpd.sjoin(stations, petite_couronne, predicate = 'within')
```
```{python}
+#2. 19e arrondissement
stations_19 = stations_info.loc[stations_info['NOM'].str.contains("19e")]
arrondissement_19e = petite_couronne.loc[petite_couronne['NOM'].str.contains("19e")]
```
```{python}
+# 3. Carto du 19e
base = petite_couronne.loc[petite_couronne['INSEE_DEP']=="75"].boundary.plot(edgecolor = "k", linewidth=0.5)
arrondissement_19e.boundary.plot(ax = base, edgecolor = "red", linewidth=0.9)
stations_19.plot(ax = base, color = "red", alpha = 0.4)
@@ -703,7 +714,11 @@ base
```
+
+Carte obtenue à la question 4 :
+
```{python}
+#4. Calcul et carte des capacity
stations_agg = (
stations_info
.groupby("INSEE_COG")
@@ -711,18 +726,10 @@ stations_agg = (
.reset_index()
)
-petite_couronne['area'] = petite_couronne.area
petite_couronne_count = petite_couronne.merge(
stations_agg
).to_crs(2154)
-petite_couronne_count['density'] = petite_couronne_count['area'].div(1e6)
-petite_couronne_count['density'] = petite_couronne_count['capacity']*petite_couronne_count['density']
-```
-
-Carte obtenue à la question 3:
-
-```{python}
petite_couronne_count = petite_couronne_count.loc[petite_couronne_count["INSEE_DEP"]== "75"]
aplat = petite_couronne_count.plot(
column = "capacity", cmap="coolwarm", legend=True)
@@ -730,19 +737,25 @@ aplat.set_axis_off()
aplat
```
-Avec la carte de la question 3, basée sur des aplats de couleurs (choropleth map), le lecteur est victime d’une illusion classique. Les arrondissements les plus visibles sur la carte sont les plus grands. D’ailleurs c’est assez logique qu’ils soient également mieux pourvus en velib. Même si l’offre de velib est probablement plus reliée à la densité de population et d’équipements, on peut penser que l’effet taille joue et qu’ainsi on est victime d’une illusion avec la carte précédente.
+Avec la carte de la question 4, basée sur des aplats de couleurs (choropleth map), le lecteur est victime d’une illusion classique. Les arrondissements les plus visibles sur la carte sont les plus grands. D’ailleurs c’est assez logique qu’ils soient également mieux pourvus en velib. Même si l’offre de velib est probablement plus reliée à la densité de population et d’équipements, on peut penser que l’effet taille joue et qu’ainsi on est victime d’une illusion avec la carte précédente.
-Si on représente plutôt la capacité sous forme de densité, pour tenir compte de la taille différente des arrondissements, les conclusions sont inversées et correspondent mieux aux attentes d’un modèle centr-périphérie. Les arrondissements centraux sont mieux pourvus, cela se voit encore mieux avec des ronds proportionnels plutôt qu’une carte chorolèpthe.
-
+Si on représente plutôt la capacité sous forme de densité, pour tenir compte de la taille différente des arrondissements, les conclusions sont inversées et correspondent mieux aux attentes d’un modèle centre-périphérie. Les arrondissements centraux sont mieux pourvus, cela se voit encore mieux avec des ronds proportionnels plutôt qu’une carte chorolèpthe.
```{python}
+#5. Calcul et carte des area et density
+petite_couronne_count['area'] = petite_couronne_count.area
+
+petite_couronne_count['density'] = petite_couronne_count['area'].div(1e6)
+petite_couronne_count['density'] = petite_couronne_count['capacity']*petite_couronne_count['density']
+
aplat = petite_couronne_count.plot(
column = "density", cmap="coolwarm", legend=True)
aplat.set_axis_off()
aplat
```
+
## Exercice supplémentaire
Les exercices précédents ont permis de se familiariser au traitement de données
diff --git a/content/manipulation/03_geopandas_tutorial.qmd b/content/manipulation/03_geopandas_tutorial.qmd
index 7ff9af794..a598b3f71 100644
--- a/content/manipulation/03_geopandas_tutorial.qmd
+++ b/content/manipulation/03_geopandas_tutorial.qmd
@@ -1,5 +1,5 @@
---
-title: "Données spatiales: découverte de geopandas"
+title: "Données spatiales : découverte de geopandas"
date: 2023-07-09T13:00:00Z
draft: false
weight: 40
@@ -21,7 +21,7 @@ description: |
étend les fonctionalités de l'écosystème `Pandas` afin de permettre
de manipuler des données géographiques complexes de manière simple.
image: featured_geopandas_tutorial.png
-eval: false
+eval: true
---
::: {.cell .markdown}
@@ -61,6 +61,7 @@ Il peut servir de pendant à celui-ci pour l'utilisateur de `R`.
Quelques installations préalables sont nécessaires :
```python
+#| output: false
!pip install pandas fiona shapely pyproj rtree # à faire obligatoirement en premier pour utiliser rtree ou pygeos pour les jointures spatiales
!pip install contextily
!pip install geopandas
@@ -76,13 +77,16 @@ Pour être en mesure d'exécuter ce tutoriel, les imports suivants
seront utiles.
```{python}
+#| output: false
import geopandas as gpd
import contextily as ctx
import matplotlib.pyplot as plt
```
-## Données spatiales: quelle différence avec des données traditionnelles ?
+## Données spatiales
+
+### Quelle différence avec des données traditionnelles ?
**Le terme "données spatiales" désigne les données qui portent sur les caractéristiques géographiques des objets (localisation, contours, liens)**.
Les caractéristiques géographiques des objets sont décrites à l'aide d'un **système de coordonnées**
@@ -93,7 +97,7 @@ de données spatiales :
* Une table décrivant des bâtiments, avec les coordonnées géographiques de chaque bâtiment;
* Le découpage communal du territoire, avec le contour du territoire de chaque commune;
-* Les routes terrestres, avec les coordonnées décrivant leur parcours.
+* Les routes terrestres, avec les coordonnées décrivant leur parcours en 3 dimensions (longitude, latitude, altitude).
Les données spatiales rassemblent classiquement deux types de données :
@@ -102,20 +106,14 @@ Les données spatiales rassemblent classiquement deux types de données :
**Les données spatiales sont fréquemment traitées à l'aide d'un système d'information géographique (SIG)**, c'est-à-dire un système d'information capable de stocker, d'organiser et de présenter des données alphanumériques spatialement référencées par des coordonnées dans un système de référence (CRS). `Python` dispose de fonctionnalités lui permettant de réaliser les mêmes tâches qu'un SIG (traitement de données spatiales, représentations cartographiques).
-**Les systèmes de projection font l'objet de standards internationaux et sont souvent désignés par des codes dits codes EPSG**. Ce [site](https://epsg.io/) est un bon aide-mémoire. Les plus fréquents, pour les utilisateurs français, sont les suivants (plus d'infos [ici](https://geodesie.ign.fr/contenu/fichiers/documentation/SRCfrance.pdf)):
-
-* `2154`: système de projection Lambert 93. Il s'agit du système de projection officiel. La plupart des données diffusées par l'administration pour la métropole sont disponibles dans ce système de projection.
-* `27572`: Lambert II étendu. Il s'agit de l'ancien système de projection officiel. Les données spatiales anciennes peuvent être dans ce format.
-* `4326`: WGS 84 ou système de pseudo-Mercator. Attention, ce n'est en réalité pas un système de projection mais un système de coordonnées (longitude / latitude) qui permet simplement un repérage angulaire sur l'ellipsoïde. Il est utilisé pour les données GPS.
-
-### De `pandas` à `geopandas`
+### De `Pandas` à `Geopandas`
-Le *package* `geopandas` est une boîte à outils conçue pour faciliter la manipulation de données spatiales. **La grande force de `geopandas` est qu'il permet de manipuler des données spatiales comme s'il s'agissait de données traditionnelles**, car il repose sur le standard ISO 19125 [*simple feature access*](https://en.wikipedia.org/wiki/Simple_Features) défini conjointement par l'*Open Geospatial Consortium (OGC)* et l'*International Organization for Standardization (ISO)*.
+Le *package* `Geopandas` est une boîte à outils conçue pour faciliter la manipulation de données spatiales. **La grande force de `Geopandas` est qu'il permet de manipuler des données spatiales comme s'il s'agissait de données traditionnelles**, car il repose sur le standard ISO 19125 [*simple feature access*](https://en.wikipedia.org/wiki/Simple_Features) défini conjointement par l'*Open Geospatial Consortium (OGC)* et l'*International Organization for Standardization (ISO)*.
-Par rapport à un DataFrame standard, un objet `geopandas` comporte
+Par rapport à un DataFrame standard, un objet `Geopandas` comporte
une colonne supplémentaire: `geometry`. Elle stocke les coordonnées des
-objets géographiques (ou ensemble de coordonnées s'agissant de contours). Un objet `geopandas` hérite des propriétés d'un
-DataFrame pandas mais propose des méthodes adaptées au traitement des données spatiales.
+objets géographiques (ou ensemble de coordonnées s'agissant de contours). Un objet `Geopandas` hérite des propriétés d'un
+DataFrame `Pandas` mais propose des méthodes adaptées au traitement des données spatiales.
Ainsi, grâce à `Geopandas`, on pourra effectuer des manipulations sur les attributs des données comme avec `pandas` mais on pourra également faire des manipulations sur la dimension spatiale des données. En particulier,
@@ -143,16 +141,234 @@ il peut être préférable d’effectuer les opérations sur les données avant
Par rapport à un logiciel spécialisé comme `QGIS`, `Python` permettra
d'automatiser le traitement et la représentation des données. D'ailleurs,
-`QGIS` utilise lui-même `python`...
+`QGIS` utilise lui-même `Python`...
+### Résumé
-## Importer des données spatiales
+En résumé, un objet `GeoPandas` comporte les éléments suivantes:
+
+![](https://rgeo.linogaliana.fr/slides/img/sf.png)
+
+1. Les __attributs__. Ce sont les valeurs associées à chaque niveau géographique.
+Il s'agit de la dimension tabulaire usuelle, dont le traitement est similaire
+à celui d'un objet `Pandas` classique.
+2. Les __géométries__. Ce sont les valeurs numériques interprétées pour représenter la dimension géographique. Elles permettent de représenter dans un certain
+référentiel (le système de référence) la dimension géographique.
+3. Le __système de référence__. Il s'agit du système permettant de transformer les positions sur
+le globe (3 dimensions avec une boule asymétrique) en un plan en deux dimensions.
+Il en existe une multitude, identifiables à partir d'un code EPSG (4326, 2154...).
+Leur manipulation est facilitée par `Geopandas` qui s'appuie sur `Shapely`, de la même
+manière que `Pandas` s'appuie sur `Numpy` ou `Arrow`.
-Les données spatiales sont plus riches que les données traditionnelles car elles
+## Le système de projection cartographique
+
+### Principe
+
+Les données spatiales sont
+plus riches que les données traditionnelles car elles
incluent, habituellement, des éléments supplémentaires pour placer dans
un espace cartésien les objets. Cette dimension supplémentaire peut être simple
(un point comporte deux informations supplémentaire: $x$ et $y$) ou
-assez complexe (polygones, lignes avec direction, etc.)
+assez complexe (polygones, lignes avec direction, etc.).
+
+L'analyse cartographique emprunte dès lors à la géométrie
+des concepts
+pour représenter des objets dans l'espace. Les __projections__
+sont au coeur de la gestion des données spatiales.
+Ces dernières consistent à transformer une position dans l'espace
+terrestre à une position sur un plan. Il s'agit donc d'une opération
+de projection d'un espace tri-dimensionnel dans un espace
+à deux dimensions.
+Ce [post](https://www.earthdatascience.org/courses/earth-analytics/spatial-data-r/geographic-vs-projected-coordinate-reference-systems-UTM/) propose de riches éléments sur le
+sujet, notamment l'image suivante qui montre bien le principe d'une projection :
+
+![Les différents types de projection](https://www.earthdatascience.org/images/courses/earth-analytics/spatial-data/spatial-projection-transformations-crs.png)
+
+
+Cette opération n'est pas neutre. L'une des conséquences du
+[théorème remarquable de Gauss](https://fr.wikipedia.org/wiki/Theorema_egregium)
+est que la surface de la Terre ne peut être cartographiée sans distortion.
+Une projection ne peut simultanément conserver intactes les distances et les
+angles (i.e. les positions).
+Il n'existe ainsi pas de projection universellement meilleure, ce qui ouvre
+la porte à la coexistence de nombreuses projections différentes, pensées
+pour des tâches différentes.
+Un mauvais système de représentation
+fausse l'appréciation visuelle mais peut aussi entraîner des erreurs dans
+les calculs sur la dimension spatiale.
+
+**Les systèmes de projection font l'objet de standards internationaux et sont souvent désignés par des codes dits codes EPSG**. Ce [site](https://epsg.io/) est un bon aide-mémoire. Les plus fréquents, pour les utilisateurs français, sont les suivants (plus d'infos [ici](https://geodesie.ign.fr/contenu/fichiers/documentation/SRCfrance.pdf)):
+
+* `2154`: système de projection Lambert 93. Il s'agit du système de projection officiel. La plupart des données diffusées par l'administration pour la métropole sont disponibles dans ce système de projection.
+* `27572`: Lambert II étendu. Il s'agit de l'ancien système de projection officiel. Les données spatiales anciennes peuvent être dans ce format.
+* `4326`: WGS 84 ou système de pseudo-Mercator ou encore _Web Mercator_. Ce n'est en réalité pas un système de projection mais un système de coordonnées (longitude / latitude) qui permet simplement un repérage angulaire sur l'ellipsoïde. Il est utilisé pour les données GPS. Il s'agit du système le plus
+usuel, notamment quand on travaille avec des fonds de carte _web_.
+
+
+Comme évoqué plus haut, l'une des projections les plus connues est la
+projection _Web Mercator_ dite WGS84 (code EPSG 4326). Il
+s'agit d'une projection conservant intacte les angles, ce
+qui implique qu'elle altère les distances. Celle-ci a en effet été
+pensée, à l'origine, pour représenter l'hémisphère Nord. Plus
+on s'éloigne de celui-ci, plus les distances sont distordues. Cela
+amène à des distorsions bien
+connues (le Groenland hypertrophié, l'Afrique de taille réduite, l'Antarctique démesuré...).
+En revanche, la projection Mercator conserve intacte les positions.
+C'est cette propriété qui explique son utilisation dans les systèmes
+GPS et ainsi dans les fonds de carte de navigation du type _Google Maps_.
+
+![*Exemple de reprojection de pays depuis le site [thetruesize.com](https://thetruesize.com/)*](truesize.png)
+
+
+::: {.content-visible when-format="html"}
+
+Observez les variations significatives
+de proportions pour certains pays selon les projections
+choisies:
+
+```{ojs}
+//| echo: false
+html`
${container_projection}
`
+```
+
+{{< include "_play_with_projection.qmd" >}}
+
+```{ojs}
+//| echo: false
+width_projected_map = screen.width/2
+```
+
+:::
+
+Pour aller plus loin, la carte interactive
+suivante, construite par Nicolas Lambert, issue de
+ce [_notebook_ `Observable`](https://observablehq.com/@neocartocnrs/impact-of-projections-on-areas), illustre l'effet
+déformant de la projection Mercator, et de quelques unes autres,
+sur notre perception de la taille des pays.
+
+::: {.content-visible when-format="html"}
+
+
+
+Voir la carte interactive
+
+```{ojs}
+//| echo: false
+html`
+
${viewof projectionBertin}
+
${viewof mycountry}
+
${mapBertin}
+
`
+```
+
+
+
+```{ojs}
+//| echo: false
+import {map as mapBertin, viewof projection as projectionBertin, viewof mycountry} from "@neocartocnrs/impact-of-projections-on-areas"
+```
+
+:::
+
+
+Il existe en fait de nombreuses représentations possibles du monde, plus ou moins
+alambiquées. Les projections sont très nombreuses et certaines peuvent avoir une [forme suprenante](https://imgs.xkcd.com/comics/map_projections.png).
+Par exemple,
+la [projection de Spillhaus](https://storymaps.arcgis.com/stories/756bcae18d304a1eac140f19f4d5cb3d)
+propose de centrer la vue sur les océans et non une terre. C'est pour
+cette raison qu'on parle parfois de monde tel que vu par les poissons
+à son propos.
+
+::: {.content-visible when-format="html"}
+
+```{ojs}
+//| echo: false
+html`
${spilhaus}
`
+```
+
+
+```{ojs}
+//| echo: false
+//| fig-align: center
+spilhaus = {
+ const width = 600;
+ const height = width;
+
+ const context = DOM.context2d(width, height);
+ const projection = d3.geoStereographic()
+ .rotate([95, 45])
+ .translate([width / 2, height / 2])
+ .scale(width / 10.1)
+ .center([30, -5])
+ .clipAngle(166);
+ const path = d3.geoPath(projection, context);
+
+ const land = topojson.feature(world, world.objects.land);
+
+ context.lineJoin = "round";
+ context.lineCap = "round";
+ context.fillStyle = "#f2f1ed";
+ context.fillRect(0, 0, width, height);
+
+ context.beginPath();
+ path({type: "Sphere"});
+ path(land);
+ context.lineWidth = 0.5;
+ context.stroke();
+ context.clip("evenodd");
+
+ context.save();
+ context.beginPath();
+ path(land);
+ context.filter = "blur(12px)";
+ context.fillStyle = "#006994";
+ context.fill("evenodd");
+ context.restore();
+
+ context.beginPath();
+ path(d3.geoGraticule10());
+ context.globalAlpha = 0.2;
+ context.strokeStyle = "#000";
+ context.stroke();
+
+ return context.canvas;
+}
+```
+
+
+```{ojs}
+//| echo: false
+//import {map as spilhausmap} with {height, width} from "@d3/spilhaus-shoreline-map"
+import { world } from "@d3/spilhaus-shoreline-map"
+```
+
+:::
+
+::: {.cell .markdown}
+```{=html}
+
+
Astuce pour la France
+```
+
+Pour la France, dans le système WGS84 (4326) :
+
+- Longitude ($x$) tourne autour de 0° (de -5.2 à +9.6 pour être plus précis)
+- La latitude ($y$) autour de 45 (entre +41.3 à +51.1)
+
+Dans le système Lambert 93 (2154) :
+
+- Coordonnées $x$: entre 100 000 et 1 300 000
+- La latitude ($y$): entre 6 000 000 et 7 200 000
+
+[Plus de détails](https://medium.com/@_FrancoisM/introduction-%C3%A0-la-manipulation-de-donn%C3%A9es-cartographiques-23b4e38d8f0f)
+
+```{=html}
+
+```
+:::
+
+
+## Importer des données spatiales
Les formats les plus communs de données spatiales sont les suivants :
@@ -161,14 +377,16 @@ La table de données (attributs) est stockée dans un fichier séparé des
données spatiales. En faisant `geopandas.read_file("monfichier.shp")`, le
package fait lui-même le lien entre les observations et leur représentation spatiale ;
* _geopackage_ (`.gpkg`) : ce (relativement) nouveau format **libre** en un seul fichier également (lui recommandé par l'OGC) vise progressivement à se substituer au shapefile. Il est par exemple le format par défaut dans QGIS.
-* _geojson_ (`.json`) : ce format, non préconisé par l'OGC est largement utilisé pour le développement web
+* _geojson_ (`.json`) : ce format, non préconisé par l'OGC, est largement utilisé pour le développement _web_
comme dans la librairie `leaflet.js`.
La dimension spatiale est stockée dans le même fichier que les attributs.
Ces fichiers sont généralement beaucoup plus légers que les *shapefiles* mais possèdent des limites s'agissant de gros jeux de données.
+* _topojson_ (`.json`): une variante du `geojson` qui se développe progressivement pour assister les visualisations _web_. Au lieu de stocker l'ensemble des points permettant de représenter une
+géométrie, seuls les arcs sont conservés. Cela allège substantiellement le poids du fichier et
+permet, avec une librairie adaptée, de reconstruire l'ensemble des contours géographiques.
-Cette [page](https://si.ecrins-parcnational.com/blog/2020-02-geojson-shapefile-geopackage.html) compare plus en détail ces trois types de formats de données géographiques.
-
-L'aide de [geopandas](https://geopandas.org/io.html) propose des bouts de code en fonction des différentes situations dans lesquelles on se trouve.
+Cette [page](https://si.ecrins-parcnational.com/blog/2020-02-geojson-shapefile-geopackage.html) compare plus en détail les principes formats de données géographiques.
+L'aide de [`Geopandas`](https://geopandas.org/io.html) propose des bouts de code en fonction des différentes situations dans lesquelles on se trouve.
### Exemple: récupérer les découpages territoriaux
@@ -178,22 +396,14 @@ Celui-ci peut être récupéré de plusieurs manières.
En premier lieu, pour récupérer
le fond de carte officiel, produit par l'IGN, sous
le nom d'[`AdminExpress`](https://geoservices.ign.fr/adminexpress#telechargementCog)[^1],
-il est possible de se rendre sur le site de l'IGN et de le télécharger depuis
-le serveur `FTP`.
+il est possible de se rendre sur le site de l'IGN et de le télécharger.
Il est également possible d'utiliser l'une des [API de l'IGN](https://api.gouv.fr/les-api/api_carto_cadastre)
mais ces dernières ne sont pas encore très documentées pour des utilisateurs
de `Python`.
-Le package `pynsee` propose notamment un module dédié à la récupération de fonds de carte officiels pour valoriser des données
-d'open data. L'API sur laquelle il repose étant parfois lente, nous présentons le code dédié uniquement en annexe.
-
-
-[^1]: Il existe également une version moins officielle sur [data.gouv](https://www.data.gouv.fr/fr/datasets/decoupage-administratif-communal-francais-issu-d-openstreetmap/), construite à
-partir d'`OpenStreetMap`.
-
-
-Nous proposons ici une méthode nouvelle de récupération de
-ces données qui s'appuie sur le projet interministériel
-[`cartiflette`](https://www.10pourcent.etalab.gouv.fr/projets/cartiflette/).
+Le package [`cartiflette`](https://github.com/InseeFrLab/cartiflette.git), issu
+d'un projet interministériel, propose
+une récupération
+facilitée de fonds de carte officiels de l'IGN.
Ce projet vise à faciliter la récupération des sources officielles, notamment
celles de l'IGN, et leur association à des jeux de données géographiques.
@@ -213,22 +423,16 @@ contours géographiques.
Pour installer `cartiflette`, il est nécessaire d'utiliser les commandes suivantes
depuis un `Jupyter Notebook` (si vous utilisez la ligne de commande directement,
-vous pouvez retirer les `!` et `%` en début de ligne):
+vous pouvez retirer les `!` en début de ligne):
```{python}
#| eval: false
-!pip install --upgrade botocore==1.23.26 #Sur colab, sinon bug
-!pip install --upgrade urllib3==1.22.0 #Sur colab, sinon bug
-!pip install py7zr #Sur colab, sinon bug
-!pip install s3fs #Sur colab, sinon bug
-!git clone https://github.com/InseeFrLab/cartogether.git
-%cd ./cartogether
-!pip install -r requirements.txt
-!pip install .
+!pip install requests py7zr geopandas openpyxl tqdm s3fs PyYAML xlrd
+!pip install git+https://github.com/inseefrlab/cartiflette@80b8a5a28371feb6df31d55bcc2617948a5f9b1a
```
Ces commandes permettent de récupérer l'ensemble du code
-source depuis [`Github`
](https://github.com/InseeFrLab/cartiflette)
+source depuis [`Github`](https://github.com/InseeFrLab/cartiflette)
```{=html}
@@ -246,27 +450,31 @@ la récupération de fonds de carte sur un ensemble de département.
Cela évite la récupération d'un fond de carte très
volumineux (plus de 500Mo) pour une analyse restreinte (quelques départements).
Un autre avantage de `cartiflette` est de faciliter la récupération de fonds
-de carte consolidés comme celui dont on a besoin ici: arrondissements
+de carte consolidés comme celui dont on a besoin ici : arrondissements
dans Paris, communes ailleurs. Comme cela est expliqué dans un encadré à part,
il s'agirait d'une opération pénible à mettre en oeuvre sans `cartiflette`.
Les contours de cet espace peuvent être récupérés de la manière suivante :
```{python}
+#| output: false
import cartiflette.s3 as s3
shp_communes = s3.download_vectorfile_url_all(
crs = 4326,
values = ["75", "92", "93", "94"],
- borders="COMMUNE",
+ borders="COMMUNE_ARRONDISSEMENT",
vectorfile_format="topojson",
filter_by="DEPARTEMENT",
source="EXPRESS-COG-CARTO-TERRITOIRE",
year=2022)
+```
-shp_communes.head()
+```{python}
+shp_communes.head(3)
```
+
On reconnaît la structure d'un `DataFrame` `Pandas`. A cette structure s'ajoute
une colonne `geometry` qui enregistre la position des limites des polygones de chaque
observation.
@@ -308,6 +516,7 @@ carte en plusieurs phases.
En premier lieu, il est nécessaire de récupérer le niveau des communes.
```{python}
+#| output: false
import cartiflette.s3 as s3
shp_communes = s3.download_vectorfile_url_all(
@@ -318,8 +527,10 @@ shp_communes = s3.download_vectorfile_url_all(
filter_by="DEPARTEMENT",
source="EXPRESS-COG-CARTO-TERRITOIRE",
year=2022)
+```
-shp_communes.head()
+```{python}
+shp_communes.head(4)
```
```{python}
@@ -347,16 +558,15 @@ grâce à `cartiflette`:
arrondissements = s3.download_vectorfile_url_all(
crs = 4326,
values = ["75"],
- borders="COMMUNE",
+ borders="COMMUNE_ARRONDISSEMENT",
vectorfile_format="topojson",
filter_by="DEPARTEMENT",
source="EXPRESS-COG-CARTO-TERRITOIRE",
year=2022)
-
```
```{python}
-ax = arrondissements.plot(alpha = 0.8, edgecolor = "k")
+ax = arrondissements.boundary.plot(alpha = 0.8, edgecolor = "k")
ax.set_axis_off()
```
@@ -378,6 +588,11 @@ shp_communes = pd.concat(
])
```
+```{python}
+ax = shp_communes.boundary.plot(alpha = 0.8, edgecolor = "k")
+ax.set_axis_off()
+```
+
Cette approche fonctionne mais elle nécessite un certain nombre
de gestes, qui sont autant de risques d'erreurs. Il est
donc recommandé de privilégier le niveau `COMMUNE_ARRONDISSEMENT`
@@ -390,7 +605,7 @@ qui fait exactement ceci mais de manière fiable.
## Opérations sur les attributs et les géométries
-### Import des données velib
+### Import des données vélib
Souvent, le découpage communal ne sert qu'en fond de cartes, pour donner des
repères. En complément de celui-ci, on peut désirer exploiter
@@ -430,7 +645,7 @@ fig,ax = plt.subplots(figsize=(10, 10))
stations.sample(200).to_crs(3857).plot(ax = ax, color = 'red', alpha = 0.4, zorder=2)
shp_communes.to_crs(3857).plot(ax = ax, zorder=1, edgecolor = "black", facecolor="none",
color = None)
-ctx.add_basemap(ax, source = ctx.providers.Stamen.Watercolor)
+#ctx.add_basemap(ax, source = ctx.providers.Stamen.Watercolor)
ax.set_axis_off()
```
@@ -444,25 +659,22 @@ fig.savefig("featured_geopandas_tutorial.png")
Découvrez ci-dessous par étape les différentes lignes de commandes permettant d'afficher cette carte complète,
étape par étape :
-:one:
-Afficher le nuage de points de 200 stations vélibs prises au hasard
+1️⃣ Afficher le nuage de points de 200 stations vélibs prises au hasard
```{python}
#| output: hide
-fig,ax = plt.subplots(figsize=(10, 10))
+fig, ax = plt.subplots(figsize=(10, 10))
stations.sample(200).to_crs(3857).plot(ax = ax, color = 'red', alpha = 0.4, zorder=2)
```
-
-:two:
-Ajouter à cette couche, en-dessous, les contours des communes
+2️⃣ Ajouter à cette couche, en-dessous, les contours des communes
```{python}
#| output: false
#| echo: true
-fig,ax = plt.subplots(figsize=(10, 10))
+fig, ax = plt.subplots(figsize=(10, 10))
stations.sample(200).to_crs(3857).plot(ax = ax, color = 'red', alpha = 0.4, zorder=2)
shp_communes.to_crs(3857).plot(ax = ax, zorder=1, edgecolor = "black", facecolor="none",
color = None)
@@ -473,10 +685,12 @@ shp_communes.to_crs(3857).plot(ax = ax, zorder=1, edgecolor = "black", facecolor
ax.get_figure()
```
-:three:
-Ajouter un fond de carte de type *open street map* grâce au package
+
+
+3️⃣ Ajouter un fond de carte de type *open street map* grâce au package
`contextily`
+
```{python}
#| output: false
#| echo: true
@@ -485,7 +699,7 @@ fig,ax = plt.subplots(figsize=(10, 10))
stations.sample(200).to_crs(3857).plot(ax = ax, color = 'red', alpha = 0.4, zorder=2)
shp_communes.to_crs(3857).plot(ax = ax, zorder=1, edgecolor = "black", facecolor="none",
color = None)
-ctx.add_basemap(ax, source = ctx.providers.Stamen.Watercolor)
+#ctx.add_basemap(ax, source = ctx.providers.Stamen.Watercolor)
```
```{python}
@@ -494,7 +708,7 @@ ax.get_figure()
```
-:four:
+4️⃣
Il ne reste plus qu'à retirer l'axe des coordonnées, qui n'est pas très
esthétique.
@@ -506,7 +720,7 @@ fig,ax = plt.subplots(figsize=(10, 10))
stations.sample(200).to_crs(3857).plot(ax = ax, color = 'red', alpha = 0.4, zorder=2)
shp_communes.to_crs(3857).plot(ax = ax, zorder=1, edgecolor = "black", facecolor="none",
color = None)
-ctx.add_basemap(ax, source = ctx.providers.Stamen.Watercolor)
+#ctx.add_basemap(ax, source = ctx.providers.Stamen.Watercolor)
ax.set_axis_off()
ax
```
@@ -526,51 +740,43 @@ sur un objet `GeoPandas`. Pour manipuler les données, et non la géométrie,
on parlera d'opérations sur les attributs.
Par exemple, si on désire
-connaître quelques statistiques sur la taille des stations:
+connaître quelques statistiques sur la taille des stations, l'approche
+est identique à si on avait un objet `Pandas` classique :
```{python}
stations.describe()
```
-Pour connaître les plus grands départements de France métropolitaine,
+Pour classer les départements de la petite couronne, du plus grand au plus petit,
procédons en deux étapes:
-1. Récupérons le contour des communes de France métropolitaine dans son ensemble
+1. Récupérons le contour des communes
grâce à `cartiflette`.
Notons qu'on pourrait récupérer directement les contours départementaux mais
pour l'exercice, nous allons le créer nous-mêmes comme agrégation
des contours communaux
-(voir [ce notebook `observable`](https://observablehq.com/@linogaliana/cartiflette-demo) pour la méthode plus
+(voir plus bas ainsi que [ce notebook `Observable`](https://observablehq.com/@linogaliana/cartiflette-demo) pour la méthode plus
légère qui utilise pleinement les fonctionnalités de `cartiflette`).
-2. Calculons la surface (méthode `area` sur un objet `GeoPandas.GeoDataFrame` ramenée en km², attention néamoins au système de projection comme cela est expliqué plus bas)
-```{python}
-#| output: false
-from cartiflette.download import get_vectorfile_ign
-france = get_vectorfile_ign(
- borders = "COMMUNE",
- field = "metropole",
- source = "EXPRESS-COG-CARTO-TERRITOIRE",
- provider="IGN"
-)
-```
+2. Calculons la surface totale de ce territoire (méthode `area` sur un objet `GeoPandas.GeoDataFrame` ramenée en km², attention néamoins au système de projection comme cela est expliqué plus bas)
+
```{python}
-france['surface'] = france.area.div(10**6)
+shp_communes['surface'] = shp_communes.area.div(10**6)
```
Les plus grands départements s'obtiennent par une agrégation des
surfaces communales :
```{python}
-france.groupby('INSEE_DEP').sum(numeric_only = True).sort_values('surface', ascending = False)
+shp_communes.groupby('INSEE_DEP').sum(numeric_only = True).sort_values('surface', ascending = False)
```
Si on veut directement les plus
-grandes communes de France métropolitaine :
+grandes communes de la petite couronne parisienne :
```{python}
-france.sort_values('surface', ascending = False).head(10)
+shp_communes.sort_values('surface', ascending = False).head(10)
```
Lors des étapes d'agrégation, `groupby` ne conserve pas les géométries. Autrement
@@ -583,22 +789,38 @@ attribus avec la méthode `dissolve`:
```{python}
fig,ax = plt.subplots(figsize=(10, 10))
-france.dissolve(by='INSEE_DEP', aggfunc='sum').plot(ax = ax, column = "surface")
+shp_communes.dissolve(by='INSEE_DEP', aggfunc='sum').plot(ax = ax, column = "surface")
ax.set_axis_off()
ax
```
-Pour produire cette carte, il serait néanmoins plus simple de directement
+Pour produire l'équivalent de cette carte à un niveau France entière, il est néanmoins plus simple de directement
récupérer les fonds officiels des départements plutôt que d'agréger les
contours des communes:
```{python}
-dep = get_vectorfile_ign(
- borders = "DEPARTEMENT",
- field = "metropole",
- source = "EXPRESS-COG-CARTO-TERRITOIRE",
- provider="IGN")
-dep["area"] = dep.area
+#| output: false
+dep = s3.download_vectorfile_url_all(
+ values = "metropole",
+ crs = 4326,
+ borders = "DEPARTEMENT",
+ vectorfile_format="topojson",
+ filter_by="FRANCE_ENTIERE",
+ source="EXPRESS-COG-CARTO-TERRITOIRE",
+ year=2022)
+
+dep["area"] = dep.to_crs(2154).area
+```
+
+Avant de calculer les surfaces des départements, pour éviter les déformations liées au
+système `Mercator`, nous faisons une reprojection des données à la volée. Plus de détails
+par la suite.
+
+```{python}
+dep.sort_values('area', ascending = False).head(3)
+```
+
+```{python}
ax = dep.plot(column = "area")
ax.set_axis_off()
```
@@ -627,7 +849,7 @@ de carte `OpenStreetMap`, `Stamen`, `Google Maps`, etc.
Mais ce n'est pas un
format sur lequel on désire faire des calculs car les distances sont faussées sans utiliser de projection. D'ailleurs, `geopandas` refusera certaines opérations
-sur des données dont le crs est `4326`. On reprojete ainsi les données
+sur des données dont le crs est `4326`. On reprojette ainsi les données
dans la projection officielle pour la métropole, le Lambert 93
(epsg: `2154`).
@@ -681,7 +903,7 @@ communes_31['geometry'] = communes_31['geometry'].centroid
ax = communes_31.plot(figsize = (10,10), color = 'red', alpha = 0.4, zorder=2)
dep_31.to_crs(3857).plot(ax = ax, zorder=1, edgecolor = "black", facecolor="none",
color = None)
-ctx.add_basemap(ax, source = ctx.providers.Stamen.Toner)
+#ctx.add_basemap(ax, source = ctx.providers.Stamen.Toner)
ax.set_axis_off()
ax
```
@@ -699,37 +921,6 @@ stations = stations.to_crs(2154)
```
-Le système de projection est fondamental pour que la dimension
-spatiale soit bien interprétée par `Python`. Un mauvais système de représentation
-fausse l'appréciation visuelle mais peut aussi entraîner des erreurs dans
-les calculs sur la dimension spatiale.
-Ce [post](https://www.earthdatascience.org/courses/earth-analytics/spatial-data-r/geographic-vs-projected-coordinate-reference-systems-UTM/) propose de riches éléments sur le
-sujet, notamment l'image suivante qui montre bien le principe d'une projection :
-
-![Les différents types de projection](https://www.earthdatascience.org/images/courses/earth-analytics/spatial-data/spatial-projection-transformations-crs.png)
-
-La Terre peut ainsi être représentée de multiples manières, ce qui n'est pas neutre sur la manière de se représenter
-certains continents.
-L'Afrique apparaît beaucoup moins vaste qu'elle ne l'est en réalité sur les cartes utilisant
-cette projection.
-L'une des déformations les mieux connue est celle provoquée par la projection Mercator.
-Le Groënland paraît avoir
-la même surface que l'Amérique du Sud. Pourtant, cette dernière est 8 fois
-plus grande.
-
-Il existe en fait de nombreuses représentations possibles du monde, plus ou moins
-alambiquées. Les projections sont très nombreuses et certaines peuvent avoir une [forme suprenante](https://imgs.xkcd.com/comics/map_projections.png).
-Par exemple,
-la [projection de Spillhaus](https://storymaps.arcgis.com/stories/756bcae18d304a1eac140f19f4d5cb3d)
-propose de centrer la vue sur les océans et non une terre. C'est pour
-cette raison qu'on parle parfois de monde tel que vu par les poissons
-à son propos.
-
-
-![](truesize.png)
-*Exemple de reprojection de pays depuis le site [thetruesize.com](https://thetruesize.com/)*
-
-
Concernant la gestion des projections avec `GeoPandas`,
la [documentation officielle](https://geopandas.org/projections.html) est très bien
faite. Elle fournit notamment l'avertissement suivant qu'il est
@@ -739,8 +930,8 @@ bon d'avoir en tête:
>
> From time to time, however, you may get data that does not include a projection. In this situation, you have to set the CRS so geopandas knows how to interpret the coordinates.
-![](https://imgs.xkcd.com/comics/bad_map_projection_south_america.png)
-*Image empruntée à XKCD