绘制两个变量,然后用第三个变量着色

问题描述

我有一个飞机飞行的数据集,我试图绘制飞机的位置(经度x纬度),然后通过这些坐标处的计划高度对该线进行着色。我的代码如下:

lat_data = np.array( [ 39.916294,39.87139,39.8005,39.70801,39.64645,39.58172,39.537853,39.55141,39.6787,39.796528,39.91702,40.008347,40.09513,40.144157,40.090584,39.96447,39.838924,39.712112,39.597103,39.488377,39.499096,39.99354,40.112175,39.77281,39.641186,39.51512,39.538853,39.882736,39.90413,39.811333,39.73279,39.65676,39.584026,39.5484,39.54484,39.629486,39.96,40.07143,40.187405,40.304718,40.423153,40.549305,40.673313,40.794548,40.74402,40.755558,40.770306,40.73574,40.795086,40.774628] )

long_data = np.array( [ -105.13034,-105.144104,-105.01132,-104.92708,-104.78505,-104.6449,-104.49255,-104.36578,-104.32623,-104.31285,-104.32199,-104.41774,-104.527435,-104.673935,-104.81152,-104.82184,-104.81882,-104.81314,-104.74657,-104.78108,-104.93442,-104.98039,-105.0168,-105.04967,-105.056564,-105.03639,-105.13429,-105.05214,-105.17435,-105.070526,-104.93587,-104.80029,-104.65973,-104.50339,-104.33972,-104.21634,-103.96216,-103.84808,-103.72534,-103.60455,-103.48926,-103.376495,-103.25937,-103.10858,-103.08469,-103.24878,-103.4169,-103.53073,-103.23694,-103.41254 ] )

altitude_data = np.array( [1.6957603e+00,1.9788861e+00,1.8547169e+00,1.8768315e+00,1.9633590e+00,2.0504241e+00,2.1115899e+00,2.1085002e+00,1.8621666e+00,1.8893014e+00,1.8268168e+00,1.7574688e+00,1.7666028e+00,1.7682364e+00,1.8120643e+00,1.7637002e+00,1.8054264e+00,1.9149075e+00,2.0173934e+00,2.0875392e+00,2.1486480e+00,1.8622510e+00,1.7937366e+00,1.8748144e+00,1.9063262e+00,1.9397615e+00,2.1261981e+00,2.0180094e+00,1.9827688e+00,-9.9999990e+06,1.8933343e+00,1.9615903e+00,2.1000245e+00,2.1989927e+00,2.3200927e+00,4.0542388e+00,4.0591464e+00,4.0597038e+00,4.3395977e+00,4.6702847e+00,5.0433373e+00,5.2824092e+00,5.2813010e+00,5.2735353e+00,5.2784677e+00,5.2784038e+00,5.2795196e+00,4.9482727e+00,4.2531524e+00] )

import matplotlib as plt    

fig,ax1 = plt.subplots( figsize = ( 10,10 ) )
ax1.plot( long_data,lat_data,alpha = .4)
ax1.scatter( long_data,c = altitude_data )
plt.show()

这给了我们这条路:

Position colored by altitude with a line connecting the points

是否有一种方法可以将数据合并为一条线,以绘制飞机的位置并调整仰角的颜色?

虽然可以同时绘制一条线和一个散点图,但当我输入所有数据时(n = 2400)看起来并不是很好。谢谢!

解决方法

所以,我有一个非常接近的东西。但是会缺少一些/平均的海拔数据。

from matplotlib import pyplot as plt
import matplotlib
import matplotlib.cm as cm
#... define arrays ...

fig,ax1 = plt.subplots( figsize = ( 10,10 ) )
minima = min(altitude_data)
maxima = max(altitude_data)

norm = matplotlib.colors.Normalize(vmin=0,vmax=maxima,clip=True)
mapper = cm.ScalarMappable(norm=norm,cmap=cm.summer)

pointsPerColor = 2

