Cartographie sous python

Dans ce TP, nous allons aborder l'aspect cartographie sous python avec le package folium (à installer donc). L'idée sera d'ajouter à un fond de carte des formes de couleurs, en fonction d'une information tierce.

Création de cartes

Le package folium est une interface entre python et la librairie leaflet. Elle s'utilise très facilement, comme nous pouvons le voir ci-dessous pour la création d'une carte. Les coordonnées indiquées sont la latitude et la longitude de New-York. L'option tiles permet de choisir le fournisseur de tuiles de fond. Vous pourrez trouver plus de fournisseurs sur cette page.

In [1]:
import folium
centre = [40.71427, -74.00597]
newyork = folium.Map(location = centre, zoom_start = 12, tiles = 'Cartodb Positron')

Pour obtenir la carte, il suffit d'appeler l'objet ainsi créé.

In [2]:
newyork
Out[2]:

Ajout de marqueurs

La fonction Marker() permet d'ajouter une marque sur la carte pour signifier un emplacement précis. Il est possible de spécifier un contenu à afficher (sous la forme d'un pop-up) lorsque l'utilisateur va cliquer sur le marqueur. Celui-ci sera placé à la localisation fournie en premier, au format [latitude, longitude]. Ce contenu est spécifié dans l'argument popup (ici, une simple chaîne de caractères, mais cela peut être plus élaboré). Il est aussi possible customiser l'icône du marqueur. Une fois ce marqueur créé, il faut l'ajouter à la carte, via la fonction add_to().

In [3]:
folium.Marker(centre, popup = "New-York").add_to(newyork)
newyork
Out[3]:

Ajout de cercles

La fonction CircleMarker() permet elle de créer des cercles, en donnant la localisation du centre et le rayon (via le paramètre radius). Idem que pour Marker(), on peut spécifier le contenu de la popup affichée lors d'un clic, et il est nécessaire de l'ajouter à la carte. Il est possible aussi de spécifier la couleur du cercle (via color) et la couleur de remplissage (via fillColor).

In [4]:
folium.CircleMarker(centre, popup = "New-York", radius = 10).add_to(newyork)
newyork
Out[4]:

Informations géographiques

Pour pouvoir ajouter ces informations, nous allons importer les contours des 5 quartiers new-yorkais disponibles dans ce fichier. Les données à récupérer sont celles au format GeoJSON. Vous devez les sauvegarder, idéalement dans le même répertoire que votre notebook.

In [5]:
import json
geo = json.load(open("newyork-borough.geojson"))
In [6]:
geo.keys()
Out[6]:
dict_keys(['type', 'crs', 'features'])

Le format GeoJSON est de plus en plus utilisé pour représenter des données de type géographique. Il suit des spécifications précises, et peut être utilisé directement comme nous le verrons plus loin.

Dans cet objet, transformé en dictionnaire sous python, il y a deux éléments : le type et les informations (nommées features)

In [7]:
geo.keys()
Out[7]:
dict_keys(['type', 'crs', 'features'])
In [8]:
geo["type"]
Out[8]:
'FeatureCollection'

Nous vérifions bien que nous avons les 5 quartiers de New-York dans l'objet features, qui est une list.

In [9]:
len(geo["features"])
Out[9]:
5

Dans chaque objet de cette liste, nous disposons aussi de différents objets de type Feature.

In [10]:
geo["features"][0].keys()
Out[10]:
dict_keys(['type', 'geometry', 'properties', 'id'])
In [11]:
geo["features"][0]["id"]
Out[11]:
0
In [12]:
geo["features"][0]['type']
Out[12]:
'Feature'

Dans le champs properties, il y a différentes informations utiles, dont les coordonnées longitude et latitude du centre.

In [13]:
geo["features"][4]['properties']
Out[13]:
{'style': {'fillOpacity': 0.6,
  'opacity': 1,
  'weight': 1,
  'fillColor': '#fdc47a',
  'color': 'black'},
 'coordinates': [40.84478, -73.86483],
 'BoroCode': 2,
 'BoroName': 'Bronx',
 'highlight': {},
 'Shape_Area': 1186804144.79,
 'Shape_Leng': 464517.89055}

