-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpython_geopandas_folium_point_data.qmd
299 lines (215 loc) · 6.81 KB
/
python_geopandas_folium_point_data.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# Interactive Maps - Point Data
```{r, echo = FALSE}
source("glossary_funcs.R")
glossary_setup()
```
```{r, echo = FALSE, results='asis'}
glossary_setup_style()
```
:::{.callout-note}
Let's first load in the dataset we will be working with.
```{python}
import pandas as pd
import geopandas
gp_list = pd.read_csv("https://github.com/hsma-programme/h6_3b_advanced_qgis_mapping_python/raw/main/h6_3b_advanced_qgis_and_mapping_in_python/example_code/gp_surgery_locations_plus_patient_list_size.csv")
gp_list_gdf = geopandas.GeoDataFrame(
gp_list, # Our pandas dataframe
geometry = geopandas.points_from_xy(
gp_list['result_eastings'], # Our 'x' column (horizontal position of points)
gp_list['result_northings'] # Our 'y' column (vertical position of points)
),
crs = 'EPSG:27700'
)
# Convert to 4326 (lat/long) for working with Folium
gp_list_gdf = gp_list_gdf.to_crs('EPSG:4326')
# Filter out instances with no 'list' (e.g. things like specialist clinics)
gp_list = gp_list[~gp_list['Total List Size'].isna()]
# reduce to the southwest to not overload Folium
xmin, xmax = -6.449974,-2.717735
ymin, ymax = 49.814737,51.246969
gp_list_gdf_sw = gp_list_gdf.cx[xmin:xmax, ymin:ymax]
```
:::
To load in point data we
1. Filter out instances in our dataframe with no geometry (they’ll cause problems!)
2. Make a list from the geometry column to iterate through
3. Use a for loop at add markers to our empty folium map
Let's do step 1 and 2 first - the data prep steps.
```{python}
# Filter out instances with no geometry
gp_list_gdf_sw = gp_list_gdf_sw[~gp_list_gdf_sw['geometry'].is_empty]
# Create a geometry list from the GeoDataFrame
geo_df_list = [[point.xy[1][0], point.xy[0][0]] for point in gp_list_gdf_sw.geometry]
```
Now let's make the map.
```{python}
import folium
# Make the empty map
gp_map_interactive = folium.Map(
location=[50.7, -4.2],
zoom_start=8,
tiles='openstreetmap',
)
# Add markers to the map
for coordinates in geo_df_list:
gp_map_interactive = gp_map_interactive.add_child(
folium.Marker(
location=coordinates
)
)
gp_map_interactive
```
## Custom markers
### Web markers
We can pass in a custom marker from the fontawesome library in the for loop.
```{python}
gp_map_web_icon = folium.Map(
location=[50.7, -4.2],
zoom_start=8,
tiles='openstreetmap',
)
for coordinates in geo_df_list:
gp_map_web_icon = gp_map_web_icon.add_child(
folium.Marker(
location=coordinates,
icon=folium.Icon(icon="user-md", prefix='fa', color="black")
)
)
gp_map_web_icon
```
:::{.callout-warning}
As of Feb 2024, this only supports fontawesome v4 - this link will show you all available icons: https://fontawesome.com/v4/icons/
:::
### Markers stored locally
If we want to use an icon stored on our local machine, we have to do it slightly differently.
While it seems inefficient to remake the icon each time inside the loop, it won’t work otherwise!
```{python}
gp_map_local_icon = folium.Map(
location=[50.7, -4.2],
zoom_start=8,
tiles='openstreetmap',
)
for coordinates in geo_df_list:
custom_icon = folium.features.CustomIcon(
"resources/logos/hsma_logo_transparent_background_small.png",
icon_size=(48,48)
)
gp_map_local_icon = gp_map_local_icon.add_child(
folium.Marker(
location=coordinates,
icon=custom_icon
)
)
gp_map_local_icon
```
## Tooltips
We can also add in tooltips - here we need to use `enumerate(our_list)` instead of just `our_list` so that we get a counter as well.
The first time through the loop, `i` will be 0. This means in the tooltip we pull out the `name` column (the GP surgery name) and then pull back the 0th row of the dataframe, which will correspond to the 0th point in our geo_df list.
:::{.callout-tip}
Remember - Python starts counting from 0, not 1!
:::
We then repeat this for the 1st, 2nd, 3rd and so on.
Tooltips will then appear when hovering over.
:::{.callout-warning}
We use the `.values` attribute of our column to return a list of values.
Accessing the values directly could cause a problem in filtered dataframes as the index will not start from 0. Using `.values` will ensure we always have a list that we can iterate through as expected.
:::
```{python}
#| label: tooltip
gp_map_tooltip = folium.Map(
location=[50.7, -4.2],
zoom_start=8,
tiles='openstreetmap',
)
for i, coordinates in enumerate(geo_df_list):
gp_map_tooltip = gp_map_tooltip.add_child(
folium.Marker(
location=coordinates,
tooltip=gp_list_gdf_sw['name'].values[i]
)
)
gp_map_tooltip
```
### More complex tooltips
We could use an f-string to create a more complex tooltip from multiple columns of our dataframe.
:::{.callout-tip}
Use \<br> to get a line break. It’s sort of the web equivalent of \\n.
:::
```{python}
#| label: complex_tooltip
gp_map_complex_tooltip = folium.Map(
location=[50.7, -4.2],
zoom_start=8,
tiles='openstreetmap',
)
for i, coordinates in enumerate(geo_df_list):
gp_map_complex_tooltip = gp_map_complex_tooltip.add_child(
folium.Marker(
location=coordinates,
tooltip=f"{gp_list_gdf_sw['name'].values[i].title()}<br>List Size: {gp_list_gdf_sw['Total List Size'].values[i]:.0f}"
)
)
gp_map_complex_tooltip
```
## Heatmaps
By using a Folium plugin, we can create a heatmap from point data.
```{python}
from folium import plugins
gp_map_heatmap = folium.Map(
location=[50.7, -4.2],
zoom_start=8,
tiles='openstreetmap',
)
heatmap_layer = plugins.HeatMap(
geo_df_list,
radius=15,
blur=5)
heatmap_layer.add_to(gp_map_heatmap)
gp_map_heatmap
```
### Radius and blur
Changing the radius and blur parameters can have a significant impact on the display.
```{python}
from folium import plugins
gp_map_heatmap_2 = folium.Map(
location=[50.7, -4.2],
zoom_start=8,
tiles='openstreetmap',
)
heatmap_layer = plugins.HeatMap(
geo_df_list,
radius=10,
blur=5)
heatmap_layer.add_to(gp_map_heatmap_2)
gp_map_heatmap_2
```
```{python}
from folium import plugins
gp_map_heatmap_3 = folium.Map(
location=[50.7, -4.2],
zoom_start=8,
tiles='openstreetmap',
)
heatmap_layer = plugins.HeatMap(
geo_df_list,
radius=10,
blur=10)
heatmap_layer.add_to(gp_map_heatmap_3)
gp_map_heatmap_3
```
### Initial zoom with heatmaps
And zooming out a long way can turn it into a complete mess regardless of your settings!
```{python}
from folium import plugins
gp_map_heatmap_4 = folium.Map(
location=[50.7, -4.2],
zoom_start=5,
tiles='openstreetmap',
)
heatmap_layer = plugins.HeatMap(
geo_df_list,
radius=15,
blur=5)
heatmap_layer.add_to(gp_map_heatmap_4)
gp_map_heatmap_4
```