# x- T( _- U& Y) X- d/ | Folium 简介
4 c) Z# k6 k3 M' B 作为 Python 的一个可视化工具包 Folium,它通过 Leaflet 的地图服务,可以在 Jupyter Notebook 上实现可视化的地理位置作图,制作各种各样精美的地图信息。它不仅可以针对某个经纬度进行地理位置的可视化操作,还能够根据实时的人群地理位置信息来构建静态与动态热力图,甚至还能够针对经纬度的数量来进行必要的聚类可视化。本文将会基于新加坡的地图,对 Folium 的一些功能做简要的介绍,对此工具有兴趣的读者可以参阅 Folium 的官方文档。
# U% f" f; C% R3 D 创建地图
( d ?/ A' ?/ f N: E 通过 Folium 工具,可以直接作出一张世界地图,其代码也十分地简洁明了。 2 N8 o6 f5 N( Y! s: q
import folium+ ?7 G. }* s8 _$ k) t) U) b% A
%matplotlib inline
! _9 z6 l7 z3 H9 N8 z( A; a% }
0 P- P% T' |, l3 |; C& o' E import webbrowser
# H- P% Y. P8 y+ ^3 W+ d; i/ H, v; c' \1 m
print(folium.__version__)
7 X* d$ H: c- ]- o1 K* v
4 Q9 {+ g6 C6 e4 l # define the world map1 Y5 ?/ T3 N+ Z( p+ }: A% O' p5 i
world_map = folium.Map()
7 x: L+ j) J5 W; V. y2 `4 M2 h # display world map
0 v2 o& p: p7 z5 w x world_map
: H/ }# K ~( Q$ k' @, ? 5 m% v0 }3 i# z) W7 ?1 j
世界地图除了能够作出一张完整的世界地图之外,通常程序员最常见的需求是针对某个或者某一些经纬度,来作出一张局部地图。地图中不仅需要包括经纬度信息,也需要有街道信息等必要的内容,甚至需要对经纬度的标记做一些必要的定制化工作。 ) T* M/ O! [9 l) F: J$ D9 G
在初始化一张地图的时候,需要指定它的经纬度信息,也就是 location 的位置。也需要根据需要放大的尺寸来指定相应的 zoom_start 值。另外,tiles 是 str 型,用于控制绘图调用的地图样式,默认为OpenStreetMap,也有一些其他的内建地图样式,如Stamen Terrain,Stamen Toner。
1 i) D7 r" w8 O/ d& z0 s2 ~ 有的时候需要在地图上标识出相应的经纬度,此时需要使用 folium.Marker 函数。其使用方法就是直接输入相应的经纬度信息,以及 icon 的形状(例如 cloud, info-sign 等);除此之外,还可以对其颜色进行标记,一般是对参数 color 进行调整即可。 ) p: h. e" o8 i# ?! J
另外,在某些经纬度上,还可以使用“点击-弹出“的控件 tooltip 和 popup,一旦鼠标指向该位置,就会呈现出相应的弹出信息。
/ t' Z) ^) E5 L9 D( u # latitude and longitude in Singapore city
4 h3 g, Z7 p4 l9 k: j1 | coordinate_sentosa = [1.248946, 103.834306]
6 H0 P7 g6 V L coordinate_orchard_road = [1.304247, 103.833264]
# [3 ]8 o' ?1 i/ [1 D# r* B/ W coordinate_changi_airport = [1.357557, 103.98847]( W- O* j3 t$ C$ X7 w0 J0 x
coordinate_nus = [1.296202,103.776899]
* Y( S/ Y/ @" g7 H2 L coordinate_ntu = [1.34841, 103.682933]
% B8 `8 S# A: \8 c coordinate_zoo = [1.403717, 103.793974]8 ^# n* U) S- N/ h# m; A
coordinate_ang_mo_kio = [1.37008, 103.849523]
& \3 E- A/ g5 K" O( K/ ]# ~) ]6 O coordinate_yi_shun = [1.429384, 103.835028]
% X5 p( f% y% n7 E; H: z- A
% g) K0 b! h m- q1 P1 a # icon
/ y( C; G- \ a" r icon_cloud = "cloud"
! `6 f+ U) s6 G, O9 `: P6 ` icon_sign = "info-sign"8 i) X& D+ h0 C9 }) T. j. D
* ^" I9 s3 M1 }' `4 z
# define the city map
% k( r4 x) ?% V& v/ ?5 X # tiles in {OpenStreetMap, Stamen Terrain, Stamen Toner, Mapbox Bright}
% |, x5 z& P5 W0 A city_map = folium.Map(
, e4 F6 g0 y( @8 i! G location=coordinate_orchard_road,/ k; o* O+ C. V7 z- z* U4 m
zoom_start=11,9 Q! y ?9 T A% J+ k! K; c1 |: k
tiles=OpenStreetMap), S5 u9 w+ t# y# }2 R4 g' Z4 x
- _, p2 {/ y; K/ a
# add marker in the city map
9 r: f! _5 G3 P+ ] folium.Marker(8 q2 \: h2 i/ ^' @# q
coordinate_sentosa,
* j+ R( I: Z1 I' A icon=folium.Icon(color=blue)2 A& i) y5 k! X, b
).add_to(city_map)! f; K T {( l2 ]$ s
folium.Marker(9 v& R- z4 h; }, B+ m' O" ]
coordinate_orchard_road,5 L* ]* t# [' u( r8 l1 T
icon=folium.Icon(color=green, icon=icon_cloud)' ~( f+ G" r- Y( C$ h9 Z( f
).add_to(city_map)
! S4 \/ ]; q( K2 ?) ^0 O/ o
& A4 {3 o; h3 _$ d0 D- f% H3 e1 [ # add popup1 X) w. w1 Z! |0 L) g/ k7 E
folium.Marker(
x* \& N5 g0 T4 x/ m* i coordinate_changi_airport,8 R5 C! v Y& }/ d
popup=Changi Airport,' `( p# L* B' L6 N% w7 c: y
icon=folium.Icon(color=red, icon=icon_sign)4 y6 {. E: \2 v/ S
).add_to(city_map)7 E h$ S- x3 O! I
7 B5 }2 Z4 ?, M J; G
# add tooltips and popup
& d- y- P# Q4 v( S3 P; @ tooltip = "Click me!"
' r2 A- \" B) j; t& f# f) Y1 I: a; d8 t) A
folium.Marker(5 T' m$ q( e4 R5 x
coordinate_nus,
9 r) Z: c4 M2 x' o# \7 M( N$ I popup="<i>National University of Singapore</i>",& v6 O+ t8 a& c1 Y1 v* M" h8 w( U
tooltip=tooltip- Q" S. L* S; h
).add_to(city_map)
5 K, D7 H; x- W1 W5 W% v# X1 A% h; |: W. ^8 t$ T. N. G
folium.Marker(
2 q% L0 K U+ W2 m# v coordinate_ntu,
; j9 V) g- k$ |" {& V' @' f popup="<b>Nanyang Technological University</b>",5 a/ M* U _6 L4 q' B) q* p
tooltip=tooltip
$ V% S; C+ Z. _: b8 M ).add_to(city_map)1 {# U% o8 ?1 ?2 h4 h7 R7 X
4 b/ [ l X. C5 g
# display city map8 x/ u& s6 d2 I E2 v2 i* ?
city_map
% b- C5 M# h7 g Folium 的经纬度作图(OpenStreetMap)Folium 的经纬度作图(Stamen Terrain)Folium 的经纬度作图(Stamen Toner)有的时候,我们只知道某个地点,但是并不知道相应的经纬度数据,此时,只需要使用 folium.LatLngPopup() 就可以在鼠标指向位置上呈现相应的经纬度值,可以方便的查阅和使用。 3 w) j) M' I. d
# define the city map
1 j- l# Y, `* I( b' ^* o- j city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11), F/ f* H8 q" o- F
- d* f+ ~* k8 a
# 在地图中添加经纬度, add latitude and longitude in the map when click6 O+ I# U# z1 f' r9 S
city_map.add_child(folium.LatLngPopup())
! S6 W. M3 {( Q! R( v! H' ?1 |( ?/ }8 c8 k; P4 p2 z
city_map & b5 W, J# }" Y+ X1 x6 \
! `% _4 N) K4 S# x
几何形状
1 @ Q2 S6 N' S 在作出关键的经纬度点之后,有的时候我们需要作出相应的几何图形将其显示得更加清楚。Folium 提供线段相连,多边形,圆形,矩形等诸多图形,只需要使用相应的函数即可。
! U# `) ^4 Q- o$ }+ { # define the city map
. p1 j8 Y! k' b+ l/ c' O: a. L, v city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)& U. f6 w/ ?8 ]+ Y# }
+ p1 u# Q4 m! ~7 ~+ A$ } # 在地图中如何添加形状$ ]7 S& x2 i+ O5 D" f W
# 多条边
7 q6 E6 E+ O& P0 S& t points_1 = [
- Y' M1 z7 Q. n- s% D( g coordinate_ntu,( m( @5 R9 ^- G5 s: ^3 X3 R+ `; j; h
coordinate_nus,
. V ]& ^! x# ]$ J/ L& T coordinate_zoo/ G8 J |$ i0 @# W. U* x# ^% k
]% J3 s( a( i3 M; V, s$ W
7 V* s- ?9 d# Y) R # 在 city_map 中添加多条边,第一种添加方式
0 F+ g/ i6 k8 ^7 C5 B- ]0 q city_map.add_child(folium.PolyLine(7 g( q2 T3 G; T- s: U- W
locations=points_1, # 坐标列表
; {, Q- f- g& H! K. ~" W3 g weight=3, # 线条宽度
! {! ]4 ?9 ?: v5 X5 ]4 K9 s color=gray))
! y9 J* ?1 d; d) e' s! D% n1 O( E; n5 Z" g- ?1 B! c/ z
# 在 city_map 中添加多条边,第二种添加方式5 W- L+ r) ~4 ]) k; d
folium.PolyLine(- h* Z9 T2 F# A: n( E4 m$ {2 B+ `
locations=points_1, # 坐标列表
& n" Z: k/ E7 d$ m4 f- X weight=3, # 线条宽度
+ E3 ] T7 Q) Q* L8 q color=gray).add_to(city_map)( E, x& W& u/ X% o. i/ o* K
/ |( n8 V+ P+ Z# T0 ~# c # 多边形+ U8 ^" w" p+ ]! s
points_2 = [
, }* ? _7 l+ G4 C/ m) v coordinate_orchard_road,
' D- B) z# l( J) ?3 _/ H coordinate_sentosa,
; t- m; D+ ?1 X5 |& O* c/ C# b coordinate_changi_airport
5 Y8 F- b# y$ B+ {5 A# q9 G: f ]
$ [$ K& S9 u4 g2 {- e6 j i
5 Y3 N( E7 e7 P* g4 ]9 ? city_map.add_child(folium.Polygon(/ r1 t* t. ] e) H# I9 l; k) n
locations=points_2, # 坐标列表
" N$ f2 q7 G$ y0 s weight=3, # 线条宽度
% t! ^- ?" x; r# A# l8 y0 _ color=yellow))
2 j* L4 n3 p- \* e \1 l( a& ^) Y9 z, h
# 矩形9 D, z) W% M7 ?* S; L9 z
bounds = [
# Y" }3 X) p8 `' z4 F9 J coordinate_ang_mo_kio,6 `- Y1 H/ E9 K- X1 \1 V
coordinate_yi_shun6 a/ J! K7 R4 r6 h1 Z
]( m8 z/ \+ P8 W
6 a7 y' w4 p' h5 X, H( {
city_map.add_child(folium.Rectangle(
2 E- N* x& @' n# X2 S% X. Z2 }7 n bounds=bounds, # 坐标列表, Latitude and Longitude of line (Northing, Easting)6 a: m* A2 F5 K" m
weight=2, # 线条宽度; b8 b/ f5 P0 b2 z9 j$ Y, S
color=blue)), f5 @& Z: R1 P
; ^* h0 c" [$ V. `% i8 S/ l: c
# 圆形, circle, radius units meters3 b2 @/ A4 Q6 G4 L: z, }& D# W
folium.Circle(: G2 t2 d! w" L4 q* S) Y
radius=1000,
, j: V# d8 R* a7 e location=coordinate_nus,
* d) J, L3 w2 T5 J1 _# T popup="National University of Singapore",- e! M- r3 T# C% V5 N) e6 Q# X8 E- r
color="crimson",
* q; a `0 Z/ _, _8 B7 ]) @' t4 c fill=False,! R* b" l) r i, `- N
).add_to(city_map)
3 s% u) @& q) m1 n
# @, d$ z8 E' `! U # 圆形, circle, radius units pixels
6 ~" C8 i8 G- f9 i8 B8 f/ S folium.CircleMarker(9 d9 q7 U- Y* |9 ~( \
location=coordinate_ntu,. h% w i; I; J9 E; I! F4 i
radius=30,# I/ v3 @1 `6 M
popup="Nanyang Technological University",
& ^3 w( H( [" S+ s2 G color="#3186cc",
( ]' A+ f* f* I0 B fill=True,
% x& U7 |! L7 Q5 h5 a fill_opacity=0.3, # 透明度+ }0 I( {2 G% X; `' S7 i! l
fill_color="#3186cc",
% D4 ^; w6 ] m+ B! ?2 S ).add_to(city_map)& d' u: |# k+ r
( R, r* t. ]( ~5 b3 n
city_map
+ ]0 v1 C9 L+ A0 P/ o Folium 中的画出各种形状热力图$ g4 b8 m* {8 L5 m
在实际使用经纬度信息的时候,通常来说会针对某个 APP 或者多款 APP 的实时经纬度信息来获取路况的拥挤程度,景区的人流量等信息,在这种情况下,就可以做出热力图。通过实时的热力图信息,我们可以获得相应的人流量信息进行必要的数据分析工作。 5 j4 n I6 H: D+ w0 c) o, x% c( Y. R
import numpy as np
$ l# [: p \1 G6 v# z; T7 M% V from folium.plugins import HeatMap
4 ^' B5 e7 |7 Q# m+ V; W! ]' e
, w# P2 U% o2 Z6 d5 M1 F # define the city map* U* p& r5 z* a, ]* b( ~! S% f
city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)% h. o" r- W! v" @ y3 V
5 |7 B2 i' J% V# i' k$ v7 K! R # 构建随机数据" _0 B ~( W k0 p1 l
data = (- }+ A* Q1 E' E; J; B
np.random.normal(" m8 X4 w& y" w# z: }6 d5 L& W! H+ C
size=(10, 3)) * 0.03 * np.array([[1, 1, 1]]) +
H, q: _3 U4 ~% G8 G np.array([[coordinate_orchard_road[0], coordinate_orchard_road[1], 1]])
* R3 _( [+ Q: O7 P5 c: A ).tolist()8 @9 F6 P+ s* N: e$ r- v
* K9 s4 m T) c+ V
city_map.add_child(HeatMap(data=data))) H0 S |. [& `/ \
city_map
6 i* w% R# k* W. R# M $ M, p9 x2 ?% c! n
除了单张图的热力图之外,Folium 还能够计算一段时间的连续热力图信息。
/ x. |( u: y. U$ f1 h3 ] import numpy as np
* e7 o3 [' W3 ^5 c/ I7 |/ O from folium.plugins import HeatMapWithTime
$ _+ l; y `) w3 ]* k5 }7 a7 c, L
/ _! Z8 L+ T5 ]* n2 r& P5 N9 T # define the city map6 Z: _ M' R1 q# y: I5 L8 H: Z
city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)0 c0 [4 W: U* |" @
% A; w( k* [3 }& F
# 使用 numpy 建立初始数据
) }8 R9 G8 L# u f; D initial_data = (np.random.normal(size=(200, 2)) *
. N# [2 V% c" ^ np.array([[0.02, 0.02]]) +
' q% j: q8 D0 Q6 F" g np.array([coordinate_orchard_road]))
3 a) j: C6 z1 D) o) c/ ]. z
: c7 U% f; @8 S # 建立连续的数据
) u3 A ~% J ]; _ data = [initial_data.tolist()]
F$ s$ N. U- T7 f4 Q% u* n8 X4 v8 E for i in range(20):# Q, s# n! J* E( A8 w- v2 F0 A
data.append((data[i] + np.random.normal(size=(200, 2)) * 0.001).tolist())0 a8 R/ j8 H* l/ R. q1 H1 c1 H
* E: F2 v6 C; | W
# 显示连续的热力图
1 w5 p. T5 c: O% P city_map.add_child(HeatMapWithTime(data)), L& Q: s, R3 A* q
city_map 6 l/ t: d; v) I- j, b
7 i7 U; ?! e$ E 经纬度点的聚类$ E9 d2 o3 t" ^+ u9 Z; v! n. J
除了热力图能反映人流量的信息,基于地理位置的聚类算法同样能够反映一个地区的拥挤程度,Folium 的 MarkerCluster() 函数可以对一个区域中的点来做聚类,在地图中可以放大和缩小,从而知道局部最拥挤的点在哪里了,最稀疏的区域是哪里。 9 N, o. R! X, v. {! Y( ]
from folium.plugins import MarkerCluster
0 E# |5 L8 b% V7 `- }
% x# x' H' L- w # define the city map
D1 f9 |" y9 }3 [ city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)0 i3 u% w4 f0 X7 V
; U9 I3 a* L2 l2 H/ ~( k# I # 经纬度的聚类) I5 Z. R" t( T; w6 U, M3 q
# 在 NUS 的经纬度附近随机生成 100 个点;
; s( k# j- `) u w. L5 x data = (
+ u4 C3 `8 o7 u0 b! Y( | np.random.normal(size=(100, 2)); P0 g7 l) h4 a% z, P
* np.array([[0.001, 0.001]]) +2 @. H, ?0 S/ @- X2 y0 T) r; s N' G4 g
np.array([coordinate_nus]))1 K& I9 u) [& e B: i" n8 S J3 ]
, P+ }* S- y, {, l# c* r
# create a mark cluster object
' F; |4 T8 k0 z& U2 S- H, s d marker_cluster = MarkerCluster().add_to(city_map)# a2 S8 P8 N4 O& u0 H
7 w$ J3 u, X( }" d9 Y
# 将这些经纬度数据加入聚类
' j, c/ G/ B' H0 }* N6 K/ Z for element in data:
/ v/ z4 M. p' f1 y* H folium.Marker(location=[element[0], element[1]],icon=None).add_to(marker_cluster)( N/ L7 ^7 L6 o. V9 j2 b6 x
4 l0 O% N& w0 [7 h. {: [. y
# add marker_cluster to map
0 J$ r2 Y" k0 ] city_map.add_child(marker_cluster)
7 E! ?- w, R0 v1 p, i
' b! i: O6 _7 [1 R: z # 作图& J8 \% }' ?4 p# h
city_map n$ W. w* ?! k% d' ^' d" }! @7 Q: Y3 q
) n& Y- W. m# d3 R 以上就是关于 Folium 的基础内容,有兴趣的读者可以自行参阅 Folium 的官方文档。
0 U" S, U9 ~- ^5 P9 u( M$ R; Y4 j3 W 参考文献
0 f+ J* b: L. u Folium 官方文档:Folium - Folium 0.12.1 documentation 8 F$ b" H9 e, `: V
4 }5 d# B# r6 l8 v3 l( J4 Y
$ Y: ~' P7 o' g! T6 A- `0 K- |1 D/ |6 G/ `6 H, e! V- D
7 c7 N% h2 a) V, ]% M& D8 F
|