Et dans la partie geometry, nous voyons que c'est un MultiPolygon qui est représenté, avec toutes les coordonnées des sommets de ce polygone.

In [14]:
geo["features"][0]['geometry']
Out[14]:
{'type': 'MultiPolygon',
 'coordinates': [[[[-74.24927399999999, 40.54492199999982],
    [-74.249211, 40.54506399999981],
    [-74.24741499999999, 40.549199999999814],
    [-74.23921099999998, 40.55376399999984],
    [-74.233052, 40.557608999999815],
    [-74.23278799999997, 40.55777299999983],
    [-74.23252499999998, 40.55793799999982],
    [-74.23215199999999, 40.55815799999982],
    [-74.23213399999999, 40.558169999999826],
    [-74.23192599999999, 40.55831099999982],
    [-74.23169299999998, 40.55845699999982],
    [-74.23099199999999, 40.5583959999998],
    [-74.23031199999998, 40.558335999999834],
    [-74.23029099999998, 40.55833399999982],
    [-74.22968499999999, 40.55828099999982],
    [-74.22907999999998, 40.558227999999815],
    [-74.22823999999999, 40.55813099999981],
    [-74.22739999999999, 40.55803399999982],
    [-74.22595599999998, 40.557867999999814],
    [-74.224513, 40.557700999999824],
    [-74.21839799999997, 40.55699599999982],
    [-74.21683899999998, 40.558617999999804],
    [-74.21681599999998, 40.55864199999981],
    [-74.21669999999999, 40.55876299999983],
    [-74.21658299999999, 40.558883999999814],
    [-74.21602799999998, 40.559460999999814],
    [-74.21547299999999, 40.560037999999814],
    [-74.21527799999997, 40.56024099999981],
    [-74.21220999999998, 40.56817699999982],
    [-74.21148099999998, 40.57006199999981],
    [-74.21077899999999, 40.571876999999816],
    [-74.21075199999999, 40.57194799999983],
    [-74.20985999999998, 40.57425599999982],
    [-74.20896799999998, 40.57656299999981],
    [-74.20829599999998, 40.57905199999983],
    [-74.20826699999998, 40.57919299999982],
    [-74.20825699999999, 40.57924199999981],
    [-74.20823699999998, 40.579333999999825],
    [-74.20751899999998, 40.58274099999982],
    [-74.20680199999998, 40.586147999999824],
    [-74.20629799999998, 40.58854199999983],
    [-74.20368799999999, 40.59269099999981],
    [-74.19951999999996, 40.59753899999981],
    [-74.19951899999998, 40.59756499999981],
    [-74.19948799999999, 40.59828699999982],
    [-74.19945799999998, 40.59900899999982],
    [-74.19942099999999, 40.59987999999982],
    [-74.19940799999998, 40.60020099999982],
    [-74.20381299999998, 40.605960999999816],
    [-74.20375899999998, 40.60660399999982],
    [-74.20312799999998, 40.61410899999982],
    [-74.20254099999998, 40.61617499999982],
    [-74.20243399999998, 40.616549999999826],
    [-74.20186399999999, 40.61855699999982],
    [-74.20316199999999, 40.6224859999998],
    [-74.20373699999998, 40.62422699999981],
    [-74.20348499999999, 40.62505799999982],
    [-74.20244099999998, 40.62852099999982],
    [-74.202247, 40.63090299999984],
    [-74.20152999999998, 40.631599999999835],
    [-74.201, 40.632114999999835],
    [-74.20046899999998, 40.63262999999982],
    [-74.19742799999997, 40.635429999999836],
    [-74.19709399999996, 40.635736999999814],
    [-74.19698999999997, 40.63583199999982],
    [-74.19650499999999, 40.63649599999984],
    [-74.19601999999998, 40.63716099999982],
    [-74.19584199999998, 40.637403999999826],
    [-74.19571099999999, 40.63758299999983],
    [-74.19566399999998, 40.63764799999981],
    [-74.19564299999998, 40.63766699999983],
    [-74.19348099999998, 40.639669999999825],
    [-74.19129799999999, 40.64169099999982],
    [-74.19048899999999, 40.642439999999816],
    [-74.18967999999998, 40.64318799999983],
    [-74.18921599999999, 40.64356099999983],
    [-74.18875399999999, 40.64393399999983],
    [-74.18854899999997, 40.64409999999983],
    [-74.18834399999999, 40.644263999999815],
    [-74.18779699999999, 40.64460399999983],
    [-74.18775399999998, 40.64463099999982],
    [-74.18677599999997, 40.645239999999816],
    [-74.18563599999999, 40.645994999999814],
    [-74.18138999999998, 40.64647499999982],
    [-74.18054799999999, 40.64638199999982],
    [-74.17907099999998, 40.64621499999982],
    [-74.17759499999998, 40.646048999999834],
    [-74.17061099999997, 40.64528899999983],
    [-74.16170699999998, 40.644321999999825],
    [-74.16156299999999, 40.64430599999982],
    [-74.16148999999999, 40.64429799999983],
    [-74.16137399999998, 40.64428499999981],
    [-74.15953499999999, 40.6440539999998],
    [-74.15825499999998, 40.64389499999982],
    [-74.15797699999999, 40.643859999999826],
    [-74.15769899999997, 40.64382499999983],
    [-74.15643199999998, 40.64366699999984],
    [-74.15350899999999, 40.64332999999983],
    [-74.14938099999998, 40.64285499999982],
    [-74.14325499999998, 40.642148999999804],
    [-74.14207699999999, 40.64224599999982],
    [-74.14189899999998, 40.64227599999981],
    [-74.13590399999998, 40.64333199999982],
    [-74.13581699999999, 40.64334799999981],
    [-74.13391199999998, 40.64368399999983],
    [-74.13179999999998, 40.643754999999835],
    [-74.13002199999997, 40.643814999999826],
    [-74.12815099999997, 40.64390199999982],
    [-74.12556899999998, 40.64402299999982],
    [-74.12410699999998, 40.64445099999981],
    [-74.12382999999998, 40.64453199999981],
    [-74.12355299999999, 40.64461299999982],
    [-74.12211399999998, 40.645033999999825],
    [-74.12067499999998, 40.645455999999825],
    [-74.11650899999998, 40.646450999999814],
    [-74.11234299999998, 40.64744599999982],
    [-74.10997599999999, 40.64801099999982],
    [-74.10631599999998, 40.64806199999982],
    [-74.10503999999999, 40.648079999999815],
    [-74.10024699999998, 40.648147999999836],
    [-74.09417799999999, 40.64823299999984],
    [-74.09374599999998, 40.648238999999826],
    [-74.091253, 40.64944399999981],
    [-74.09058199999998, 40.64976999999982],
    [-74.08741799999999, 40.65129999999981],
    [-74.08680599999998, 40.651595999999806],
    [-74.07726099999998, 40.65173099999983],
    [-74.07441499999999, 40.651770999999805],
    [-74.07156999999998, 40.65181199999983],
    [-74.05573899999999, 40.65175999999981],
    [-74.05619299999998, 40.63928099999982],
    [-74.05623299999998, 40.63817699999982],
    [-74.05626099999998, 40.63740699999981],
    [-74.05646799999998, 40.63171499999982],
    [-74.05662999999997, 40.62728699999981],
    [-74.05532399999997, 40.62510299999982],
    [-74.05383799999998, 40.622617999999825],
    [-74.05352199999999, 40.62209099999981],
    [-74.05247699999998, 40.62034399999982],
    [-74.05235199999998, 40.620133999999815],
    [-74.04996299999998, 40.61625699999983],
    [-74.04962399999998, 40.61571199999982],
    [-74.04907499999999, 40.61482999999983],
    [-74.04810099999999, 40.613263999999816],
    [-74.04756799999998, 40.612405999999815],
    [-74.04751399999998, 40.61231899999983],
    [-74.04645099999996, 40.61073499999981],
    [-74.04519699999999, 40.60886299999982],
    [-74.04411399999998, 40.60724799999982],
    [-74.044074, 40.60718799999983],
    [-74.043945, 40.60699599999982],
    [-74.04382499999998, 40.60681799999982],
    [-74.04203599999998, 40.60408799999981],
    [-74.04139299999999, 40.60310599999982],
    [-74.03787699999997, 40.589200999999804],
    [-74.03454699999999, 40.576249999999824],
    [-74.03504699999998, 40.57284999999981],
    [-74.03505299999998, 40.572746999999836],
    [-74.03515299999998, 40.57100299999982],
    [-74.03597799999999, 40.55656299999981],
    [-74.03620899999999, 40.55252399999982],
    [-74.03621599999998, 40.55239999999983],
    [-74.03628499999998, 40.55118499999981],
    [-74.03629299999999, 40.55104199999981],
    [-74.03629699999998, 40.55096899999982],
    [-74.03629799999999, 40.55094799999982],
    [-74.03629999999998, 40.55090499999982],
    [-74.03654899999998, 40.54920899999981],
    [-74.03655099999999, 40.54919399999982],
    [-74.03666999999999, 40.548383999999814],
    [-74.03667199999998, 40.548369999999814],
    [-74.03749399999998, 40.54278999999981],
    [-74.03813099999996, 40.538293999999816],
    [-74.04211199999999, 40.509298999999814],
    [-74.05759499999998, 40.50652899999983],
    [-74.05787299999999, 40.50647599999983],
    [-74.07105199999998, 40.5039109999998],
    [-74.07817899999998, 40.50250499999982],
    [-74.08289599999999, 40.501665999999815],
    [-74.09149199999999, 40.50013699999981],
    [-74.09166999999998, 40.50010499999981],
    [-74.09229799999999, 40.49998899999982],
    [-74.09448299999998, 40.499600999999814],
    [-74.098576, 40.49890399999983],
    [-74.10757599999997, 40.497416999999814],
    [-74.12454899999999, 40.49462099999982],
    [-74.13113199999998, 40.493532999999815],
    [-74.13715599999998, 40.49253799999982],
    [-74.14391699999997, 40.491393999999815],
    [-74.14993799999998, 40.49037199999982],
    [-74.15206899999997, 40.49001699999982],
    [-74.16039299999997, 40.48868999999982],
    [-74.16263399999998, 40.48828999999982],
    [-74.16386099999997, 40.48807099999982],
    [-74.20034199999998, 40.48201799999981],
    [-74.20039499999999, 40.48200899999982],
    [-74.20645099999999, 40.481014999999815],
    [-74.21704099999998, 40.47922399999981],
    [-74.22815299999996, 40.477398999999814],
    [-74.23793499999998, 40.48124099999982],
    [-74.24069099999998, 40.48234199999981],
    [-74.24365199999998, 40.48352599999983],
    [-74.24826099999999, 40.485366999999805],
    [-74.24858499999998, 40.485496999999825],
    [-74.24958899999999, 40.48589799999983],
    [-74.24974299999998, 40.485959999999814],
    [-74.24989699999996, 40.486020999999816],
    [-74.24993399999997, 40.486034999999816],
    [-74.25264599999998, 40.48711999999981],
    [-74.25315899999998, 40.48732499999982],
    [-74.25331299999998, 40.48738599999982],
    [-74.25428899999999, 40.48900599999982],
    [-74.25652999999997, 40.49296499999981],
    [-74.25671899999998, 40.49329899999982],
    [-74.25731699999999, 40.494348999999815],
    [-74.25902799999999, 40.49710699999982],
    [-74.25908999999999, 40.49720699999982],
    [-74.25908899999999, 40.49956099999982],
    [-74.25908899999999, 40.49987199999981],
    [-74.25908799999998, 40.50011199999982],
    [-74.25908799999998, 40.500133999999804],
    [-74.25908899999999, 40.500464999999814],
    [-74.25908999999999, 40.50098299999981],
    [-74.25908899999999, 40.50227799999981],
    [-74.25908899999999, 40.502889999999816],
    [-74.25857599999998, 40.50611199999983],
    [-74.25855599999998, 40.50623199999982],
    [-74.25847299999997, 40.50675299999981],
    [-74.25836999999999, 40.50739699999983],
    [-74.25829099999999, 40.50790499999982],
    [-74.25826199999997, 40.50796399999982],
    [-74.25719199999999, 40.51024399999983],
    [-74.25698399999999, 40.51068799999982],
    [-74.25633999999998, 40.51206699999982],
    [-74.25592399999996, 40.51295799999981],
    [-74.25548799999997, 40.51388999999982],
    [-74.25514899999999, 40.51461799999983],
    [-74.25480999999998, 40.51534399999983],
    [-74.25441699999999, 40.51571899999981],
    [-74.25303799999999, 40.51658299999983],
    [-74.25165999999999, 40.51744799999982],
    [-74.24606899999998, 40.52095199999983],
    [-74.24606999999999, 40.520971999999816],
    [-74.24608399999998, 40.52129299999984],
    [-74.24609699999999, 40.52161399999982],
    [-74.24640799999999, 40.52435699999981],
    [-74.24644399999998, 40.52467299999983],
    [-74.24878699999998, 40.53303299999982],
    [-74.25020499999998, 40.53962899999983],
    [-74.25060899999998, 40.54185099999984],
    [-74.24927399999999, 40.54492199999982]]]]}

