Complex Network project_2021_M2 Complex Systems
Context
According to the Food and Agriculture Organisation (FAO), forests are characterized by a minimal area of 50 ares (5000 m²), the presence of trees higher than 5 meters, a density of tree higher than 10% and a mean width superior to 20 meters.
France's forests host numerous animals, including large mammals such as deers. Those usually live in herds in 'seasonally ephemeral' home range, meaning they establish a territory during the breeding season then move.
The population home range can span across several patches of nearby forests. It is explored daily or weekly for food and shelter opportunities, during foraging explorations Hjeljord (2001). Foraging movements can extend the home range from 2 to up to 24 km².
During their lives, deers also migrate under different constraints: to find mating partners and feeding resources, to avoid inbreeding or predators such as wolves, and under environmental constraints such as flood or fire. The deer dispersal distance range from tens to hundred kilometers. For example, GPS-tracking of collared white-tailed deer have evidenced patterns of spring and fall migration in Canada ranging from 6.9 to 87 km. Another study conducted by Nelson on white-tailed deer have shown comparable migration speed among the animals, with little differences in spring and fall. The deer migrated at an average speed of 1.6 km/h, in a non-linear fashion: they migrated during day only, pausing and turning back in unpredictable manner. Deers can migrate between non-connected forests in agricultural landscape, but only settle in forests or mountains.
Project
Deers tend to feed in clearings and agricultural fields, and avoid dense forests. Deers can cause damage to nearby cultures and to forests due to selective grazing. Forest management is thus of prime importance to regulate deer populations.
Here, we are interested in the distribution of forest patches on the french homeland territory and how that distribution could impact deer settlement, foraging movements and migration patterns.
Foraging movements
We assume that deer foraging movements can extend from 1 to 15km in a day. For a deer resting or settled in a given patch of forest, forests patches within that range can thus be explored with a probability related to their distance to the resting forest and the forests area and geometry.
Deers settle in forest patches if they can access sufficient resources. For our model, we assume a linear relationship between resource availability and area of a forest or the area of connected forests in close vicinity (home range).
Under those assumptions, we investigate aspects of the putative foraging patterns: in what patches of forests do deers have a higher probability to settle and meet reproductive partners? (components, sufficient weight/connectivity, …)
Migration
Deers migrate between potential settlement sites during migration events.
Under this model, we try to answer questions related to migration patterns: what migratory paths between far patches are more probable ? (how does the network of forests extend, betweenness of the nodes,…)
Method
To study the putative migration area of the raindeer, we picture French homeland forests as a geographical graph.
We represent forests by nodes at their centroid location. The nodes are weighted by the forest area. Edges represents forests accessible during foraging, i.e. in a arbitrary distance from forests contours, and they are weighted relative to the probability of travel between two forests.
import pandas as pd
import json
import numpy as np
from matplotlib import pyplot as plt
import networkx as nx
import shapely.geometry
import sys
Forest outlines were obtained from the geoportail website.
with open('FOR_PUBL_FR.json', 'r') as j:
data = json.loads(j.read())
For each public forest on the national territory (a 'feature' in the dictionary), the data contains the coordinates of several points on the forest's edges. This allows us to visualize a forest as a polygon.
print("Polygon of the first forest : ")
polygon = np.asarray(data['features'][0]['geometry']['coordinates'][0])
x= polygon[:,0]
y= polygon[:,1]
plt.scatter(x,y)
plt.show()
Polygon of the first forest :
print("Polygon of the second forest : ")
polygon = np.asarray(data['features'][1]['geometry']['coordinates'][0])
x= polygon[:,0]
y= polygon[:,1]
plt.scatter(x,y)
plt.show()
Polygon of the second forest :
Some forests are made of more than one polygon, like the eleventh forest below.
For a deer, it makes no difference if two separated polygons belong to the same forest or not. Therefore, we treat the different polygons of a same forest as different forests. This explains the 'for' statement when we construct the graph.
polygons = np.asarray(data['features'][11]['geometry']['coordinates'])
polygons = polygons[:,0]
for i in range(len(polygons)):
polygon = np.asarray(polygons[i], dtype = object)
x= polygon[:,0]
y= polygon[:,1]
plt.scatter(x,y)
plt.show()
We choose to represent forests' location by their centroid. Centroids are computed from their discrete contour data using the shapely.geometry method for polygon.
Each forest's polygon is constructed using the coordinates of their contour.
centroid = [] #Will contain the positions of the centroids of the forests
areas = [] #Will contain the areas of the forests
for i in range(len(data['features'])):
if len(data['features'][i]['geometry']['coordinates'][0][0]) > 2 : #When the forest has multiple polygons
for j in range(len(data['features'][i]['geometry']['coordinates'][0])):
shapely_pol = shapely.geometry.Polygon(data['features'][i]['geometry']['coordinates'][0][j])
centroid.append(list(shapely_pol.centroid.coords)[0])
areas.append(shapely_pol.area) #We add positions and areas of each polygon independently
else : #If there is only one polygon
shapely_pol = shapely.geometry.Polygon(data['features'][i]['geometry']['coordinates'][0])
centroid.append(list(shapely_pol.centroid.coords)[0])
areas.append(shapely_pol.area)
centroid = np.asarray(centroid)
areas = np.asarray(areas)
fig, ax = plt.subplots(figsize = (20, 20))
for val in centroid :
ax.add_patch(plt.Circle(val, 10**(-3), facecolor = 'r', edgecolor='black'))
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
ax.axes.set_xlim([min(np.transpose(centroid)[0]) - 1,
max(np.transpose(centroid)[0]) + 1])
ax.axes.set_ylim([min(np.transpose(centroid)[1]) - 1,
max(np.transpose(centroid)[1]) + 1])
plt.show()
The size of the dots here are only for representation.
We check the global distribution of the forests centroids compared to the geoportail map.
Public forests are more densely distributed in the East and South of France: our analysis will be biased due to the lack of data on private forests, which represents 74% of French forests.
To take forests' area into account, we weight the nodes according to each polygon's area.
G = nx.Graph()
count = 0
for (val, a) in zip(centroid, areas) :
G.add_node(count, lon = val[0], lat = val[1], area = a)
count += 1
We link together nodes representing patches of forest a deer can explore during daily foraging.
Forests have various geometry that the centroid position does not represent. Two adjacent forests can share a border of various length: deers would have a higher probability of moving between two forest if they have a long border than if they only have a small corridor to get from one to the other. Moreover, even if two forest are serparated by a distance that a deer can travel in a day, deers would have less chance of getting from one to the other if they are further appart than if they are very close. To take into account the fact that deers have different probabilities of getting from one forest to another, we use weighted edges.
Weighting the edges with the distances between the centroids would erase the importance of the shape of the forest, the fact that the interfaces at short distances can be more or less important. To avoid that bias, we use the shapely buffer method to compute the distance between polygons.
The buffer method is a way to extend the polygon while conserving the shape. The method takes all the points within a certain distance (the buffer distance) of the polygon. Bellow are the results of the buffer method on the first forest polygon for different values of d the buffer distance. You can see the way the buffer modify the polygon by running the script.
poly = shapely.geometry.Polygon(data['features'][0]['geometry']['coordinates'][0]) # polygon
poly