我想用Bokeh生成一个chloropleth贴图。 我已经准备好法国部门及其人口的数据集。我还下载了法国部门的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)
查看结果,
我的猜测是,这些较暗的部门具有重叠的形状,而Bokeh施加了两倍的人口,使其变得更暗...
我试图找到一种方法来删除shapefile中的重叠部分(到目前为止还没有成功),但我想知道是否有一种方法可以配置Bokeh以使其不对重叠部分进行汇总?
答案 0 :(得分:0)
好的,我终于设法了解我做错了什么。
这与重叠或类似内容无关(我使用QGIS确认没有重叠)。 相反,我注意到比其他部门更暗的部门实际上是被分成几部分的部门!
这是东西;在应用Bokeh补丁时,我使用的fill_alpha小于1。我只需要将此参数设置为1,以便无论部门构成什么,颜色都将相同!
p.patches(
'xs', 'ys',
fill_alpha=1,
fill_color={'field': 'population', 'transform': cmap},
line_color='black',
line_width=0.5,
source=geo_src
)
答案 1 :(得分:0)
我遇到了同样的问题,并且有解决方案。
问题是当补丁使用p.patches
边界数据列表中的nan
分隔符描述多个多边形时,在使用x
的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',))
所以不要使用第二个代码,而要使用第一个代码以获得所需的结果。