Marqueur par quartier

Avec les informations contenues dans ce fichier, nous allons pouvoir ajouter un marqueur pour chaque quartier, en mettant dans la popup, son nom.

In [15]:
newyork_qu_marker = folium.Map(location = centre, zoom_start = 11, tiles = 'Cartodb Positron')
for qu in geo["features"]:
    prop = qu["properties"]
    folium.Marker(prop["coordinates"], popup = prop["BoroName"]).add_to(newyork_qu_marker)
newyork_qu_marker
Out[15]:

Cercle par arrondissement

Toujours avec les mêmes informations, nous pouvons disposer des cercles pour chaque arrondissement. Ici, nous décidons de régler la taille des cercles (via radius) en fonction de la surface des arrondissements. Nous remarquons que nous devons faire cela manuellement (ici, on divise la surface par 1000000 pour avoir des tailles raisonnables).

In [16]:
newyork_qu_circle = folium.Map(location = centre, zoom_start = 11, tiles = 'Cartodb Positron')
for qu in geo["features"]:
    prop = qu["properties"]
    folium.CircleMarker(prop["coordinates"], 
                        popup = prop["BoroName"],
                        radius = prop["Shape_Area"] / 1e08,
                        fill = True).add_to(newyork_qu_circle)
newyork_qu_circle
Out[16]:

