Drawing flight paths on maps with cartopy (bonus: on Google Maps tiles)#
We spent a lot of time tracking down suspicious planes, don't we want to dress it all up with some visualizations of where they're flying?
Imports#
import pandas as pd
import matplotlib.pyplot as plt
pd.set_option("display.max_columns", 200)
%matplotlib inline
Read in our data#
We're doing to be using two datasets for this.
First, we'll need our plane features - how height they were flying, how fast they were flying, how much they were steering, etc etc. We'll use this to find the codes for interesting planes.
Second, we'll need all of the original flight readings. We'll take their readings for the planes we're interested in and draw lines between them, essentially recreating the flight paths.
# Feature data
features = pd.read_csv("data/opensky-features.csv")
features.head(2)
# Original data
df = pd.read_csv('data/flights_data4-1529539200-cleaned.csv', dtype={'icao24': 'str'})
df.head(2)
Which planes are we interested in?#
If a plane is steering to the left or right a lot, it's probably going in circles! Let's take the top 10 planes that have high steer2
values as our suspicious planes.
suspicious = features.sort_values(by='steer2', ascending=False).head(10)
suspicious
We'll then find the readings of those interesting planes.
The features have a code in capitals -
A26A04
- while the readings have lowercase -a26a04
- so we'll need to adjust for that.
readings = df[df.icao24.isin(suspicious.icao24.str.lower())]
readings.head()
Then we can organize them into flights based on the firstseen
column.
flights = readings.groupby(['firstseen', 'icao24'])
len(flights)
Draw the flight paths#
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import shapely.geometry as sgeom
for name, flight in flights:
#
fig = plt.figure(figsize=(10,10), facecolor='white')
ax = fig.add_subplot(projection=ccrs.LambertConformal())
plt.title(name)
# Set the boundaries around the map
ax.set_extent([
flight.longitude.max(), flight.longitude.min(),
flight.latitude.min(), flight.latitude.max()
], crs=ccrs.Geodetic())
# Draw the path of the fligt
track = sgeom.LineString(zip(flight.longitude, flight.latitude))
ax.add_geometries([track],
ccrs.PlateCarree(),
facecolor='none',
edgecolor='red',
linewidth=2)
Those are some pretty exciting flight paths! But... where are they? We can do better if we fill the background with Google Maps tiles.
Map tile backgrounds for cartopy#
We hit a rate limit pretty quickly if we use them, though, so we need to jump through a couple hoops to make sure we cache them. The "couple hoops" means "run this code below."
import os
import types
import cartopy.io.img_tiles as img_tiles
import requests
import PIL
class CachedTiler(object):
def __init__(self, tiler):
self.tiler = tiler
def __getattr__(self, name):
attr = getattr(self.tiler, name, None)
if isinstance(attr, types.MethodType):
attr = types.MethodType(attr.__func__, self)
return attr
def get_image(self, tile):
tileset_name = '{}'.format(self.tiler.__class__.__name__.lower())
cache_dir = os.path.expanduser(os.path.join('~/', 'image_tiles', tileset_name))
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
tile_fname = os.path.join(cache_dir, '_'.join(str(v) for v in tile) + '.png')
if not os.path.exists(tile_fname):
response = requests.get(self._image_url(tile),
stream=True)
with open(tile_fname, "wb") as fh:
for chunk in response:
fh.write(chunk)
with open(tile_fname, 'rb') as fh:
img = PIL.Image.open(fh)
img = img.convert(self.desired_tile_form)
return img, self.tileextent(tile), 'lower'
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import shapely.geometry as sgeom
import cartopy.io.img_tiles as cimgt
# Initialize our Google Maps tiles
actual_tiler = cimgt.GoogleTiles()
imagery = CachedTiler(actual_tiler)
for name, flight in flights:
fig = plt.figure(figsize=(10,10), facecolor='white')
ax = fig.add_subplot(projection=ccrs.LambertConformal())
plt.title(name)
# Set the boundaries around the map
ax.set_extent([
flight.longitude.max(), flight.longitude.min(),
flight.latitude.min(), flight.latitude.max()
], crs=ccrs.Geodetic())
# Draw the background
ax.add_image(imagery, 9)
# Draw the path of the fligt
track = sgeom.LineString(zip(flight.longitude, flight.latitude))
ax.add_geometries([track],
ccrs.PlateCarree(),
facecolor='none',
edgecolor='red',
linewidth=2)
Let's put them all on one graphic#
Just in case we wanna get crazy, let's put them all on one graphic.
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import shapely.geometry as sgeom
import cartopy.io.img_tiles as cimgt
import math
# Initialize our Google Maps tiles
actual_tiler = cimgt.GoogleTiles()
imagery = CachedTiler(actual_tiler)
# 3 per row, calculate number of rows
num_cols = 3
num_rows = math.ceil(len(flights) / num_cols)
# Make a 15x15 graphic
plt.figure(figsize=(10, num_rows * 3), facecolor='white')
for i, (name, flight) in enumerate(flights):
ax = plt.subplot(num_rows, num_cols, i+1, projection=imagery.crs)
# Set the boundaries around the map
ax.set_extent([
flight.longitude.max(), flight.longitude.min(),
flight.latitude.min(), flight.latitude.max()
], crs=ccrs.Geodetic())
# Draw the background
ax.add_image(imagery, 9)
# Draw the path of the fligt
track = sgeom.LineString(zip(flight.longitude, flight.latitude))
ax.add_geometries([track],
ccrs.PlateCarree(),
facecolor='none',
edgecolor='red',
linewidth=2)