The world’s leading publication for data science, AI, and ML professionals.

Orthographic projection with pyproj for penguin tracking in Antarctica

How to create a map in Orthographic projection and a map-animation with penguin tracking data – Pyproj and KeplerGL

The behavioral studies of Adélie penguins are addressed to central place foraging theory which explains that breeding seabirds should energetically optimize prey acquisition and, therefore, foraging is expected to be located where prey is most available, within limits defined by the energetics of the species [1]. Despite the conditions of the Antarctic such as sea-surface temperature, chlorophyll concentration, or sea ice cover, the penguins must find foraging success in different locations based on oceanographic characteristics.

In this map review, I am going to work with a subset that contains the movement tracks of 16 penguins recorded during the year 2018 in Antarctica. The entire dataset contains the tracking information of 129 penguins recorded during the period 2010–12–21 to 2018–01–11 and can be found in Movebank directly here.

Final map animation: [HERE!](https://github.com/bryanvallejo16/penguin-tracking-antarctica) Repository: HERE!

Objective

The main objective of this practice is to visualize the penguin tracks in Orthographic projection with pyproj. Additionally, to create a map animation with Keplergl that shows the behavioral movement of the penguins during foraging activity on the coastline. So, we are going to obtain two products: 1) Map with Orthographic view, and 2) Map-animation of penguin tracks.

1) Creating a map with Orthographic Projection

The idea of Orthographic Projection is to visualize the objects in an azimuthal perspective (perpendicular) in a three-dimensional globe. For this, we are going to use the python library pyproj and geopandas. The first main step is to visualize the dataset.

import geopandas as gpd
import pandas as pd
from pyproj import CRS
from keplergl import KeplerGl
import matplotlib.pyplot as plt
# reading data
fp = r'data/penguin-tracks.gjson'
geodata = gpd.read_file(fp, driver='GeoJSON')
# defining timestamp columns
geodata['timestamp'] = pd.to_datetime(geodata['timestamp'])
geodata['year'] = pd.DatetimeIndex(geodata['timestamp']).year
geodata['t'] = geodata['timestamp'].astype(str)

The code above simply reads the data and creates two extra columns. year that contains the year of the tracks for this example all records have 2018 and t that contains the timestamp as a string type. The last column is needed in a string type for the map animation. Then, as a quick visualization in WGS84, we run the next line to visualize the data.

geodata.plot(column='ind_ident', figsize=(12,6), markersize=0.2)
Image by the author. Tracks of 16 penguins in wgs84
Image by the author. Tracks of 16 penguins in wgs84

Now, I am going to load the Natural Earth layer of global countries directly from geopandas and project it in Orthographic Projection. So, in the way we are observing a rounded globe, I am going to center the Orthographic projection in the South Pole (Antarctic) which means latitude -90° and longitude 0°. The CRS of the Orthographic projection is obtained by a transformation: from proj4 (parameters) to CRS. The code goes like this:

# create world layer
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
# define center of Orthographic Projection
lat = -90
lon = 0
ortho = CRS.from_proj4("+proj=ortho +lat_0={} +lon_0={} +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs".format(lat, lon))
# Re-project and plot
geodataor = geodata.to_crs(ortho)
world = world.to_crs(ortho)
world.plot(figsize=(12,6))
Image by the author. World layer with Orthographic Projection centered in Antarctica
Image by the author. World layer with Orthographic Projection centered in Antarctica

Now, we create a unique figure, ax where world layer and penguin tracks are going to be added. In this unique, figure, ax we set x and y limits the bounding box of the Antarctica continent. First, we get the bounding box of Antarctica then we plot it. Code goes like this:

antarctic = list(world.loc[world['continent']=='Antarctica'].geometry)[0]
antarctic
Image by the Author. Antarctic's geometry object
Image by the Author. Antarctic’s geometry object

Now we set the x min, x max, y min, y max of the bounding box:

bounds = antarctic.bounds

xmin = bounds[0]
xmax = bounds[2]
ymin = bounds[1]
ymax = bounds[3]

Now, the unique figure, ax :

# create unique fig, ax
fig, ax = plt.subplots(figsize=(12, 8))
# adding layers
world.plot(ax=ax)
geodataor.plot(ax=ax, markersize=10, color='red')
# limits
ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax))
plt.axis('off')
Image by the Author. Location of penguin tracks in Orthometric Projection
Image by the Author. Location of penguin tracks in Orthometric Projection

Now that we know the location of the penguin tracks we sharp the final map.

plt.style.use('seaborn')
# create unique fig, ax
fig, ax = plt.subplots(figsize=(10, 7))
# adding layers
world.plot(ax=ax, color='#CED7E0', edgecolor='black')
geodataor.plot(ax=ax, column = 'ind_ident', markersize=7, alpha=0.8, cmap='tab10', legend=True)
# limits
ax.set(xlim=(1500000, 1800000), ylim=(-2050000, -1800000))
# plt.axis('off')
plt.grid(False)
ax.set_facecolor('#0F4983')
plt.title('Adélie Penguins tracks in Antarctic')
ax.get_legend().set_bbox_to_anchor((1.2,1))
ax.get_legend().set_title("Penguin ID")
plt.savefig(r'png/penguin-tracks.png')
Image by the Author. Penguin tracks in the Antarctic.
Image by the Author. Penguin tracks in the Antarctic.

2) Creating a map animation with KeplerGL

Now, we want to visualize the movement pattern of the penguin tracks. We select only the columns we need for the map animation in the geodata layer. Then, a great tool to visualize the movement pattern is the KeplerGL library. The steps to create a KeplerGL map are to start with an empty instance, then add the data, then save the map. As next:

# selecting the needed columns
geodata = geodata[['t', 'ind_ident', 'geometry']]
# Create KeplerGl instance
m = KeplerGl(height=600)
# Add stop durations
m.add_data(geodata, 'Penguins')
m

Here we configure the map as desired with KeplerGL.

Image by the Author. KeplerGL instance with penguin tracking data
Image by the Author. KeplerGL instance with penguin tracking data

Finally, just save the map after configuration.

# Save map as html
m.save_to_html(file_name='index.html')

Conclusion

You can use the Orthographic Projection as you want, not only with this data but you may notice you always need to include in the same map, everything with the same projection. This case Orthometric. For KeplerGL always the data must be in Geographic CRS (EPSG 4326).

The penguin tracking records contain spatial gaps. As usual, this happens with movement data and it is related to the signal of the GPS. There are options to reconstruct the track (path). If you are interested in knowing more about animal tracking and movement data ping me on my LinkedIn profile.

References

[1] Ballard, G., Schmidt, A., Toniolo, V., Veloz, S., Jongsomjit, D., Arrigo, K., Ainley, D. (2019). "Fine-scale oceanographic features characterizing successful Adélie penguin foraging in the SW Ross Sea". Vol. 608: 263–277. DOI: https://doi.org/10.3354/meps12801


Related Articles