如何使用 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',))

所以不要使用第二个代码,使用第一个来获得想要的结果。