for x in range(len(lat_data)//pointsPerColor):
    startIndex = x * pointsPerColor
    stopIndex = startIndex + pointsPerColor + 1

    #get color for this section
    avgAltitude = sum(altitude_data[startIndex:stopIndex])/pointsPerColor
    rbga = mapper.to_rgba(avgAltitude)

    #plot section (leng)
    ax1.plot( long_data[startIndex:stopIndex],lat_data[startIndex:stopIndex],alpha=.7,color=rbga )

plt.show()

所以依次发生了。

  1. 获取您的海拔高度的最小值和最大值,并使用其制作彩色映射器 有几种颜色选择
  2. 确定间隔。需要至少2分才能明显地划出一条线
  3. (点数)/ pointsPerColor循环(需要进行整数除法) 一种。获得平均颜色 b。带有颜色的图段

就是这样!..我可能本可以稍微漂亮一点,但它可以工作 也..那些超低的值弄乱了映射..所以我只是将min设置为0

线图与高度数据的色标 line plot with color scale of altitude data

,

更新
如所讨论的,这里现在没有for循环并且包括第四类,例如加速的代码。现在,代码使用Line3DCollection生成轨迹,并使用带有LinearSegmentedColormap的定制颜色映射来指示第四类(加速度):

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Line3DCollection
from matplotlib.colors import LinearSegmentedColormap

fig = plt.figure(figsize=(12,12))
ax = fig.gca(projection='3d')

#rolling average between two acceleration data points
aver_accel = np.convolve(acceleration_data,np.ones((2,))/2,mode='valid')     

#custom colour map to visualize acceleartion and decelaration
cmap_bgr = LinearSegmentedColormap.from_list("bluegreyred",["red","lightgrey","blue"])

#creating the trajectory as line segments
points = np.transpose([lat_data,long_data,altitude_data])
window = (2,3)
view_shape = (len(points) - window[0] + 1,) + window 
segments = np.lib.stride_tricks.as_strided(points,shape = view_shape,strides = (points.itemsize,) + points.strides)
trajectory = Line3DCollection(segments,cmap=cmap_bgr,linewidth=3)
#set the colour according to the acceleration data
trajectory.set_array(aver_accel)
#add line collection and plot color bar for acceleration
cb = ax.add_collection(trajectory)
cbar = plt.colorbar(cb,shrink=0.5)
cbar.set_label("acceleration",rotation=270)

#let's call it "autoscale"
ax.set_xlim(min(lat_data),max(lat_data))
ax.set_ylim(min(long_data),max(long_data))
ax.set_zlim(min(altitude_data),max(altitude_data))

ax.set_xlabel("latitude")
ax.set_ylabel("longitude")
ax.set_zlabel("altitude")

plt.show()

样品输出(带有任意加速度数据): enter image description here

借助量身定制的颜色图,您可以清楚地看到加速和减速阶段。由于我们直接使用阵列,因此可以轻松添加用于校准的颜色条。请注意,您仍然拥有变量linewidth,该变量也带有一个数组(例如,用于速度的数组),尽管这样可能很难读取。在生成大型3D线集thanks to this marvellous answer.

时,还可以节省大量时间

为进行比较,此处是其他答案产生的2D视图: enter image description here

原始答案
既然您拥有3D数据,为什么不创建3D投影呢?您可以随时将视图移动到2D投影中。为避免颜色由每条线的第一点定义的问题(即陡峭的上升看起来与陡峭的下降有所不同),此程序将确定每条线的中间点以进行颜色编码的高度计算。缺点:使用缓慢的for循环,并且海拔高度的颜色在0到1之间归一化(此处无关紧要,因为此3D投影中的海拔高度过高,但是如果您要对其他颜色进行颜色编码,则会成为问题参数)。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm

fig = plt.figure(figsize=(10,10))
ax = fig.gca(projection='3d')

min_alt = np.min(altitude_data)
max_alt = np.max(altitude_data)
#generate normalized altitude array for colour code
#the factor 0.95 filters out the end of this colormap
cols_raw = 0.95 * (altitude_data-min_alt) / (max_alt-min_alt) 
#rolling average between two data point colors
cols = np.convolve(cols_raw,mode='valid')     

for i,col in enumerate(cols):
    ax.plot(lat_data[i:i+2],long_data[i:i+2],altitude_data[i:i+2],c=cm.gnuplot(col))

ax.set_xlabel("latitude")
ax.set_ylabel("longitude")
ax.set_zlabel("altitude")

plt.show()

enter image description here

上述输出的样本数据:

lat_data = np.array( [ 39.916294,39.87139,39.8005,39.70801,39.64645,39.58172,39.537853,39.55141,39.6787,39.796528,39.91702,40.008347,40.09513,40.144157,40.090584,39.96447,39.838924,39.712112,39.597103,39.488377,39.499096,39.99354,40.112175,39.77281,39.641186,39.51512,39.538853,39.882736,39.90413,39.811333,39.73279,39.65676,39.584026,39.5484,39.54484,39.629486,39.96,40.07143,40.187405,40.304718,40.423153,40.549305,40.673313,40.794548,40.74402,40.755558,40.770306,40.73574,40.795086,40.774628] )
  
long_data = np.array( [ -105.13034,-105.144104,-105.01132,-104.92708,-104.78505,-104.6449,-104.49255,-104.36578,-104.32623,-104.31285,-104.32199,-104.41774,-104.527435,-104.673935,-104.81152,-104.82184,-104.81882,-104.81314,-104.74657,-104.78108,-104.93442,-104.98039,-105.0168,-105.04967,-105.056564,-105.03639,-105.13429,-105.05214,-105.17435,-105.070526,-104.93587,-104.80029,-104.65973,-104.50339,-104.33972,-104.21634,-103.96216,-103.84808,-103.72534,-103.60455,-103.48926,-103.376495,-103.25937,-103.10858,-103.08469,-103.24878,-103.4169,-103.53073,-103.23694,-103.41254 ] )

altitude_data = np.array( [1.6957603e+00,1.9788861e+00,1.8547169e+00,1.8768315e+00,1.9633590e+00,2.0504241e+00,2.1115899e+00,2.1085002e+00,1.8621666e+00,1.8893014e+00,1.8268168e+00,1.7574688e+00,1.7666028e+00,1.7682364e+00,1.8120643e+00,1.7637002e+00,1.8054264e+00,1.9149075e+00,2.0173934e+00,2.0875392e+00,2.1486480e+00,1.8622510e+00,1.7937366e+00,1.8748144e+00,1.9063262e+00,1.9397615e+00,2.1261981e+00,2.0180094e+00,1.9827688e+00,1.9999990e+00,1.8933343e+00,1.9615903e+00,2.1000245e+00,2.1989927e+00,2.3200927e+00,2.9999990e+00,4.0542388e+00,4.0591464e+00,4.0597038e+00,4.3395977e+00,4.6702847e+00,5.0433373e+00,5.2824092e+00,5.2813010e+00,5.2735353e+00,5.2784677e+00,5.2784038e+00,5.2795196e+00,4.9482727e+00,4.2531524e+00] )

acceleration_data = np.array( 
    [1,2,3,4,5,15,26,49,67,83,89,72,77,63,75,82,69,37,-29,-37,-27,-14,9,4] )
    
,

如果您想使用Line2D对象,则每个对象只能使用一种颜色。解决方法是,您可以将每个线段绘制为一组(一阶线性)插值线段,并通过其相应的无穷小值为每个线段上色。

该功能似乎包含在LineCollection实例中,但是我在下面只是采用了一种更快捷,更脏的方法。

enter image description here

enter image description here

为了获得更多的荣誉,由于我们在这里谈论的是地理空间数据,为什么不使用cartopy绘制数据呢?这样,您可以拥有一个“底图”,为您提供一些参考。毕竟,如果值得绘图,就值得精美绘制。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import cartopy
import cartopy.crs as ccrs

import numpy as np
import scipy
from scipy import interpolate

import matplotlib
#matplotlib.use('Agg')
import matplotlib.pyplot as plt

### clean data
filter_inds   = np.where(np.abs(altitude_data) < 100)
lat_data      = lat_data[filter_inds]
long_data     = long_data[filter_inds]
altitude_data = altitude_data[filter_inds]

# =============== plot

plt.close('all')
plt.style.use('dark_background') ## 'default'
fig = plt.figure(figsize=(1500/100,1000/100))
#ax1 = plt.gca()

lon_center = np.mean(long_data); lat_center = np.mean(lat_data)

ax1 = plt.axes(projection=ccrs.Orthographic(central_longitude=lon_center,central_latitude=lat_center))
ax1.set_aspect('equal')

scale = 3 ### 'zoom' with smaller numbers
ax1.set_extent((lon_center-((0.9*scale)),lon_center+((0.7*scale)),lat_center-(0.5*scale),lat_center+(0.5*scale)),crs=ccrs.PlateCarree())

### states
ax1.add_feature(cartopy.feature.NaturalEarthFeature(category='cultural',scale='10m',facecolor='none',name='admin_1_states_provinces_shp'),zorder=2,linewidth=1.0,edgecolor='w')

ax1.add_feature(cartopy.feature.RIVERS.with_scale('10m'),edgecolor='lightblue')
ax1.add_feature(cartopy.feature.LAKES.with_scale('10m'),edgecolor='gray')

### download counties from https://prd-tnm.s3.amazonaws.com/StagedProducts/Small-scale/data/Boundaries/countyl010g_shp_nt00964.tar.gz
### untar with : tar -xzf countyl010g_shp_nt00964.tar.gz

try:
    reader = cartopy.io.shapereader.Reader('countyl010g.shp')
    counties = list(reader.geometries())
    COUNTIES = cartopy.feature.ShapelyFeature(counties,ccrs.PlateCarree())
    ax1.add_feature(COUNTIES,alpha=0.5,edgecolor='gray')
except:
    pass

#norm = matplotlib.colors.Normalize(vmin=altitude_data.min(),vmax=altitude_data.max())
norm = matplotlib.colors.Normalize(vmin=1.0,vmax=6.0)
cmap = matplotlib.cm.viridis
mappableCmap = matplotlib.cm.ScalarMappable(norm=norm,cmap=cmap)

# ===== plot line segments individually for gradient effect

for i in range(long_data.size-1):
    long_data_this_segment = long_data[i:i+2]
    lat_data_this_segment  = lat_data[i:i+2]
    altitude_data_this_segment  = altitude_data[i:i+2]
    
    ### create linear interp objects
    ### scipy doesnt like when the data isn't ascending (hence the flip)
    
    try:
        spl_lon = scipy.interpolate.splrep(altitude_data_this_segment,long_data_this_segment,k=1)
        spl_lat = scipy.interpolate.splrep(altitude_data_this_segment,lat_data_this_segment,k=1)
    except:
        long_data_this_segment = np.flip(long_data_this_segment)
        lat_data_this_segment = np.flip(lat_data_this_segment)
        altitude_data_this_segment = np.flip(altitude_data_this_segment)
        spl_lon = scipy.interpolate.splrep(altitude_data_this_segment,k=1)
    
    ### linearly resample on each segment
    nrsmpl=100
    altitude_data_this_segment_rsmpl = np.linspace(altitude_data_this_segment[0],altitude_data_this_segment[1],nrsmpl)
    long_data_this_segment_rsmpl = scipy.interpolate.splev(altitude_data_this_segment_rsmpl,spl_lon)
    lat_data_this_segment_rsmpl = scipy.interpolate.splev(altitude_data_this_segment_rsmpl,spl_lat)
    
    for j in range(long_data_this_segment_rsmpl.size-1):
        
        long_data_this_segment_2 = long_data_this_segment_rsmpl[j:j+2]
        lat_data_this_segment_2  = lat_data_this_segment_rsmpl[j:j+2]
        altitude_data_this_segment_2  = altitude_data_this_segment_rsmpl[j:j+2]
        
        ax1.plot(long_data_this_segment_2,lat_data_this_segment_2,transform=ccrs.PlateCarree(),c=mappableCmap.to_rgba(np.mean(altitude_data_this_segment_2)),zorder=3,linestyle='solid',alpha=0.8,lw=5.0)

# =====

### plot the actual data points as a scatter plot
pts = ax1.scatter(long_data,lat_data,alpha=1.0,marker='o',c=mappableCmap.to_rgba(altitude_data),edgecolor='w',zorder=4)

cbar = fig.colorbar(mappable=mappableCmap,ax=ax1,orientation='vertical',fraction=0.046,pad=0.04)
cbar.set_label(r'$Altitude$ [units]',fontsize=20)
cbar.ax.tick_params(labelsize=16)
cbar.set_ticks(np.linspace(1.0,6.0,5+1),update_ticks=True)
cbar.set_ticklabels([ ('%0.1f' % x) for x in cbar.get_ticks() ])

fig.tight_layout()
fig.savefig('flightPath.png',dpi=100)
plt.show()
,

这是我使用Plotly的ScatterGeo对象以及Pandas和NumPy加载数据的解决方案。我选择此程序包是因为您可以进行交互式绘图(包含缩放和悬停数据),还可以查看飞机飞过:)的状态。

# Import packages
import pandas as pd
import numpy as np
import plotly.graph_objects as go

# Load your data into a Pandas DataFrame object
d = {'Lat': lat_data,'Long': long_data,'Altitude': altitude_data}
df = pd.DataFrame(data=d)

# Create scatterGeo object with the proper data 
scatterMapData = go.Scattergeo(lon = df['Long'],lat = df['Lat'],text=df['Altitude'],mode = 'markers+lines',marker_color = df['Altitude'],marker = dict(colorscale = 'Viridis',cmin = 0,cmax = df['Altitude'].max(),colorbar_title = "Altitude",#line = dict(width=1,color='black')
                                            )
                               )

# Load scatterMapData object into Plotly Figure
# and configure basic options for title and scoping
fig = go.Figure(data=scatterMapData)
fig.update_layout(title = 'Plane Flight Data',geo_scope = 'usa',geo = dict(scope = 'usa',#projection_scale = 5,center={'lat': np.median(df['Lat']),'lon': np.median(df['Long'])})
                 )

# Finally show the plot
fig.show()

以下是该图的放大版本:
PlaneFlightData

我只想指出,您可以将mode='marker'对象中的scattergeo更改为散点图,将mode='lines'更改为连接每个位置的折线图。 / p>