广州经典3日旅行攻略?Python动态图告诉你!

Python与算法之美

共 21784字,需浏览 44分钟

 · 2021-08-30

今日表情😋 :


先上数据

Day1 石室圣心教堂(2小时) → 沙面(2小时) → 陈家祠(2小时) → 上下九步行街(2小时) 


Day2 越秀公园(4小时) → 中山纪念堂(2小时) → 永庆坊(2小时) → 夜游珠江(3小时) 


Day3 广州长隆野生动物世界(1天)


再上视频



最后上代码


import numpy as np 
import pandas as pd 
import geopandas as gpd 
import shapely 
from shapely import geometry as geo 
from shapely import wkt 
import geopandas as gpd 
import matplotlib.pyplot as plt 
import matplotlib.animation as  animation 
import contextily as ctx

import imageio
import os 
from PIL import Image

plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['animation.writer'] = 'html'
plt.rcParams['animation.embed_limit'] = 100

def rgba_to_rgb(img_rgba):
    img_rgb = Image.new("RGB", img_rgba.size, (255255255))
    img_rgb.paste(img_rgba, mask=img_rgba.split()[3]) 
    return img_rgb 

def html_to_gif(html_file, gif_file, duration=0.5):
    path = html_file.replace(".html","_frames")
    images = [os.path.join(path,x) for x in sorted(os.listdir(path))]
    frames = [imageio.imread(x) for x in images]
    if frames[0].shape[-1]==4:
        frames = [np.array(rgba_to_rgb(Image.fromarray(x))) for x in frames]
    imageio.mimsave(gif_file, frames, 'gif', duration=duration)
    return gif_file

cmap = [
'#2E91E5',
'#1CA71C',
'#DA16FF',
'#B68100',
'#EB663B',
'#00A08B',
'#FC0080',
'#6C7C32',
'#862A16',
'#620042',
'#DA60CA',
'#0D2A63']*100

dfcity = gpd.read_file("./data/dfdistrict.geojson"
                         ).query("parent=='440100'").set_crs("epsg:4326").to_crs("epsg:3857")
                         
                         dfplace  = pd.read_csv("./data/travel_guangzhou.csv")
dfplace["geometry"] = [shapely.wkt.loads(x) for x in dfplace["geometry"]] 
dfplace = gpd.GeoDataFrame(dfplace,geometry="geometry")
dfplace = dfplace.set_crs("epsg:4326").to_crs("epsg:3857")

df = pd.DataFrame({"x":[p.x for  p in dfplace["geometry"]],
                        "y":[p.y for p in dfplace["geometry"]],
                        "z":dfplace["interval"],
                        "day":dfplace["day"]})

df.index = dfplace["place"]

def trace_map_dance(df,title = "广州经典3日旅行路线",
                     filename = None,
                     figsize = (8,6),dpi = 144,
                     duration = 0.5,
                     anotate_points = ["沙面","越秀公园","广州长隆野生动物世界"])
