Skip to content

Commit bb4a97e

Browse files
committed
add lesson6 activity 3
1 parent ef5f965 commit bb4a97e

File tree

2 files changed

+484
-13
lines changed

2 files changed

+484
-13
lines changed
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"## Working with custom layers"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"In this activity, we will take a look at how to create custom layers that allow you to not only display geo-spatial data but also animate your datapoints over time. \n",
15+
"We'll get a deeper understanding of how geoplotlib works and how layers are created and drawn.\n",
16+
"\n",
17+
"Our dataset does not only contain spatial but also temporal information which enables us to plot flights over time on our map. \n",
18+
"There is an example on how to do this with taxis in the examples folder of geoplotlib. \n",
19+
"https://github.com/andrea-cuttone/geoplotlib/blob/master/examples/taxi.py\n",
20+
"\n",
21+
"**Note:** \n",
22+
"The dataset can be found here: \n",
23+
"https://datamillnorth.org/dataset/flight-tracking"
24+
]
25+
},
26+
{
27+
"cell_type": "markdown",
28+
"metadata": {},
29+
"source": [
30+
"#### Loading the dataset"
31+
]
32+
},
33+
{
34+
"cell_type": "markdown",
35+
"metadata": {},
36+
"source": [
37+
"This time our dataset contains flight data recorded from different machines. \n",
38+
"Each entry is assigned to a unique plane through a `hex_ident`. \n",
39+
"Each location is related to a specific timestamp that consists of a `date` and a `time`."
40+
]
41+
},
42+
{
43+
"cell_type": "code",
44+
"execution_count": 1,
45+
"metadata": {},
46+
"outputs": [],
47+
"source": [
48+
"# importing the necessary dependencies\n",
49+
"import pandas as pd"
50+
]
51+
},
52+
{
53+
"cell_type": "code",
54+
"execution_count": 2,
55+
"metadata": {},
56+
"outputs": [],
57+
"source": [
58+
"# loading the dataset from the csv file\n"
59+
]
60+
},
61+
{
62+
"cell_type": "code",
63+
"execution_count": 3,
64+
"metadata": {},
65+
"outputs": [],
66+
"source": [
67+
"# displaying the first 5 rows of the dataset\n"
68+
]
69+
},
70+
{
71+
"cell_type": "code",
72+
"execution_count": 4,
73+
"metadata": {},
74+
"outputs": [],
75+
"source": [
76+
"# renaming columns latitude to lat and longitude to lon\n"
77+
]
78+
},
79+
{
80+
"cell_type": "markdown",
81+
"metadata": {},
82+
"source": [
83+
"**Note:** \n",
84+
"Remember that geoplotlib needs columns that are named `lat` and `lon`. You will encounter an error if that is not the case."
85+
]
86+
},
87+
{
88+
"cell_type": "code",
89+
"execution_count": 5,
90+
"metadata": {},
91+
"outputs": [],
92+
"source": [
93+
"# displaying the first 5 rows of the dataset\n"
94+
]
95+
},
96+
{
97+
"cell_type": "markdown",
98+
"metadata": {},
99+
"source": [
100+
"---"
101+
]
102+
},
103+
{
104+
"cell_type": "markdown",
105+
"metadata": {},
106+
"source": [
107+
"#### Adding an unix timestamp"
108+
]
109+
},
110+
{
111+
"cell_type": "markdown",
112+
"metadata": {},
113+
"source": [
114+
"The easiest way to work with and handle time is to use a unix timestamp. \n",
115+
"In previous activities, we've already seen how to create a new column in our dataset by applying a function to it. \n",
116+
"We are using the datatime library to parse the date and time columns of our dataset and use it to create a unix timestamp."
117+
]
118+
},
119+
{
120+
"cell_type": "code",
121+
"execution_count": 6,
122+
"metadata": {},
123+
"outputs": [],
124+
"source": [
125+
"# method to convert date and time to an unix timestamp\n",
126+
"from datetime import datetime\n",
127+
"\n",
128+
"def to_epoch(date, time):\n",
129+
" try:\n",
130+
" timestamp = round(datetime.strptime('{} {}'.format(date, time), '%Y/%m/%d %H:%M:%S.%f').timestamp())\n",
131+
" return timestamp\n",
132+
" except ValueError:\n",
133+
" return round(datetime.strptime('2017/09/11 17:02:06.418', '%Y/%m/%d %H:%M:%S.%f').timestamp())"
134+
]
135+
},
136+
{
137+
"cell_type": "code",
138+
"execution_count": 6,
139+
"metadata": {},
140+
"outputs": [],
141+
"source": [
142+
"# creating a new column called timestamp with the to_epoch method applied\n"
143+
]
144+
},
145+
{
146+
"cell_type": "code",
147+
"execution_count": 7,
148+
"metadata": {},
149+
"outputs": [],
150+
"source": [
151+
"# displaying the first 5 rows of the dataset\n"
152+
]
153+
},
154+
{
155+
"cell_type": "markdown",
156+
"metadata": {},
157+
"source": [
158+
"**Note:** \n",
159+
"We round up the miliseconds in our `to_epoch` method since epoch is the number of seconds (not miliseconds) that have passes since January 1st 1970. \n",
160+
"Of course we loose some precision here, but we want to focus on creating our own custom layer instead of wasting a lot of time with our dataset."
161+
]
162+
},
163+
{
164+
"cell_type": "markdown",
165+
"metadata": {},
166+
"source": [
167+
"---"
168+
]
169+
},
170+
{
171+
"cell_type": "markdown",
172+
"metadata": {},
173+
"source": [
174+
"#### Writing our custom layer"
175+
]
176+
},
177+
{
178+
"cell_type": "markdown",
179+
"metadata": {},
180+
"source": [
181+
"After preparing our dataset, we can now start writing our custom layer. \n",
182+
"As mentioned at the beginning of this activity, it will be based on the taxi example of geoplotlib. \n",
183+
"\n",
184+
"We want to have a layer `TrackLayer` that takes an argument dataset which contains `lat` and `lon` data in combination with a `timestamp`. \n",
185+
"Given this data, we want to plot each point for each timestamp on the map, creating a tail behind the newest position of the plane.\n",
186+
"The geoplotlib colorbrewer is used to give each plane a color based on their unique `hex_ident`. \n",
187+
"The view (bounding box) of our visualization will be set to the city Leeds and a text information with the current timestamp is displayed in the upper right corner."
188+
]
189+
},
190+
{
191+
"cell_type": "code",
192+
"execution_count": 10,
193+
"metadata": {},
194+
"outputs": [],
195+
"source": [
196+
"# custom layer creation\n",
197+
"import geoplotlib\n",
198+
"from geoplotlib.layers import BaseLayer\n",
199+
"from geoplotlib.core import BatchPainter\n",
200+
"from geoplotlib.colors import colorbrewer\n",
201+
"from geoplotlib.utils import epoch_to_str, BoundingBox\n",
202+
"\n",
203+
"class TrackLayer(BaseLayer):\n",
204+
"\n",
205+
" # initialize class variables\n",
206+
" def __init__(self, dataset, bbox=BoundingBox.WORLD):\n",
207+
" self.view = bbox\n",
208+
" pass\n",
209+
"\n",
210+
" # implement draw routine\n",
211+
" def draw(self, proj, mouse_x, mouse_y, ui_manager):\n",
212+
" pass\n",
213+
" \n",
214+
" # bounding box that gets used when layer is created\n",
215+
" def bbox(self):\n",
216+
" return self.view"
217+
]
218+
},
219+
{
220+
"cell_type": "markdown",
221+
"metadata": {},
222+
"source": [
223+
"---"
224+
]
225+
},
226+
{
227+
"cell_type": "markdown",
228+
"metadata": {},
229+
"source": [
230+
"#### Visualization with of the custom layer"
231+
]
232+
},
233+
{
234+
"cell_type": "markdown",
235+
"metadata": {},
236+
"source": [
237+
"After creating the custom layer, using it is as simple as using any other layer in geoplotlib. \n",
238+
"We can use the `add_layer` method and pass in our custom layer class with the parameters needed."
239+
]
240+
},
241+
{
242+
"cell_type": "markdown",
243+
"metadata": {},
244+
"source": [
245+
"Our data is focused on the UK and specifically Leeds. \n",
246+
"So we want to adjust our bounding box to exactly this area."
247+
]
248+
},
249+
{
250+
"cell_type": "code",
251+
"execution_count": 10,
252+
"metadata": {},
253+
"outputs": [],
254+
"source": [
255+
"# bounding box for our view on leeds\n",
256+
"from geoplotlib.utils import BoundingBox\n",
257+
"\n",
258+
"leeds_bbox = BoundingBox(north=53.8074, west=-3, south=53.7074 , east=0)"
259+
]
260+
},
261+
{
262+
"cell_type": "code",
263+
"execution_count": 11,
264+
"metadata": {},
265+
"outputs": [],
266+
"source": [
267+
"# displaying our custom layer using add_layer\n",
268+
"from geoplotlib.utils import DataAccessObject\n"
269+
]
270+
},
271+
{
272+
"cell_type": "markdown",
273+
"metadata": {},
274+
"source": [
275+
"**Note:** \n",
276+
"In order to avoid any errors associated with the library, we have to convert our pandas dataframe to a geoplotlib DataAccessObject. \n",
277+
"The creator of geoplotlib provides a handy interface for this conversion."
278+
]
279+
},
280+
{
281+
"cell_type": "markdown",
282+
"metadata": {},
283+
"source": [
284+
"When looking at the upper right hand corner, we can clearly see the temporal aspect of this visualization. \n",
285+
"The first observation we make is that our data is really sparse, we sometimes only have a single data point for a plane, seldomly a whole path is drawn. \n",
286+
"\n",
287+
"Even though it is so sparse, we can already get a feeling about where the planes are flying most.\n",
288+
"\n",
289+
"**Note:** \n",
290+
"If you're interested in what else can be achieved with this custom layer approach, there are more examples in the geoplotlib repository. \n",
291+
"- https://github.com/andrea-cuttone/geoplotlib/blob/master/examples/follow_camera.py\n",
292+
"- https://github.com/andrea-cuttone/geoplotlib/blob/master/examples/quadtree.py\n",
293+
"- https://github.com/andrea-cuttone/geoplotlib/blob/master/examples/kmeans.py"
294+
]
295+
}
296+
],
297+
"metadata": {
298+
"kernelspec": {
299+
"display_name": "Python 3",
300+
"language": "python",
301+
"name": "python3"
302+
},
303+
"language_info": {
304+
"codemirror_mode": {
305+
"name": "ipython",
306+
"version": 3
307+
},
308+
"file_extension": ".py",
309+
"mimetype": "text/x-python",
310+
"name": "python",
311+
"nbconvert_exporter": "python",
312+
"pygments_lexer": "ipython3",
313+
"version": "3.7.0"
314+
}
315+
},
316+
"nbformat": 4,
317+
"nbformat_minor": 2
318+
}

0 commit comments

Comments
 (0)