9 O: z( `( ~ n# U
Folium 简介
# z! a/ h# `5 s+ }* M9 O' f4 U& x 作为 Python 的一个可视化工具包 Folium,它通过 Leaflet 的地图服务,可以在 Jupyter Notebook 上实现可视化的地理位置作图,制作各种各样精美的地图信息。它不仅可以针对某个经纬度进行地理位置的可视化操作,还能够根据实时的人群地理位置信息来构建静态与动态热力图,甚至还能够针对经纬度的数量来进行必要的聚类可视化。本文将会基于新加坡的地图,对 Folium 的一些功能做简要的介绍,对此工具有兴趣的读者可以参阅 Folium 的官方文档。
9 U& N3 Z) m( T4 j9 Q9 { 创建地图
. \9 ^+ S/ R# ^* g+ V 通过 Folium 工具,可以直接作出一张世界地图,其代码也十分地简洁明了。
2 L+ ~$ w/ @5 H7 C: Z import folium0 M2 _1 }1 O. `
%matplotlib inline* S& K \7 _! {" |% X! g8 o
D/ g+ }& o! i1 l3 ?
import webbrowser
) D9 B+ ~- V& D0 x1 D4 G9 C s: F4 t6 c/ j
print(folium.__version__)- Q/ z/ R# N: p% z, f
/ r. q) a3 Y0 ]7 M9 b
# define the world map7 H7 @2 M+ P. g3 T" _* j9 Y3 Z+ y
world_map = folium.Map()
/ p8 `$ o5 L- C* X/ X% Q4 H/ Y # display world map5 ]# K9 J" [6 d" }+ i
world_map0 G2 V6 p! A& {
6 h6 [* M# F& L, M3 I4 F9 Z 世界地图除了能够作出一张完整的世界地图之外,通常程序员最常见的需求是针对某个或者某一些经纬度,来作出一张局部地图。地图中不仅需要包括经纬度信息,也需要有街道信息等必要的内容,甚至需要对经纬度的标记做一些必要的定制化工作。
- M9 {# D4 ~6 l) i 在初始化一张地图的时候,需要指定它的经纬度信息,也就是 location 的位置。也需要根据需要放大的尺寸来指定相应的 zoom_start 值。另外,tiles 是 str 型,用于控制绘图调用的地图样式,默认为OpenStreetMap,也有一些其他的内建地图样式,如Stamen Terrain,Stamen Toner。
/ U0 s1 B* P% p G- {4 d( j 有的时候需要在地图上标识出相应的经纬度,此时需要使用 folium.Marker 函数。其使用方法就是直接输入相应的经纬度信息,以及 icon 的形状(例如 cloud, info-sign 等);除此之外,还可以对其颜色进行标记,一般是对参数 color 进行调整即可。
* [" B0 C2 `! M6 f8 ` 另外,在某些经纬度上,还可以使用“点击-弹出“的控件 tooltip 和 popup,一旦鼠标指向该位置,就会呈现出相应的弹出信息。 2 Y4 c# J3 d6 G T( V, g6 {
# latitude and longitude in Singapore city
& q/ P8 l* W/ Q. J3 k" h, k coordinate_sentosa = [1.248946, 103.834306]
! J! X! q: a# V0 ] coordinate_orchard_road = [1.304247, 103.833264]
! ?- h0 j8 ~; {/ h6 [. a4 C/ I- o coordinate_changi_airport = [1.357557, 103.98847]
/ O, R2 {; m# A+ Y+ d" E+ S7 g coordinate_nus = [1.296202,103.776899]
6 H. y& }. U9 y- [* j5 l" ] coordinate_ntu = [1.34841, 103.682933]0 q9 }% X1 Z6 J3 r
coordinate_zoo = [1.403717, 103.793974] {0 V8 M( w6 q$ p
coordinate_ang_mo_kio = [1.37008, 103.849523]$ u1 Q/ R9 }1 A, O, y- B9 `
coordinate_yi_shun = [1.429384, 103.835028]
$ q1 k% {8 B1 d# z& j
3 P' {3 z0 u* [: M3 C( z# g# K9 q # icon0 D7 V7 Z* E- `+ t) B0 v4 I6 D
icon_cloud = "cloud"
- |3 G# j) f- @; b6 H icon_sign = "info-sign"# y p a; B# z! c" C
7 [, C, s8 A) A # define the city map) W1 V" z9 ?+ J% g: x
# tiles in {OpenStreetMap, Stamen Terrain, Stamen Toner, Mapbox Bright}
7 `* P5 d1 B' q% G! F3 ~ city_map = folium.Map(" x2 g1 ?( Z* \: ^2 g/ a' Y
location=coordinate_orchard_road,, g; B: m9 P! C6 \' u
zoom_start=11,$ U! G/ x o( r; f& P( t
tiles=OpenStreetMap)
/ q" A3 w: L+ g
B% J& \- V6 b& R # add marker in the city map
* |6 ^( r4 Q: i/ X$ H7 Q folium.Marker(6 N: P/ Q5 \, v, S
coordinate_sentosa, B8 b c3 }' d o; T' G
icon=folium.Icon(color=blue)3 E2 h4 I, Q$ g; C- [8 @7 \: O
).add_to(city_map). Y3 E) u- _9 R" ]9 L
folium.Marker(
0 w' ?- G, r6 r# Q, `* f2 a' P coordinate_orchard_road," r( l8 O$ U; {$ l
icon=folium.Icon(color=green, icon=icon_cloud)3 k: I H/ s2 r. m
).add_to(city_map)
( `# z" y0 T |3 h9 B
! z( O$ s t5 ` # add popup+ {8 i& l( O5 `; ^! r( e2 e8 H" p
folium.Marker(
5 _' w& F' s) V+ U! @6 Q6 Y$ P9 f coordinate_changi_airport,
7 v1 k- g& h- f+ R) b popup=Changi Airport,6 m) P1 p; O( P B+ G8 H( N
icon=folium.Icon(color=red, icon=icon_sign)
1 @9 e& A8 \1 `7 }: C ).add_to(city_map)7 y7 ~8 E" ^' y& F2 B& H
6 y- T& l0 _/ n5 Y) {: W! \) e
# add tooltips and popup
! `4 ?) `! \" a, | tooltip = "Click me!"
& w5 _" O+ x$ k( n6 c% q
1 n4 G% B# O/ E' ~ folium.Marker(
, e7 g( q" x5 R$ D coordinate_nus,7 H% c$ X+ `. E6 S
popup="<i>National University of Singapore</i>",9 B- _- B) j! S& K. B
tooltip=tooltip7 K' a4 W" B( Y) v
).add_to(city_map)2 l9 L& m# L3 z1 M
' l5 x6 T- D- t- X" c0 C folium.Marker(
. O" k" B, U7 n4 U+ E% E$ U coordinate_ntu,
* |+ Y+ {5 |$ `- I: ?; z popup="<b>Nanyang Technological University</b>",
}/ ]1 N" ]; \# `/ M tooltip=tooltip
: c3 i' j! E4 I, a2 d! v1 _% O ).add_to(city_map)
6 S! B: L. b. |7 H5 R$ F
, _! k. @- E) z1 t* }( e# |, D # display city map
5 ]2 s( U& O/ Q) c city_map
( w; Z; @% ^0 q7 M. V Folium 的经纬度作图(OpenStreetMap)Folium 的经纬度作图(Stamen Terrain)Folium 的经纬度作图(Stamen Toner)有的时候,我们只知道某个地点,但是并不知道相应的经纬度数据,此时,只需要使用 folium.LatLngPopup() 就可以在鼠标指向位置上呈现相应的经纬度值,可以方便的查阅和使用。
$ f( {9 }8 \3 `( _" H9 a # define the city map3 a& v W" Y4 T, ]1 W& m4 _# {5 D& s
city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)
5 y6 ~7 e" X% ?: F$ X/ @
2 k$ [: a f; u8 Y # 在地图中添加经纬度, add latitude and longitude in the map when click
' d9 j) j. }1 x, W, h city_map.add_child(folium.LatLngPopup()) ?1 |* c- w; [6 g9 R9 }
5 Q( f0 H% a6 N7 Q$ d6 j$ F
city_map
; ?( v" Q3 P1 w0 L0 q0 I5 j1 F & _, _. l9 R+ g
几何形状
0 c$ I# J2 e; Y8 W K 在作出关键的经纬度点之后,有的时候我们需要作出相应的几何图形将其显示得更加清楚。Folium 提供线段相连,多边形,圆形,矩形等诸多图形,只需要使用相应的函数即可。
, {1 O* O6 _6 ^$ l" C- x) ] # define the city map
- W% V" }% K0 |" m- S city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)
# k- I/ @) {/ Z0 E: B9 c. F3 d2 n) s" E0 a( m7 W. A
# 在地图中如何添加形状
4 t) _" @* g _" ]: C # 多条边
/ u3 G2 d" _; m0 m+ U7 u- t points_1 = [0 l& v% y3 G+ [) ]6 \8 v5 I6 ?" F
coordinate_ntu,
/ d7 {; u/ c9 A% y# J coordinate_nus,+ _% z ?4 X' ^$ ~( n
coordinate_zoo
& B, G% M- r3 Z( d! ^; W, [ ]) y5 c" f4 l! [
; K8 C4 y6 ^# D( F
# 在 city_map 中添加多条边,第一种添加方式
: o; L# x, ^/ Q( p+ @* x' m3 P city_map.add_child(folium.PolyLine(
& K0 @2 g- e) C locations=points_1, # 坐标列表
6 R. i3 I! ^) x4 D weight=3, # 线条宽度
* u6 L( i5 ^+ s0 t* \ color=gray))
2 ?8 m- t$ O8 w# _8 B% b. b- H# p' `' l) t: {- O1 N* K
# 在 city_map 中添加多条边,第二种添加方式3 `3 m# ?3 ]" S, w
folium.PolyLine(
3 l- J9 {6 A/ a' j" W+ \ locations=points_1, # 坐标列表
$ Q- @4 @4 j3 v' o2 ^+ o' q weight=3, # 线条宽度
3 F) d- ?! |) H9 }- ~+ L9 A' V color=gray).add_to(city_map)
+ A( H4 z4 s2 d0 O2 o# i; T' j
7 C: g9 J _5 g9 A # 多边形7 ^6 u% z% ^) o, Y5 ^; P9 a4 A) G+ ?
points_2 = [
* B6 I' ^7 A7 [5 z7 |( P% Q coordinate_orchard_road,1 Y7 b$ g8 k1 e- e. X: S% |7 W
coordinate_sentosa,
2 ]: r1 ?$ K$ s8 Q$ X7 H+ |: L8 L coordinate_changi_airport
1 L. X. J4 h. h! @$ D" m ]& @5 t- c4 A) ]: S" M. B1 t
5 V* Z# \8 Z5 [% U. Z2 D# _& W
city_map.add_child(folium.Polygon(
& B- N! U, B& g2 y# O2 T' k$ Q locations=points_2, # 坐标列表
4 T5 e$ d' _* w N weight=3, # 线条宽度) O; g* s% U4 X6 |9 u" K
color=yellow))6 k: G1 i h: q: c8 f) S0 t
' Z1 L1 l; z6 A7 W6 D4 z3 A # 矩形) `2 ] N2 R% U; x2 A Z
bounds = [
" h+ X! N5 d# `. w coordinate_ang_mo_kio,
# \& b8 o: V1 ]# K# J `5 z coordinate_yi_shun) @1 L+ r5 T; H& G/ O+ m# z3 ~
]3 Y5 I! x5 G$ L# ]" o% O
! T; T& B) N, W8 o% s( p city_map.add_child(folium.Rectangle(
6 A# i0 ?0 X$ N8 \' T0 r8 q) r: u5 K a bounds=bounds, # 坐标列表, Latitude and Longitude of line (Northing, Easting)
& c/ g; \) r! M! r! S' Q1 A2 k% c% X weight=2, # 线条宽度
) g2 a7 N3 @+ n/ T9 u color=blue)): ?2 v' U2 o7 H. q( [
8 i! d! _: i, ^# r( E # 圆形, circle, radius units meters
7 b* ^0 n& U6 Y4 E) ]4 L folium.Circle(
; u. Y0 `5 _( h' K' | radius=1000,; i1 m) U* \+ ?" i; i& m9 T/ R
location=coordinate_nus,$ [5 Y, ]5 A# D# w4 ?' ^% B# q
popup="National University of Singapore",. E" U9 g% \' K9 H' U$ J( f
color="crimson",
; S8 @& z: j" ^3 e0 l fill=False,
+ m6 D& V( W( C! v5 } ).add_to(city_map)1 E+ l1 S4 M5 A7 s; I. V- @
: Z1 ~8 S( F" A( _1 X+ E3 X. s7 H* H
# 圆形, circle, radius units pixels
1 ?( d; M+ W/ k! Q" H A+ U s folium.CircleMarker(" s, {/ V3 K3 ?% d
location=coordinate_ntu," ^, D# t& S9 L/ g+ m
radius=30,: a9 Q' i' b k5 `
popup="Nanyang Technological University",
: [& K8 t1 Y; w/ { color="#3186cc",' ^, ~* f- {! J g! K4 Y- o g& e( s9 i
fill=True,
& P6 C [, s( O fill_opacity=0.3, # 透明度
! {" H6 @4 |" P3 [ fill_color="#3186cc",- t$ ?6 w8 l' I4 q& f) e4 {
).add_to(city_map)# n- z; {3 y! o' R# S
1 \; P, v! v3 e; b F9 Y city_map 1 I$ q& K8 X3 b. W
Folium 中的画出各种形状热力图; j0 @- M! M% E5 @+ X3 G
在实际使用经纬度信息的时候,通常来说会针对某个 APP 或者多款 APP 的实时经纬度信息来获取路况的拥挤程度,景区的人流量等信息,在这种情况下,就可以做出热力图。通过实时的热力图信息,我们可以获得相应的人流量信息进行必要的数据分析工作。
. e8 n2 j( k! q; z$ J' _* o import numpy as np
0 z g3 U+ t$ r# E4 ?7 {. r from folium.plugins import HeatMap! O* G- U1 F- X: R
3 H6 o6 @$ g. f8 `( H/ ~ # define the city map
7 M9 ]# D# c( @; A, r3 Q2 n city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)
) w# N k- w* A, a4 e! ]& m
% [4 j/ l y$ e& ?+ x ` # 构建随机数据 p5 B( U8 c& k3 p! d9 J
data = (' g, M/ b; D" r
np.random.normal(& d N7 u) R) R# \
size=(10, 3)) * 0.03 * np.array([[1, 1, 1]]) +
G G a, M% E np.array([[coordinate_orchard_road[0], coordinate_orchard_road[1], 1]])# l& e7 t* X; |+ H( o" p! V
).tolist()
$ M0 E, u) [% o8 |7 R( f
' o+ J" P9 y+ t city_map.add_child(HeatMap(data=data))! L% ^/ j! u. @; U
city_map 4 ? \' o0 I$ I" e" d# a& b
7 Y* q1 ^0 Q' O/ F( R 除了单张图的热力图之外,Folium 还能够计算一段时间的连续热力图信息。 8 f* d! q$ e5 h0 E# t! I
import numpy as np5 k8 s$ m. r8 t, v% `3 ~$ V
from folium.plugins import HeatMapWithTime6 O& m9 E0 O% l# q. q% ]0 o& T3 E
% p4 |; O: G$ Q* _ # define the city map
1 J* ?! r* x( j# V5 o/ l city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)
/ k8 p0 _/ W8 C! P2 V/ I6 C P h
- `9 M- r6 l* B W e # 使用 numpy 建立初始数据
. W6 e: b0 R: c+ l7 t' [ initial_data = (np.random.normal(size=(200, 2)) *8 l7 y X/ s% b* a
np.array([[0.02, 0.02]]) +5 e! }2 o5 g3 [; W5 K4 ]
np.array([coordinate_orchard_road]))
& {) Z0 d$ @6 G; O$ @' M
. u6 G$ J, }2 f8 e, d0 @. k' E # 建立连续的数据
* l: P4 B5 n4 u" n, m data = [initial_data.tolist()]# g9 u! l, \: U( U, G+ R
for i in range(20):8 z. L2 Q" f7 q! v! L7 z+ |
data.append((data[i] + np.random.normal(size=(200, 2)) * 0.001).tolist())8 \* ~. N, h: d% M( \) ~% z
6 \( P) g7 G0 [" i # 显示连续的热力图& c4 d |( F% K8 J
city_map.add_child(HeatMapWithTime(data))/ e( J& L* Z9 _% I' J$ M' F! D
city_map
" `# D7 d6 X" p5 ? & f% I" b d1 \/ ^2 i8 d/ Z' L! ^
经纬度点的聚类' z" z; g' w# R P3 V, G+ |
除了热力图能反映人流量的信息,基于地理位置的聚类算法同样能够反映一个地区的拥挤程度,Folium 的 MarkerCluster() 函数可以对一个区域中的点来做聚类,在地图中可以放大和缩小,从而知道局部最拥挤的点在哪里了,最稀疏的区域是哪里。 / n0 C- H# e) J6 P$ u
from folium.plugins import MarkerCluster
2 k7 r: `3 Q e. V! E( @/ D) X4 f: U# g, i5 D
# define the city map
: `1 W" p, W% q- |2 \6 P! C city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)$ b; Y4 N# o; e) U6 k% y- u0 k
* {" S' y3 o2 w9 {
# 经纬度的聚类
6 `- I6 `4 P# X# i& i* e # 在 NUS 的经纬度附近随机生成 100 个点;
' E2 y2 m+ _1 ~" P# N0 h0 D data = (0 z8 O2 k8 q# }" L$ [) e- J1 a& v# d
np.random.normal(size=(100, 2))
& w @: _; ]7 M& V' p * np.array([[0.001, 0.001]]) +/ F( }2 \! q# q+ x0 d
np.array([coordinate_nus]))
$ i5 W H6 v2 c- S$ g6 p. o" L4 t+ H, [
" [1 W% v1 h% A! P2 i3 w1 j # create a mark cluster object" n) y" N: k3 f% n5 o t5 i
marker_cluster = MarkerCluster().add_to(city_map)9 T: b5 V# X% F! J
' K3 k+ L* a( n' Z( a1 f: f' V
# 将这些经纬度数据加入聚类. x _4 M, q1 w. ~, h1 a
for element in data:4 z- K* x; q2 b/ a$ Y. l7 I* ^
folium.Marker(location=[element[0], element[1]],icon=None).add_to(marker_cluster)
& Q4 R, P8 p, b& G+ {- e! S N, z% W! r. f# J: h! s
# add marker_cluster to map) x" Z: \: \$ M; F, e
city_map.add_child(marker_cluster)
- @# h! M$ p* F+ I& U7 A) N f9 ~) r2 t! {- D: b t/ ^2 @1 t" J
# 作图
`0 `. q* i+ K+ V' i, X1 Y city_map
1 x" G1 e& v) k( F* o 6 j& @% X) Q5 z+ k( Q+ B
以上就是关于 Folium 的基础内容,有兴趣的读者可以自行参阅 Folium 的官方文档。
# N! H; _, `5 [% E- g! ]2 j8 \0 \ 参考文献
( g$ c+ ?& R% m4 R1 [0 v Folium 官方文档:Folium - Folium 0.12.1 documentation $ T/ O- h f4 h: Z0 ^
7 N6 K4 [( n6 j8 j, ?0 U- n" P+ o+ C9 U4 P
9 b' J! v7 ^ @( f h9 k; b% B. C( f
|