:


    fig, ax =plt.subplots(figsize=figsize,dpi=dpi)

    def plot_frame(i):

            ax.clear()
            ax.axis("off")
            
            k = i//3+1
            m = i%3

            dfdata = df.iloc[:k,:].copy()
            dftmp = df.iloc[:k-1,:].copy()
            text = dfdata["day"].tolist()[-1]
            
            #============================================================
            #绘制背景
            #============================================================
            
            #设置绘图范围
            bounds = dfcity.total_bounds # k==1   
            if 1<k<len(df)+1:
                bounds = dfplace.query("day=='{}'".format(text)).total_bounds
            if k>=len(df)+1:
                bounds = dfplace.total_bounds
                
            (xmin,ymin,xmax,ymax) = bounds
            cx = (xmin+xmax)/2.0
            cy = (ymin+ymax)/2.0
            dx = max(xmax-xmin,100)
            dy = max(ymax-ymin,100)

            if dx/dy<8/6.0:
                dx = dy*8/6.0
            else:
                dy = dx*6.0/8
            bounds = np.array([min(cx-8000,cx-dx/2.0),min(cy-6000,cy-dy/2.0),
               max(cx+8000,cx+dx/2.0),max(cy+6000,cy+dy/2.0)])
                
            ax.set_xlim(bounds[0]-(bounds[2]-bounds[0])/3, bounds[2]+(bounds[2]-bounds[0])/3)    
            ax.set_ylim(bounds[1]-(bounds[3]-bounds[1])/3, bounds[3]+(bounds[3]-bounds[1])/3)
                
            gaode = 'http://wprd01.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7'
            openstreet = "https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png"
            ctx.add_basemap(ax,source=gaode,alpha=0.5)
            
            #============================================================
            #绘制散点
            #============================================================

            # 绘制散点图像
            if len(dftmp)>0:
                ax.scatter(dftmp["x"],dftmp["y"],s = 100*dftmp["z"]/df["z"].mean(),
                       c = (cmap*100)[0:len(dftmp)],alpha = 0.5,zorder = 3)

                # 添加注释文字
                for j,p in enumerate(dftmp.index):
                    px,py,pz = dftmp.loc[p,["x","y","z"]].tolist() 
                    if p in anotate_points:
                        ax.annotate(p,xy = (px,py),  xycoords = "data",xytext = (-20,15),
                        fontsize = 10,fontweight = "bold",color = cmap[j], textcoords = "offset points")
                        
            #绘制轨迹线
            lines = [dfdata[["x","y"]].values[i:i+2for i in range(len(dfdata)-1)] 
            for ln in lines[:-1]:
                x, y = np.transpose(ln)
                line = plt.Line2D(x,y,color="gray",linestyle="-",linewidth= 2.5)
                ax.add_artist(line)
            
            # 添加标题和排名序号
            ax.set_title(title,color = "black",fontsize = 12)
            ax.text(0.130.9, text, va="center", ha="center"
                         alpha=0.5, size = 50,transform = ax.transAxes)
            
            # 结尾呈现全局轨迹无动画
            if i>=3*len(df):
                for ln in lines[-1:]:
                    x, y = np.transpose(ln)
                    line = plt.Line2D(x,y,color="gray",linestyle="-",linewidth= 2.5)
                    ax.add_artist(line)
                return 0

            # 添加注意力动画
            if m==0:
                px,py,pz = dfdata["x"][[-1]],dfdata["y"][[-1]],dfdata["z"][-1]
                p = dfdata.index[-1]+":"+str(pz)+"Hours"
                ax.scatter(px,py,s = 800*pz/df["z"].mean(),
                   c = cmap[len(dfdata)-1:len(dfdata)],alpha = 0.5,zorder = 4)
                ax.annotate(p,xy = (px,py),  xycoords = "data",
                        xytext = (-20,15),fontsize = 20,fontweight = "bold",
                        color = cmap[k-1], textcoords = "offset points",zorder = 5)
                
                for ln in lines[-1:]:
                    x, y = np.transpose(ln)
                    line = plt.Line2D(x,y,color="gray",linestyle=":",linewidth= 4.5)
                    ax.add_artist(line)
            
            if m==1:
                px,py,pz = dfdata["x"][[-1]],dfdata["y"][[-1]],dfdata["z"][-1]
                p = dfdata.index[-1]+":"+str(pz)+"Hours"
                ax.scatter(px,py,s = 400*pz/df["z"].mean(),
                   c = cmap[len(dfdata)-1:len(dfdata)],alpha = 0.5,zorder = 4)
                ax.annotate(p,xy = (px,py),  xycoords = "data",
                        xytext = (-20,15),fontsize = 15,fontweight = "bold",
                        color = cmap[k-1], textcoords = "offset points",zorder = 5)
                
                for ln in lines[-1:]:
                    x, y = np.transpose(ln)
                    line = plt.Line2D(x,y,color="gray",linestyle="--",linewidth= 3.5)
                    ax.add_artist(line)
                
            if m==2:
                px,py,pz = dfdata["x"][[-1]],dfdata["y"][[-1]],dfdata["z"][-1]
                p = dfdata.index[-1]+":"+str(pz)+"Hours"
                ax.scatter(px,py,s = 100*pz/df["z"].mean(),
                   c = cmap[len(dfdata)-1:len(dfdata)],alpha = 0.5,zorder = 4)
                ax.annotate(p,xy = (px,py),  xycoords = "data",
                        xytext = (-20,15),fontsize = 10,fontweight = "bold",
                        color = cmap[k-1], textcoords = "offset points",zorder = 5)
                
                for ln in lines[-1:]:
                    x, y = np.transpose(ln)
                    line = plt.Line2D(x,y,color="gray",linestyle="-",linewidth= 2.5)
                    ax.add_artist(line)
            return 0
                
    my_animation = animation.FuncAnimation(fig,plot_frame,
                            frames = range(0,3*len(df)+5),interval = int(duration*1000))
    
    if filename is None:
        try:
            from IPython.display import HTML
            HTML(my_animation.to_jshtml())
            return HTML(my_animation.to_jshtml())
        except ImportError:
            pass
    else:
        my_animation.save(filename)
        return filename

html_file = "广州经典3日旅行路线.html"
trace_map_dance(df,filename=html_file)

gif_file = html_file.replace(".html",".gif")
html_to_gif(html_file,gif_file,duration=0.8)


收工。


万水千山总是情,点个在看行不行?😋

浏览 7
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报