frommathimportatan2,cos,radians,sin,sqrtimportnumpyasnpimportscipy.ndimageasimfrombokeh.documentimportDocumentfrombokeh.embedimportfile_htmlfrombokeh.modelsimport(Column,ColumnDataSource,GMapOptions,GMapPlot,Grid,Label,Line,LinearAxis,PanTool,Patches,Plot,Range1d,ResetTool,WheelZoomTool)frombokeh.resourcesimportINLINEfrombokeh.sampledata.mtbimportobiszow_mtb_xcmfrombokeh.util.browserimportviewdefhaversin(theta):returnsin(0.5*theta)**2defdistance(p1,p2):"""Distance between (lat1, lon1) and (lat2, lon2). """R=6371lat1,lon1=p1lat2,lon2=p2phi1=radians(lat1)phi2=radians(lat2)delta_lat=radians(lat2-lat1)delta_lon=radians(lon2-lon1)a=haversin(delta_lat)+cos(phi1)*cos(phi2)*haversin(delta_lon)return2*R*atan2(sqrt(a),sqrt(1-a))defprep_data(dataset):df=dataset.copy()latlon=list(zip(df.lat,df.lon))dist=np.array([distance(latlon[i+1],latlon[i])foriinrange(len(latlon[:-1]))])df["dist"]=np.concatenate(([0],np.cumsum(dist)))slope=np.abs(100*np.diff(df.alt)/(1000*dist))slope[np.where(slope<4)]=0# "green"slope[np.where((slope>=4)&(slope<6))]=1# "yellow"slope[np.where((slope>=6)&(slope<10))]=2# "pink"slope[np.where((slope>=10)&(slope<15))]=3# "orange"slope[np.where(slope>=15)]=4# "red"slope=im.median_filter(slope,6)colors=np.empty_like(slope,dtype=object)colors[np.where(slope==0)]="green"colors[np.where(slope==1)]="yellow"colors[np.where(slope==2)]="pink"colors[np.where(slope==3)]="orange"colors[np.where(slope==4)]="red"df["colors"]=list(colors)+[None]# NOTE: add [None] just make pandas happyreturndfname="Obiszów MTB XCM"# Google Maps now requires an API key. You can find out how to get one here:# https://developers.google.com/maps/documentation/javascript/get-api-keyAPI_KEY="GOOGLE_API_KEY"deftrail_map(data):lon=(min(data.lon)+max(data.lon))/2lat=(min(data.lat)+max(data.lat))/2map_options=GMapOptions(lng=lon,lat=lat,zoom=13)plot=GMapPlot(width=800,height=800,map_options=map_options,api_key=API_KEY)plot.title.text="%s - Trail Map"%nameplot.x_range=Range1d()plot.y_range=Range1d()plot.add_tools(PanTool(),WheelZoomTool(),ResetTool())line_source=ColumnDataSource(dict(x=data.lon,y=data.lat,dist=data.dist))line=Line(x="x",y="y",line_color="blue",line_width=2)plot.add_glyph(line_source,line)ifplot.api_key=="GOOGLE_API_KEY":plot.add_layout(Label(x=240,y=700,x_units='screen',y_units='screen',text='Replace GOOGLE_API_KEY with your own key',text_color='red'))returnplotdefaltitude_profile(data):plot=Plot(width=800,height=400)plot.title.text="%s - Altitude Profile"%nameplot.y_range.range_padding=0xaxis=LinearAxis(axis_label="Distance (km)")plot.add_layout(xaxis,'below')yaxis=LinearAxis(axis_label="Altitude (m)")plot.add_layout(yaxis,'left')plot.add_layout(Grid(dimension=0,ticker=xaxis.ticker))# x gridplot.add_layout(Grid(dimension=1,ticker=yaxis.ticker))# y gridplot.add_tools(PanTool(),WheelZoomTool(),ResetTool())X,Y=data.dist,data.alty0=min(Y)patches_source=ColumnDataSource(dict(xs=[[X[i],X[i+1],X[i+1],X[i]]foriinrange(len(X[:-1]))],ys=[[y0,y0,Y[i+1],Y[i]]foriinrange(len(Y[:-1]))],color=data.colors[:-1]))patches=Patches(xs="xs",ys="ys",fill_color="color",line_color="color")plot.add_glyph(patches_source,patches)line_source=ColumnDataSource(dict(x=data.dist,y=data.alt))line=Line(x='x',y='y',line_color="black",line_width=1)plot.add_glyph(line_source,line)returnplotdata=prep_data(obiszow_mtb_xcm)trail=trail_map(data)altitude=altitude_profile(data)layout=Column(children=[altitude,trail])doc=Document()doc.add_root(layout)if__name__=="__main__":doc.validate()filename="trail.html"withopen(filename,"w")asf:f.write(file_html(doc,INLINE,"Trail map and altitude profile"))print(f"Wrote {filename}")view(filename)