如何使用 Bokeh 颜色图处理 shapefile 中的重叠
How to handle overlaps in shapefile with Bokeh color map
我想用 Bokeh 生成叶绿素图。
我已经准备好法国部门及其人口的数据集。我还下载了法国部门的shapefile。
第一次试用后,我发现我的托盘被错误地应用在了部门上(有些部门的颜色比其他部门的人少)。
我觉得很奇怪,给所有部门设置相同的人口只是为了检查,我发现并不是所有部门都有相同的颜色!在下面找到我的代码
data = gdf.join(df)
# apply same population per department
data.population = 5678
geo_src = bm.GeoJSONDataSource(geojson=data.to_json())
# set up a log colormap
cmap = bm.LogColorMapper(
palette=bokeh.palettes.Blues9[::-1], # reverse the palette
)
# define web tools
TOOLS = "pan,wheel_zoom,box_zoom,reset,hover,save"
# set up bokeh figure
p = figure(
title="Population",
tools=TOOLS,
toolbar_location="below",
x_axis_location=None,
y_axis_location=None,
width=800,
height=800
)
# remove the grid
p.grid.grid_line_color = None
# core part !
p.patches(
'xs', 'ys',
fill_alpha=0.7,
fill_color={'field': 'population', 'transform': cmap},
line_color='black',
line_width=0.5,
source=geo_src
)
# show plot
show(p)
查看结果,
我的猜测是那些较暗的部门有重叠的形状,散景应用了两倍的人口使它们更暗...
我试图找到一种从 shapefile 中删除重叠的方法(到目前为止没有成功),但我想知道是否有一种方法可以配置 Bokeh 以要求它不汇总重叠?
好吧,我终于明白我做错了什么了。
这不是关于重叠或类似的东西(我用QGIS确认没有重叠)。
相反,我注意到比其他部门更暗的部门实际上被分成了几部分!
事情是这样的;在应用 Bokeh 补丁时,我使用了一个小于 1 的 fill_alpha。我只需要将此参数设置为 1,这样无论部门由什么部分组成,颜色都是相同的!
p.patches(
'xs', 'ys',
fill_alpha=1,
fill_color={'field': 'population', 'transform': cmap},
line_color='black',
line_width=0.5,
source=geo_src
)
我遇到了同样的问题,我有解决办法。
问题是当补丁使用 x
边界数据列表中的 nan
分隔符描述多个多边形时,使用 p.patches
的 Bokeh 中的错误。稍后我得想办法报告这个错误。
解决方案是改用“p.multi_polygons”。他们的多个多边形可以包含在每个条目中而不会受到错误的影响。 https://docs.bokeh.org/en/latest/docs/reference/models/glyphs/multi_polygons.html
我用 geojson 的工作不多,但我用 GeoPandas 工作过。以下是使用来自 https://www.naturalearthdata.com/downloads/:
的数据的工作示例
import geopandas as gpd
from shapely.geometry.polygon import Polygon
from shapely.geometry.multipolygon import MultiPolygon
from bokeh.io import show, output_file
from bokeh.models import MultiPolygons, ColorMapper, LinearColorMapper
from bokeh.palettes import Inferno256 as palette
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.layouts import row, column
# Define the function I gave earlier
def get_MultiPoly(mpoly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') for the exterior and interior of MultiPolygon digestible by multi_polygons in Bokeh"""
if coord_type == 'x':
i=0
elif coord_type == 'y':
i=1
# Get the x or y coordinates
c = []
if isinstance(mpoly,Polygon):
mpoly = [mpoly]
for poly in mpoly: # the polygon objects return arrays, it's important they be lists or Bokeh fails
exterior_coords = poly.exterior.coords.xy[i].tolist()
interior_coords = []
for interior in poly.interiors:
if isinstance(interior.coords.xy[i],list):
interior_coords += [interior.coords.xy[i]]
else:
interior_coords += [interior.coords.xy[i].tolist()]
c.append([exterior_coords, *interior_coords])
return c
# Define input/output files and get data, index by name
output_file("tmp.html")
map_world_gpd = gpd.read_file('map_data/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp')
map_world_gpd.set_index('NAME_EN',inplace=True)
# Parse all countries into a ColumnDataSource
N=len(map_world_gpd)
source3 = ColumnDataSource({
'x': [get_MultiPoly(map_world_gpd.iloc[i]['geometry'],'x') for i in range(0,N)],
'y': [get_MultiPoly(map_world_gpd.iloc[i]['geometry'],'y') for i in range(0,N)],
'n': [i for i in range(0,N)],
'name':[map_world_gpd.iloc[i]['NAME'] for i in range(0,N)],
})
# Plot all the countries
p = figure(title="map_exploration",match_aspect=True,aspect_ratio=2,
tooltips=[
("Name",'@name')
],
)
p.multi_polygons(xs='x',ys='y', source=source3,
fill_color={'field': 'n', 'transform': LinearColorMapper(palette=palette,low=0,high=len(source3.data['x']))},
fill_alpha=0.5, line_color="black", line_width=0.5)
show(row(p,sizing_mode = 'scale_width'))
如果您对这个错误感兴趣,那么这里有一个显示错误的例子:
map_states_gpd = gpd.read_file('map_data/cb_2019_us_state_20m.shp')
map_states_gpd.set_index('NAME',inplace=True)
map_world_gpd = gpd.read_file('map_data/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp')
map_world_gpd.set_index('NAME_EN',inplace=True)
map_world_gpd = map_world_gpd.to_crs(epsg=3395)
gsource = map_world_gpd.loc['Spain']['geometry']
output_file("tmp.html")
def get_PolyCoords(poly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') of """
if coord_type == 'x':
# Get the x coordinates of the exterior
return list( poly.exterior.coords.xy[0] )
elif coord_type == 'y':
# Get the y coordinates of the exterior
return list( poly.exterior.coords.xy[1] )
def get_MultiPoly(mpoly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') for the exterior and interior of MultiPolygon digestible by multi_polygons in Bokeh"""
if coord_type == 'x':
i=0
elif coord_type == 'y':
i=1
# Get the x or y coordinates
c = []
if isinstance(mpoly,Polygon):
mpoly = [mpoly]
for poly in mpoly: # the polygon objects return arrays, it's important they be lists or Bokeh fails
exterior_coords = poly.exterior.coords.xy[i].tolist()
interior_coords = []
for interior in poly.interiors:
if isinstance(interior.coords.xy[i],list):
interior_coords += [interior.coords.xy[i]];
else:
interior_coords += [interior.coords.xy[i].tolist()]
c.append([exterior_coords, *interior_coords])
return c
def get_MultiPolyCords(mpoly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') of edges of a MultiPolygon exterior"""
if isinstance(mpoly,Polygon):
mpoly = [mpoly]
if coord_type == 'x':
# Get the x coordinates of the exterior
x = []
for poly in mpoly:
x.append(get_PolyCoords(poly,coord_type))
return x
elif coord_type == 'y':
# Get the y coordinates of the exterior
y = []
for poly in mpoly:
y.append(get_PolyCoords(poly,coord_type))
return y
def concat_MultiPolyCords(mpoly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') of edges of a MultiPolygon exterior"""
if isinstance(mpoly,Polygon):
mpoly = [mpoly]
if coord_type == 'x':
# Get the x coordinates of the exterior
x = []
for poly in mpoly:
x.append(float('NaN'))
x = x+ get_PolyCoords(poly,coord_type)
return x
elif coord_type == 'y':
# Get the y coordinates of the exterior
y = []
for poly in mpoly:
y.append(float('NaN'))
y = y+ get_PolyCoords(poly,coord_type)
return y
def plen(mp):
if isinstance(mp,MultiPolygon):
return len(mp)
else:
return 1
def plen(mpoly):
if isinstance(mpoly,Polygon):
return 1
else:
return len(mpoly)
# Plot the patches using the same color
source1 = ColumnDataSource({
'x': get_MultiPolyCords(gsource,'x'),
'y': get_MultiPolyCords(gsource,'y'),
'n': [3]*plen(gsource),
})
# Plot the patches by combining them into one patch, separate sub patches with nan, all same color
# Note that the alpha outcome is faulty, it seems to redraw all the patches for each one
ys = [concat_MultiPolyCords(gsource,'y')]
source2 = ColumnDataSource({
'x': [concat_MultiPolyCords(gsource,'x')],
'y': [concat_MultiPolyCords(gsource,'y')],
'n': [3],
})
# Initialize our figure
p1 = figure(title="map_exploration",match_aspect=True,aspect_ratio=2)
# Plot grid
p1.patches('x', 'y', source=source1,
fill_color={'field': 'n', 'transform': LinearColorMapper(palette=palette,low=0,high=plen(gsource))},
fill_alpha=0.5, line_color="black", line_width=0.5)
p1.title.text = "Map has "+str(plen(gsource))+" polygons, patched using "+str(plen(source1.data['x']))+" patches"
# Initialize our figure
p2 = figure(title="map_exploration",match_aspect=True,aspect_ratio=2)
# Plot grid
p2.patches('x', 'y', source=source2,
fill_color={'field': 'n', 'transform': LinearColorMapper(palette=palette,low=0,high=plen(gsource))},
fill_alpha=0.5, line_color="black", line_width=0.5)
p2.title.text = "Map has "+str(plen(gsource))+" polygons, patched using "+str(plen(source2.data['x']))+" patches using NaN separators"
show(row(p1,p2,sizing_mode = 'scale_width',))
所以不要使用第二个代码,使用第一个来获得想要的结果。
我想用 Bokeh 生成叶绿素图。 我已经准备好法国部门及其人口的数据集。我还下载了法国部门的shapefile。
第一次试用后,我发现我的托盘被错误地应用在了部门上(有些部门的颜色比其他部门的人少)。
我觉得很奇怪,给所有部门设置相同的人口只是为了检查,我发现并不是所有部门都有相同的颜色!在下面找到我的代码
data = gdf.join(df)
# apply same population per department
data.population = 5678
geo_src = bm.GeoJSONDataSource(geojson=data.to_json())
# set up a log colormap
cmap = bm.LogColorMapper(
palette=bokeh.palettes.Blues9[::-1], # reverse the palette
)
# define web tools
TOOLS = "pan,wheel_zoom,box_zoom,reset,hover,save"
# set up bokeh figure
p = figure(
title="Population",
tools=TOOLS,
toolbar_location="below",
x_axis_location=None,
y_axis_location=None,
width=800,
height=800
)
# remove the grid
p.grid.grid_line_color = None
# core part !
p.patches(
'xs', 'ys',
fill_alpha=0.7,
fill_color={'field': 'population', 'transform': cmap},
line_color='black',
line_width=0.5,
source=geo_src
)
# show plot
show(p)
查看结果,
我的猜测是那些较暗的部门有重叠的形状,散景应用了两倍的人口使它们更暗...
我试图找到一种从 shapefile 中删除重叠的方法(到目前为止没有成功),但我想知道是否有一种方法可以配置 Bokeh 以要求它不汇总重叠?
好吧,我终于明白我做错了什么了。
这不是关于重叠或类似的东西(我用QGIS确认没有重叠)。 相反,我注意到比其他部门更暗的部门实际上被分成了几部分!
事情是这样的;在应用 Bokeh 补丁时,我使用了一个小于 1 的 fill_alpha。我只需要将此参数设置为 1,这样无论部门由什么部分组成,颜色都是相同的!
p.patches(
'xs', 'ys',
fill_alpha=1,
fill_color={'field': 'population', 'transform': cmap},
line_color='black',
line_width=0.5,
source=geo_src
)
我遇到了同样的问题,我有解决办法。
问题是当补丁使用 x
边界数据列表中的 nan
分隔符描述多个多边形时,使用 p.patches
的 Bokeh 中的错误。稍后我得想办法报告这个错误。
解决方案是改用“p.multi_polygons”。他们的多个多边形可以包含在每个条目中而不会受到错误的影响。 https://docs.bokeh.org/en/latest/docs/reference/models/glyphs/multi_polygons.html
我用 geojson 的工作不多,但我用 GeoPandas 工作过。以下是使用来自 https://www.naturalearthdata.com/downloads/:
的数据的工作示例import geopandas as gpd
from shapely.geometry.polygon import Polygon
from shapely.geometry.multipolygon import MultiPolygon
from bokeh.io import show, output_file
from bokeh.models import MultiPolygons, ColorMapper, LinearColorMapper
from bokeh.palettes import Inferno256 as palette
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.layouts import row, column
# Define the function I gave earlier
def get_MultiPoly(mpoly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') for the exterior and interior of MultiPolygon digestible by multi_polygons in Bokeh"""
if coord_type == 'x':
i=0
elif coord_type == 'y':
i=1
# Get the x or y coordinates
c = []
if isinstance(mpoly,Polygon):
mpoly = [mpoly]
for poly in mpoly: # the polygon objects return arrays, it's important they be lists or Bokeh fails
exterior_coords = poly.exterior.coords.xy[i].tolist()
interior_coords = []
for interior in poly.interiors:
if isinstance(interior.coords.xy[i],list):
interior_coords += [interior.coords.xy[i]]
else:
interior_coords += [interior.coords.xy[i].tolist()]
c.append([exterior_coords, *interior_coords])
return c
# Define input/output files and get data, index by name
output_file("tmp.html")
map_world_gpd = gpd.read_file('map_data/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp')
map_world_gpd.set_index('NAME_EN',inplace=True)
# Parse all countries into a ColumnDataSource
N=len(map_world_gpd)
source3 = ColumnDataSource({
'x': [get_MultiPoly(map_world_gpd.iloc[i]['geometry'],'x') for i in range(0,N)],
'y': [get_MultiPoly(map_world_gpd.iloc[i]['geometry'],'y') for i in range(0,N)],
'n': [i for i in range(0,N)],
'name':[map_world_gpd.iloc[i]['NAME'] for i in range(0,N)],
})
# Plot all the countries
p = figure(title="map_exploration",match_aspect=True,aspect_ratio=2,
tooltips=[
("Name",'@name')
],
)
p.multi_polygons(xs='x',ys='y', source=source3,
fill_color={'field': 'n', 'transform': LinearColorMapper(palette=palette,low=0,high=len(source3.data['x']))},
fill_alpha=0.5, line_color="black", line_width=0.5)
show(row(p,sizing_mode = 'scale_width'))
如果您对这个错误感兴趣,那么这里有一个显示错误的例子:
map_states_gpd = gpd.read_file('map_data/cb_2019_us_state_20m.shp')
map_states_gpd.set_index('NAME',inplace=True)
map_world_gpd = gpd.read_file('map_data/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp')
map_world_gpd.set_index('NAME_EN',inplace=True)
map_world_gpd = map_world_gpd.to_crs(epsg=3395)
gsource = map_world_gpd.loc['Spain']['geometry']
output_file("tmp.html")
def get_PolyCoords(poly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') of """
if coord_type == 'x':
# Get the x coordinates of the exterior
return list( poly.exterior.coords.xy[0] )
elif coord_type == 'y':
# Get the y coordinates of the exterior
return list( poly.exterior.coords.xy[1] )
def get_MultiPoly(mpoly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') for the exterior and interior of MultiPolygon digestible by multi_polygons in Bokeh"""
if coord_type == 'x':
i=0
elif coord_type == 'y':
i=1
# Get the x or y coordinates
c = []
if isinstance(mpoly,Polygon):
mpoly = [mpoly]
for poly in mpoly: # the polygon objects return arrays, it's important they be lists or Bokeh fails
exterior_coords = poly.exterior.coords.xy[i].tolist()
interior_coords = []
for interior in poly.interiors:
if isinstance(interior.coords.xy[i],list):
interior_coords += [interior.coords.xy[i]];
else:
interior_coords += [interior.coords.xy[i].tolist()]
c.append([exterior_coords, *interior_coords])
return c
def get_MultiPolyCords(mpoly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') of edges of a MultiPolygon exterior"""
if isinstance(mpoly,Polygon):
mpoly = [mpoly]
if coord_type == 'x':
# Get the x coordinates of the exterior
x = []
for poly in mpoly:
x.append(get_PolyCoords(poly,coord_type))
return x
elif coord_type == 'y':
# Get the y coordinates of the exterior
y = []
for poly in mpoly:
y.append(get_PolyCoords(poly,coord_type))
return y
def concat_MultiPolyCords(mpoly,coord_type='x'):
"""Returns the coordinates ('x' or 'y') of edges of a MultiPolygon exterior"""
if isinstance(mpoly,Polygon):
mpoly = [mpoly]
if coord_type == 'x':
# Get the x coordinates of the exterior
x = []
for poly in mpoly:
x.append(float('NaN'))
x = x+ get_PolyCoords(poly,coord_type)
return x
elif coord_type == 'y':
# Get the y coordinates of the exterior
y = []
for poly in mpoly:
y.append(float('NaN'))
y = y+ get_PolyCoords(poly,coord_type)
return y
def plen(mp):
if isinstance(mp,MultiPolygon):
return len(mp)
else:
return 1
def plen(mpoly):
if isinstance(mpoly,Polygon):
return 1
else:
return len(mpoly)
# Plot the patches using the same color
source1 = ColumnDataSource({
'x': get_MultiPolyCords(gsource,'x'),
'y': get_MultiPolyCords(gsource,'y'),
'n': [3]*plen(gsource),
})
# Plot the patches by combining them into one patch, separate sub patches with nan, all same color
# Note that the alpha outcome is faulty, it seems to redraw all the patches for each one
ys = [concat_MultiPolyCords(gsource,'y')]
source2 = ColumnDataSource({
'x': [concat_MultiPolyCords(gsource,'x')],
'y': [concat_MultiPolyCords(gsource,'y')],
'n': [3],
})
# Initialize our figure
p1 = figure(title="map_exploration",match_aspect=True,aspect_ratio=2)
# Plot grid
p1.patches('x', 'y', source=source1,
fill_color={'field': 'n', 'transform': LinearColorMapper(palette=palette,low=0,high=plen(gsource))},
fill_alpha=0.5, line_color="black", line_width=0.5)
p1.title.text = "Map has "+str(plen(gsource))+" polygons, patched using "+str(plen(source1.data['x']))+" patches"
# Initialize our figure
p2 = figure(title="map_exploration",match_aspect=True,aspect_ratio=2)
# Plot grid
p2.patches('x', 'y', source=source2,
fill_color={'field': 'n', 'transform': LinearColorMapper(palette=palette,low=0,high=plen(gsource))},
fill_alpha=0.5, line_color="black", line_width=0.5)
p2.title.text = "Map has "+str(plen(gsource))+" polygons, patched using "+str(plen(source2.data['x']))+" patches using NaN separators"
show(row(p1,p2,sizing_mode = 'scale_width',))
所以不要使用第二个代码,使用第一个来获得想要的结果。