如何用 PYTHON 绘制漂亮的地图?— FOLIUM 作图工具介绍

[复制链接]
6 [ x9 X, Q9 s# K) m2 X. }

Folium 简介

) x7 g- Y' w$ m! C f. X* r

作为 Python 的一个可视化工具包 Folium,它通过 Leaflet 的地图服务,可以在 Jupyter Notebook 上实现可视化的地理位置作图,制作各种各样精美的地图信息。它不仅可以针对某个经纬度进行地理位置的可视化操作,还能够根据实时的人群地理位置信息来构建静态与动态热力图,甚至还能够针对经纬度的数量来进行必要的聚类可视化。本文将会基于新加坡的地图,对 Folium 的一些功能做简要的介绍,对此工具有兴趣的读者可以参阅 Folium 的官方文档。

! Y, e1 q! F8 h

创建地图

S, K6 E. x* L. o

通过 Folium 工具,可以直接作出一张世界地图,其代码也十分地简洁明了。

% U; l" J+ ^: s3 w
import folium& U3 k& d( u! p( n %matplotlib inline7 t v; g5 j/ M/ Q G ) b1 [7 f5 w$ {9 P+ t1 M, D import webbrowser $ N5 I# W1 V( I2 a' f" ` 4 P$ f7 E+ f0 @1 z( R' K, j0 Y print(folium.__version__) ( o! ~6 m$ ~( }: \5 m4 v2 j4 n$ h! G" r # define the world map 0 i' o, a& u3 l T( P world_map = folium.Map()9 F' x* N3 t$ }) i1 d0 \, B7 X! d # display world map3 F1 G* r: V# n world_map 6 R: d) V6 M/ `! d# x, w+ q% T
3 ~: b/ p! b, h' ^' K R& a
世界地图

除了能够作出一张完整的世界地图之外,通常程序员最常见的需求是针对某个或者某一些经纬度,来作出一张局部地图。地图中不仅需要包括经纬度信息,也需要有街道信息等必要的内容,甚至需要对经纬度的标记做一些必要的定制化工作。

: Y& c6 n5 ]* o8 K4 C

在初始化一张地图的时候,需要指定它的经纬度信息,也就是 location 的位置。也需要根据需要放大的尺寸来指定相应的 zoom_start 值。另外,tiles 是 str 型,用于控制绘图调用的地图样式,默认为OpenStreetMap,也有一些其他的内建地图样式,如Stamen Terrain,Stamen Toner。

2 l" [+ R$ x' a' G# m; W

有的时候需要在地图上标识出相应的经纬度,此时需要使用 folium.Marker 函数。其使用方法就是直接输入相应的经纬度信息,以及 icon 的形状(例如 cloud, info-sign 等);除此之外,还可以对其颜色进行标记,一般是对参数 color 进行调整即可。

/ e' U5 c+ ~4 J% |

另外,在某些经纬度上,还可以使用“点击-弹出“的控件 tooltip 和 popup,一旦鼠标指向该位置,就会呈现出相应的弹出信息。

) s! n/ n c) A6 `4 m' ] Z' H, a
# latitude and longitude in Singapore city / m" U) V' j" d5 O: G% _ coordinate_sentosa = [1.248946, 103.834306] ! Y9 a7 K& r7 J6 f% I. t coordinate_orchard_road = [1.304247, 103.833264] 9 E0 {( R8 i, }, ]/ [ coordinate_changi_airport = [1.357557, 103.98847] ! T5 I' T4 h8 o4 D1 g% e coordinate_nus = [1.296202,103.776899] 8 L9 D( s! z# h# U3 h3 F: [7 m$ f coordinate_ntu = [1.34841, 103.682933]/ I! s6 i" B: D* N coordinate_zoo = [1.403717, 103.793974]. B: v; o: k9 u. x2 R: @% A. Z3 U* r coordinate_ang_mo_kio = [1.37008, 103.849523] 4 R6 R0 t7 {& J) M# }3 l2 f8 i coordinate_yi_shun = [1.429384, 103.835028]0 G4 ^' ?+ T+ }) {9 T. z ' q4 b5 L5 a8 g! u! a # icon % E% v' }7 q. S icon_cloud = "cloud"( K) T+ W9 }8 ]. D$ h! C icon_sign = "info-sign" , P. S8 o5 k2 S2 o8 M # H$ }" D, z: H& ] # define the city map) x w& h" {& P- u% J5 Y8 ~: q8 y a # tiles in {OpenStreetMap, Stamen Terrain, Stamen Toner, Mapbox Bright}" u T) L \% p# Z% M city_map = folium.Map(2 N# B# d, S; E- |" Z9 d/ T/ t. p location=coordinate_orchard_road, ; B g; C0 `- z, b zoom_start=11,# G \) `* g' n ] p3 ~5 I$ o tiles=OpenStreetMap); p b- B9 {; c! T2 @/ { , _" ]5 E/ e/ R, m/ p1 Q& A1 ^+ P2 P7 [ # add marker in the city map 9 v R$ l3 f3 h; z4 x" l; D folium.Marker(- c% \8 z& S8 Y% k# i) h* p coordinate_sentosa,# q& w8 p. [7 X: s+ s) [- e icon=folium.Icon(color=blue) . E' r: n8 O, P- B" v1 D ).add_to(city_map) 0 ]0 n7 _# {; ?1 T5 @& e folium.Marker( 1 D8 G1 E2 F" F; v5 W+ N4 A coordinate_orchard_road,4 o" } x2 k6 O" v icon=folium.Icon(color=green, icon=icon_cloud) : Z! m$ @/ M5 r# F ).add_to(city_map)2 q0 a0 T/ @$ K8 e , o' ^. {9 d% D2 p4 F) s1 w; P& a # add popup : e/ f! A, w r" @: m5 G5 f1 p folium.Marker( 0 ]8 d8 F- |& w. U* i' I coordinate_changi_airport,% e) J* L1 L/ x u) C$ K popup=Changi Airport, & z" f& J/ A8 v+ z icon=folium.Icon(color=red, icon=icon_sign) ' z- K) ~2 s; o! ?; g$ a" C% z7 V ).add_to(city_map)5 t" m7 |; Z3 H* Q4 u/ R* H- L, K 9 F& c+ A/ g4 }/ X! p( d" M # add tooltips and popup 7 V8 y1 S, I- }3 x. h0 n tooltip = "Click me!": h* b* g+ w- I% h0 K( N) _- j" ?9 a- g G$ N1 B. R! P6 f: |% i7 W folium.Marker(' S# r. r+ n8 g+ ~" [; a coordinate_nus, I8 L6 e. Q- P" I/ u, b popup="<i>National University of Singapore</i>", . d& h7 [: H' t( N) x, a tooltip=tooltip ?8 W; A& P6 j2 K* O. {3 R ).add_to(city_map)# C" F# ?4 r i/ ?5 _ * o$ w6 I5 H9 X folium.Marker( : b: R" R3 M" K, R/ w: \! h coordinate_ntu, $ _- o! P, K( s( i/ s: U' |0 K popup="<b>Nanyang Technological University</b>", + {# y9 i# `# J8 j tooltip=tooltip & ~- j: c+ C3 W ).add_to(city_map)3 }7 L1 H# z _( n) X' j) P 4 D Q: L# u+ v/ A" s # display city map $ {& S- }1 t4 S& u( \' a city_map
8 P% M# Q' j- V4 P
Folium 的经纬度作图(OpenStreetMap)
Folium 的经纬度作图(Stamen Terrain)
Folium 的经纬度作图(Stamen Toner)