Quartiers

Enfin, il est possible de faire des cartes choroplètes, dans lesquelles les zones sont coloriées selon une mesure statistique.

Pour cela, nous devons devons utiliser la classe Choropleth() de la carte crééeu module. Nous passons dans le paramètre geo_data les données contenues dans le fichier GeoJSON importées plus haut, puis nous l'ajoutons à la carte précédemment créée.

In [17]:
newyork = folium.Map(location = centre, zoom_start = 10, tiles = 'Cartodb Positron')
folium.Choropleth(geo_data = geo).add_to(newyork)
newyork
Out[17]:

Avec une information tierce

Bien évidemment, nous désirons ajouter une couleur. Pour cela, nous avons besoin d'un DataFrame contenant deux variables :

  • un identifiant commun avec les informations contenues dans le GeoJSON
  • la variable statistique d'intérêt (pour cet exemple, ce sera la surface)

Pour le construire, nous allons récupérer le nom du quartier (item BoroName dans properties) et la surface (Shape_Area dans properties). Voici comment les récupérer.

In [18]:
[qu["properties"]["BoroName"] for qu in geo["features"]]
Out[18]:
['Staten Island', 'Queens', 'Brooklyn', 'Manhattan', 'Bronx']
In [19]:
[qu["properties"]["Shape_Area"] for qu in geo["features"]]
Out[19]:
[1623853249.91, 3049947236.73, 1959432236.83, 636442167.467, 1186804144.79]

Nous créons donc maintenant le DataFrame avec ces deux informations.

In [20]:
import pandas

df = pandas.DataFrame({
    # "BoroCode" : [qu["properties"]["BoroCode"] - 1 for qu in geo["features"]], 
    "Quartier": [qu["properties"]["BoroName"] for qu in geo["features"]], 
    "Surface" : [qu["properties"]["Shape_Area"] / 1e08 for qu in geo["features"]]
})
df
Out[20]:
Quartier Surface
0 Staten Island 16.238532
1 Queens 30.499472
2 Brooklyn 19.594322
3 Manhattan 6.364422
4 Bronx 11.868041

Pour relier ces informations à la carte, toujours dans la fonction choropleth(), nous devons définir les éléments suivants :

  • geo_data : les contours au format GeoJSON
  • key_on : l'item dans le GeoJSON avec lequel nous ferons la jointure
  • data : le DataFrame dans lequel nous avons les informations statistiques
  • columns : les deux colonnes à prendre
    1. clé de jointure
    2. mesure statistique
  • fill_color : la palette de couleurs à utiliser (provenant de Color Brewer)

Nous obtenons maintenant la carte suivante.

In [21]:
newyork = folium.Map(location = centre, zoom_start = 10, tiles = 'Cartodb Positron')
folium.Choropleth(geo_data = geo, key_on = "feature.properties.BoroName",
                  data = df, columns = ["Quartier", "Surface"],
                  fill_color = 'Spectral',
                  legend_name = "Surface du quartier de New-York").add_to(newyork)