有的时候,我们只知道某个地点,但是并不知道相应的经纬度数据,此时,只需要使用 folium.LatLngPopup() 就可以在鼠标指向位置上呈现相应的经纬度值,可以方便的查阅和使用。

( t; Z8 H, E5 J0 r0 f8 |
# define the city map 2 N& p. Q8 w$ Y. [1 O1 G2 e city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11) " H( d, s. b3 c& `; ?+ @3 a' g: T( Q # l' r c7 U$ H, u # 在地图中添加经纬度, add latitude and longitude in the map when click: s1 ~0 Z9 _4 ~/ b1 v7 f city_map.add_child(folium.LatLngPopup()) . L5 y# ], L8 ]5 {3 ~! _ 7 i, P, B. P7 e& H% r H5 G. s) l city_map
/ n' h$ L* z: k 2 M& E+ y- C6 \( K

几何形状

: Z! X6 i9 b& R; A4 g- G0 H

在作出关键的经纬度点之后,有的时候我们需要作出相应的几何图形将其显示得更加清楚。Folium 提供线段相连,多边形,圆形,矩形等诸多图形,只需要使用相应的函数即可。

9 ]2 [4 @) w* e2 Q
# define the city map : F, {0 F* ~1 @8 v7 o city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11) . z, T+ I( j" S! n. F ! k( z4 g# n8 l Q. l- u$ V3 D3 v # 在地图中如何添加形状 : E. _+ _. P# t; i& ]# ~) `6 N # 多条边 # F0 d/ ]- g. C' q points_1 = [ 0 f( ~, B% i0 ^ coordinate_ntu,2 N. |4 e! o* z# ~' `) F/ P coordinate_nus, 6 k' r/ D1 L/ k8 f8 [4 X: ~' x$ u8 ~ coordinate_zoo 6 l7 s1 n; j3 r$ o; t ]9 ?- {. E. C1 d$ r / {+ A5 a: i2 g. K( F2 c # 在 city_map 中添加多条边,第一种添加方式7 i( p& ^2 b$ X/ u- p7 w% \ city_map.add_child(folium.PolyLine(! n$ d% Q/ j, T1 T! x locations=points_1, # 坐标列表& S6 \# q" k2 e" n) Q! Q3 _ weight=3, # 线条宽度 0 D; n: ^' M9 u0 k9 ^ color=gray))9 [( u+ _0 P& R; ^ ; n- S2 t6 p" u$ Z" H0 b& ~ # 在 city_map 中添加多条边,第二种添加方式 5 Y% R* Z7 N3 B1 t6 u( c; V folium.PolyLine( + o1 [( Z% @( y$ T& r# ~: Z0 ]0 x% h locations=points_1, # 坐标列表& c" h! \- B' A: _7 J weight=3, # 线条宽度: l8 l9 i" r# V1 ~5 e; k color=gray).add_to(city_map)+ ^- v0 y5 b* {" ~: a0 o, Q - N+ K# V- y6 L, Z1 z. E5 e # 多边形 % l5 u; m( ^2 S& Z+ s* B points_2 = [ 0 j) i) a! I" r* ~ coordinate_orchard_road,- n# p/ ]: h% B5 Q$ k4 O2 r$ H coordinate_sentosa," ^$ t8 r+ Z0 _3 t2 K coordinate_changi_airport % B$ X H) V! F. b' ^ ]% Q+ O3 I8 T2 J) | 7 B" L, E0 ~) ]3 |$ O( y city_map.add_child(folium.Polygon( 5 _. `+ K6 c% L locations=points_2, # 坐标列表- E- b( f, T3 {+ Z" e) h/ C# N weight=3, # 线条宽度. m# {7 y4 _$ J) G; l) H5 }! t, N color=yellow)) 9 k. v* p- W! N( w1 Q ( y5 ]" _9 C/ o$ l5 h # 矩形 - U9 B$ a' j1 b! t6 _8 O bounds = [ % a4 g$ }; R, O2 I coordinate_ang_mo_kio, ! v+ Y- u- a" W# [# K coordinate_yi_shun ; Y% ^ F4 }5 `0 e: u( S, N0 P6 @ ] $ F( B0 X! n- j4 b2 h6 G9 k+ A: E+ B( b v: q$ u1 }% D5 a2 v l city_map.add_child(folium.Rectangle( 2 K, l: p& Y, O; g" s bounds=bounds, # 坐标列表, Latitude and Longitude of line (Northing, Easting)2 |! A8 _6 c0 v! r- ^7 c weight=2, # 线条宽度 3 q6 B/ d! t, {, ~ color=blue))& o1 _* r. n6 _' v3 S' V $ ? x7 B1 l% @+ O- P # 圆形, circle, radius units meters5 E6 t' h4 i" N* Y9 H9 \. K folium.Circle(" Q9 ]$ V& s# ]# A radius=1000, , |# n/ _# N+ o2 F/ C! |2 l, P location=coordinate_nus,. p$ O" L( x, N8 ^; |' d popup="National University of Singapore",: H+ Q: m7 B# p color="crimson",* J2 q1 Q5 s7 r" h. l9 J4 J fill=False, $ M8 J, u- A2 `9 ?5 G3 M ).add_to(city_map) 8 F7 ?2 B: ^ S& S3 J. J* b / C" y1 o% ?' x# K2 ?: i # 圆形, circle, radius units pixels7 F% E% i0 t* e- P; E folium.CircleMarker( & a- h" K* P9 h0 T- c location=coordinate_ntu,4 v0 i9 \( N9 J radius=30, 5 b( w& i9 r ]8 a& B3 x popup="Nanyang Technological University", 8 Z- H2 u7 l# d& r7 G color="#3186cc",! Z2 I+ y! _, Y! W* H' O fill=True,! N4 H& ]/ a" D' K5 a fill_opacity=0.3, # 透明度 4 k, h7 j' g3 I. I: W; N9 y fill_color="#3186cc",7 c- }3 v; F2 A8 K! ~: Q; G7 g ).add_to(city_map)) f! X" ~/ G* q- X * b" P- o( N1 [% E city_map
4 {( c7 s. V+ F; m& h* c
Folium 中的画出各种形状

热力图

D* ~. v$ L/ W7 j

在实际使用经纬度信息的时候,通常来说会针对某个 APP 或者多款 APP 的实时经纬度信息来获取路况的拥挤程度,景区的人流量等信息,在这种情况下,就可以做出热力图。通过实时的热力图信息,我们可以获得相应的人流量信息进行必要的数据分析工作。

" s3 U! H/ w0 l
import numpy as np1 @/ Q- H' n* i9 \$ S. N0 n$ @ from folium.plugins import HeatMap. m, U9 R3 q8 f' `; F ) W$ W; ^6 i! `" f5 i # define the city map2 T/ h% @& k8 R* u$ N# A city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11)* d7 Z6 }- k& \ ' b9 v5 w% h3 c! b( \/ r # 构建随机数据0 a1 Y" s2 j3 X e- v% \. k data = ( . @0 s7 J0 P }5 i" y$ L np.random.normal( 9 K5 t; [3 D2 M$ C2 T2 L size=(10, 3)) * 0.03 * np.array([[1, 1, 1]]) +9 @$ M+ j1 }7 [3 f np.array([[coordinate_orchard_road[0], coordinate_orchard_road[1], 1]])) O9 a: L9 o( _) R6 \# Z ).tolist() & V8 B2 ^. f0 F J . S# s& `" E% K2 W& W) X" J city_map.add_child(HeatMap(data=data))" ^3 ~6 H( [* i1 P+ x1 O city_map
/ \% W, i& _8 G
0 O. X+ A: E# W$ @& W5 e; f3 Q: D

除了单张图的热力图之外,Folium 还能够计算一段时间的连续热力图信息。

$ k! T& u6 ]. s1 S1 ?
import numpy as np; G* y) p8 o# N7 o from folium.plugins import HeatMapWithTime; F% i+ I2 Y. P2 l& |7 V- S% D 9 T* M0 Y7 T' e7 ? # define the city map % G( }2 T; U0 e4 F$ A city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11) ! {4 Z9 q' r, Z' j* {6 b ; A4 E/ |$ R; b6 Q) U4 x; o& `9 j # 使用 numpy 建立初始数据( E! [: c5 p) A+ d% h1 n( z* d initial_data = (np.random.normal(size=(200, 2)) * ! U* U+ v; g3 \+ b4 N b% S! @" [! _ np.array([[0.02, 0.02]]) +1 ~( k" M6 W0 `% i2 e np.array([coordinate_orchard_road])) 4 H3 |5 a7 o+ }+ R. E5 K6 S" G& \4 c- N5 j$ Q D( J+ B+ m # 建立连续的数据 6 h _& u) H6 u2 \0 V4 L2 A4 u- h e2 s data = [initial_data.tolist()] ! l) o9 @& A4 S1 H+ C for i in range(20): & W% }/ P% a: g% t* v. I, H data.append((data[i] + np.random.normal(size=(200, 2)) * 0.001).tolist())" H9 l4 Y- l) ?$ J' {% i* m$ X+ X/ b / ]8 T' I y) U2 I { # 显示连续的热力图 - q2 G" _6 } d4 T city_map.add_child(HeatMapWithTime(data)) , i% }% ?/ c' m* z; k( O0 { city_map
5 E6 z9 X& k. g3 z + {; ~- V5 @) q

经纬度点的聚类

9 @1 W- B7 U, p

除了热力图能反映人流量的信息,基于地理位置的聚类算法同样能够反映一个地区的拥挤程度,Folium 的 MarkerCluster() 函数可以对一个区域中的点来做聚类,在地图中可以放大和缩小,从而知道局部最拥挤的点在哪里了,最稀疏的区域是哪里。

* z B T+ `/ H! W" A
from folium.plugins import MarkerCluster, G; Z( q6 r' `9 J' `# Q+ r # w% J, [8 h7 b' v. R& O # define the city map 3 T: w9 d/ q4 P city_map = folium.Map(location=coordinate_orchard_road, zoom_start=11) - E3 s/ G# m( r3 n. y: V 7 G: ?& P( k3 q+ S3 H # 经纬度的聚类( Y* t6 O' p' w: h # 在 NUS 的经纬度附近随机生成 100 个点; 7 P$ B R# b/ W, Z. r( C data = (! f+ D! m7 f8 J `: D y! E np.random.normal(size=(100, 2)) - b, Z7 J& j8 U+ Z4 U6 N1 Q * np.array([[0.001, 0.001]]) +' m& v* j3 l% l% ]& p5 i np.array([coordinate_nus]))) {9 p; }2 \& b+ `% l / j6 X6 ]" ?" `4 ?/ i5 \ # create a mark cluster object7 J. w3 R5 Q2 B& T/ l marker_cluster = MarkerCluster().add_to(city_map)! W- ^2 O! j R. [+ a5 n # r& \- _8 D& o6 ~ # 将这些经纬度数据加入聚类 * @/ n v+ {0 K0 n* p$ W for element in data: ) k2 G6 O; t. i folium.Marker(location=[element[0], element[1]],icon=None).add_to(marker_cluster)9 F; T, l3 A# @+ i& s% c * Y7 q/ m& L; E2 R5 b! i) n # add marker_cluster to map 3 ~ q# Y: t/ q6 E: }: \2 H$ A city_map.add_child(marker_cluster) ' t6 f2 x; \8 ^1 _9 V+ J: d" s4 W& s # 作图 $ j7 E Q( D+ H# M" G city_map
$ c# ^, I0 p; B , @' H( E5 J# ` a

以上就是关于 Folium 的基础内容,有兴趣的读者可以自行参阅 Folium 的官方文档。

- a! k( P* P/ S. m& D

参考文献

9 l" V+ k! N$ L6 Y9 z2 G$ u) D% G

Folium 官方文档:Folium - Folium 0.12.1 documentation

5 b& d: {. h$ O7 X$ h" ]' m+ }; N4 K6 t ) B4 p0 V& V1 d& a 1 [! c7 C$ Y5 M4 o " O! M# \; R( [
回复

举报 使用道具

相关帖子

全部回帖
暂无回帖,快来参与回复吧
懒得打字?点击右侧快捷回复 【吾爱海洋论坛发文有奖】
您需要登录后才可以回帖 登录 | 立即注册
冰死铁
活跃在7 天前
快速回复 返回顶部 返回列表