newyork
Out[21]:

Exercices

Nous allons utiliser une base de données des restaurants new-yorkais disponible dans ce fichier (attention : il fait environ 10 Mo). Nous disposons de deux niveaux d'informations géographiques dans la base des restaurants new-yorkais :

  • les géolocalisation de chaque restaurant
  • les quartiers correspondants à chaque restaurant

Avec ces deux informations, nous allons donc pouvoir créer des cartes, soit avec des points pour chaque station, soit des formes pour les quartiers. Sur cette base, vous devez faire les demandes suivantes :

  1. Afficher un marqueur pour chaque restaurant, sur la carte de New-York
  2. Ajouter à ces marqueurs une popup intégrant le nom du restaurant
  3. Améliorer ces popups, en y ajoutant le quartier
  4. Améliorer encore en mettant en gras le nom, en italique le quartier, et en ajoutant l'adresse, ainsi que le nombre d'évaluation pour chaque score
  5. Afficher les quartiers sur la carte de New-York, avec une couleur dépandant du nombre de restaurants présents dans le quartier
  6. Faire la même carte, mais avec une couleur en fonction du score moyen

Vous trouverez ci-dessous le code pour importer les données dans python.

In [22]:
import bson
donnees = bson.decode_all(open('restaurants.bson', 'rb').read())
In [23]:
donnees[:1]
Out[23]:
[{'_id': ObjectId('58ac16d1a251358ee4ee87dd'),
  'address': {'building': '1007',
   'coord': [-73.856077, 40.848447],
   'street': 'Morris Park Ave',
   'zipcode': '10462'},
  'borough': 'Bronx',
  'cuisine': 'Bakery',
  'grades': [{'date': datetime.datetime(2014, 3, 3, 0, 0),
    'grade': 'A',
    'score': 2},
   {'date': datetime.datetime(2013, 9, 11, 0, 0), 'grade': 'A', 'score': 6},
   {'date': datetime.datetime(2013, 1, 24, 0, 0), 'grade': 'A', 'score': 10},
   {'date': datetime.datetime(2011, 11, 23, 0, 0), 'grade': 'A', 'score': 9},
   {'date': datetime.datetime(2011, 3, 10, 0, 0), 'grade': 'B', 'score': 14}],
  'name': 'Morris Park Bake Shop',
  'restaurant_id': '30075445'}]